Libsodium 是一个用C语言编写的库,是一种新的易于使用的高速软件库,用于网络通信、加密、解密、签名等实现和简化密码学。
完成 Libsodium 安装
Libsodium 是一个用于加密,解密,数字签名,密码哈希,等的,现代的,易用的密码学库。如果你在环境中没有安装sodium。可以执行如下操作:
yum -y install libsodium libsodium-devel
Libsodium 使用方法
在程序中,只需包含头文件 sodium.h 即可。库的名字是 sodium (使用 -lsodium 来链接)。
sodium_init() 初始化 libsodium 库,必须在 libsodium 库的其他函数之前被调用。也可以自己实现sodium_init。也就是在 Unix /Linux系统中, 会打开 /dev/urandom 文件,并且会保持这个文件 fd 打开.
void randombytes(uint8_t buffer[], unsigned long long size)
{
int fd;
fd = open( "/dev/urandom", O_RDONLY );
if( fd < 0 ) {
fprintf( stderr, "Failed to open /dev/urandom\n" );
exit(1);
}
int rc;
if( (rc = read( fd, buffer, size )) >= 0 ) {
close( fd );
}
}
一些基础知识
- 什么是密钥
一般就是一个字符串或数字,在加密或者解密时传递给加密/解密算法。
- 什么是对称加密算法
加密和解密都是使用的同一个密钥。因此对称加密算法要保证安全性的话,密钥要做好保密,只能让使用的人知道,不能对外公开。
- 什么是非对称加密算法
加密使用的密钥和解密使用的密钥是不同的。 公钥密码体制就是一种非对称加密算法。
- 如何理解公钥密码体制
主要分为三个部分:公钥、私钥、加密/解密算法。
可以理解为,加密解密过程如下:
加密:通过加密算法和公钥对内容(或者说明文)进行加密,得到密文。
解密:通过解密算法和私钥对密文进行解密,得到明文。
公钥密码体制的公钥和算法都是公开的,私钥是保密的。在实际的使用中,有需要的人会生成一对公钥和私钥,把公钥发布出去给别人使用,自己保留私钥。
- 简解签名和验签
服务器有两把钥匙一把公钥、一把私钥。客户端想和服务器达到秘密交流就需要服务器特有的公钥进行加密这样别人就不知道你发送的是什么,服务器收到客户端的信息后用特有的私钥解密就能看到信息内容。服务器要给客户端返回消息,这时候就会使用Hash函数对信息生成一个摘要(Digest),用私钥对摘要加密生成数字签名(Signature),将消息和数字签名一起返回给客户端。客户端收到返回后,先使用公钥对数字签名解密得到摘要,再将信息通过Hash函数生成摘要,只要这两个摘要相同则证明该信息是中途没有被篡改过的。
值得注意的是,这样也容易出现信息泄漏。因为数字签名一被更换也就不能确定是否为真实的信息发起者发出的信息了。所以为了证明服务器的数字签名是否为真实的,服务器需要将公钥进行认证,会有一个数字证书,这个证书证明了这个公钥就是这台服务器独有的。服务器返回消息的时候会将数字证书一起发送过来,客户端接收到后就可以使用证书中心声明的公钥去解开数字证书来得到这台服务器的公钥。就能判断是否为真实的公钥了。然后客户端就可以使用证书中的服务器公钥,对信息进行加密,然后与服务器交换加密信息。
Libsodium 一些常用API
int crypto_sign_keypair(unsigned char *pk, unsigned char *sk);
crypto_sign_keypair()函数随机生成密钥和相应的公钥。公钥放入pk(crypto_sign_PUBLICKEYBYTES字节),密钥放入sk(crypt_sign_SECRETKEYBYTES)。
int crypto_box_keypair(unsigned char *pk, unsigned char *sk);
crypto_box_keypair()函数随机生成密钥和相应的公钥。公钥放入pk(crypto_box_PUBLICKEYBYTES字节),密钥放入sk(crypt_box_SECRETKEYBYTES)。
libsodium 的设计尽可能地隐藏了加密算法的实现细节,从它的函数名就可以看出来。例如包含了 X25519 密钥交换和 ChaCha20Poly1305 认证加密的相关函数统一称为 crypto_box_*
int crypto_scalarmult_curve25519_base(unsigned char *q, const unsigned char *n)
从私钥生成公钥需要用到 crypto_scalarmult_curve25519_base, curve25519 系列算法可以使用任意的 32 字节作为私钥,所以可以从密码学安全的伪随机数生成器自行生成私钥。
int crypto_sign(unsigned char *sm, unsigned long long *smlen_p,
const unsigned char *m, unsigned long long mlen,
const unsigned char *sk);
crypto_sign()函数使用密钥sk为长度为mlen字节的消息m添加签名。签名的消息(包括签名和未更改的消息副本)放入sm中,长度为crypto_sign_BYTES+mlen字节。
int crypto_sign_open(unsigned char *m, unsigned long long *mlen_p,
const unsigned char *sm, unsigned long long smlen,
const unsigned char *pk);
crypto_sign_open()函数检查长度为smlen字节的签名消息sm是否具有公钥pk的有效签名。如果签名无效,则函数返回-1。
Libsodium 测试例子
一个关于如何使用libnaid加密库的简单示例。这个例子也适用于几乎相同的libnacl库——只需更改include即可。
- 加密/解密
...
void randombytes(uint8_t buffer[], unsigned long long size)
{
int fd;
fd = open( "/dev/urandom", O_RDONLY );
if( fd < 0 ) {
fprintf( stderr, "Failed to open /dev/urandom\n" );
exit(1);
}
int rc;
if( (rc = read( fd, buffer, size )) >= 0 ) {
close( fd );
}
}
char* to_hex( char hex[], const uint8_t bin[], size_t length )
{
int i;
uint8_t *p0 = (uint8_t *)bin;
char *p1 = hex;
for( i = 0; i < length; i++ ) {
snprintf( p1, 3, "%02x", *p0 );
p0 += 1;
p1 += 2;
}
return hex;
}
int is_zero( const uint8_t *data, int len )
{
int i;
int rc;
rc = 0;
for(i = 0; i < len; ++i) {
rc |= data[i];
}
return rc;
}
...
User *new_user(char* name)
{
User* user;
user = (User*) malloc(sizeof(User));
user->name = name;
crypto_box_keypair(user->public_key, user->secret_key);
return user;
}
void print_user(User *user)
{
char phexbuf[2*crypto_box_PUBLICKEYBYTES+1];
char shexbuf[2*crypto_box_SECRETKEYBYTES+1];
printf("username: %s\n", user->name);
printf("public key: %s\n", to_hex(phexbuf, user->public_key, crypto_box_PUBLICKEYBYTES ));
printf("secret key: %s\n\n", to_hex(shexbuf, user->secret_key, crypto_box_SECRETKEYBYTES ));
}
int main( int argc, char **argv )
{
...
rc = encrypt(encrypted, bob->public_key, eve->secret_key, nonce, msg, strlen(msg));
if( rc < 0 ) {
return 1;
}
printf("encrypted: %s\n", to_hex(hexbuf, encrypted, rc ));
uint8_t decrypted[1000];
rc = decrypt(decrypted, eve->public_key, bob->secret_key, nonce, encrypted, rc);
if( rc < 0 ) {
return 1;
}
decrypted[rc] = '\0';
printf("decrypted: %s\n", decrypted);
return 0;
}
编译运行:
- 签名/验签
...
int sign(uint8_t sm[], const uint8_t m[], const int mlen, const uint8_t sk[]) {
unsigned long long smlen;
if( crypto_sign(sm,&smlen, m, mlen, sk) == 0) {
return smlen;
} else {
return -1;
}
}
int verify(uint8_t m[], const uint8_t sm[], const int smlen, const uint8_t pk[]) {
unsigned long long mlen;
if( crypto_sign_open(m, &mlen, sm, smlen, pk) == 0) {
return mlen;
} else {
return -1;
}
}
int main(int argc, char **argv[])
{
...
int rc = crypto_sign_keypair(pk, sk);
if(rc < 0) {
return 1;
}
int smlen = sign(sm, m, mlen, sk);
if(smlen < 0) {
return 1;
}
mlen = verify(m, sm, smlen, pk);
if(mlen < 0) {
return 1;
}
printf("Verified!\n");
return 0;
}
编译运行:
- 密钥的派生
...
int crypto_box_recover_public_key(uint8_t secret_key[]) {
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
char phexbuf[2*crypto_sign_PUBLICKEYBYTES+1];
crypto_scalarmult_curve25519_base( public_key, secret_key );
printf("recovered public_key: %s\n", to_hex(phexbuf, public_key, crypto_sign_PUBLICKEYBYTES));
}
void crypto_box_example()
{
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
uint8_t secret_key[crypto_box_SECRETKEYBYTES];
char phexbuf[2*crypto_box_PUBLICKEYBYTES+1];
char shexbuf[2*crypto_box_SECRETKEYBYTES+1];
crypto_box_keypair(public_key, secret_key);
printf("public_key: %s\n", to_hex(phexbuf, public_key, crypto_box_PUBLICKEYBYTES));
printf("secret_key: %s\n", to_hex(shexbuf, secret_key, crypto_box_SECRETKEYBYTES));
crypto_box_recover_public_key(secret_key);
}
int crypto_sign_recover_public_key(uint8_t secret_key[]) {
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
char phexbuf[2*crypto_sign_PUBLICKEYBYTES+1];
memcpy(public_key, secret_key+crypto_sign_PUBLICKEYBYTES, crypto_sign_PUBLICKEYBYTES);
printf("recovered public_key: %s\n", to_hex(phexbuf, public_key, crypto_sign_PUBLICKEYBYTES));
}
void crypto_sign_example()
{
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
uint8_t secret_key[crypto_sign_SECRETKEYBYTES];
char phexbuf[2*crypto_sign_PUBLICKEYBYTES+1];
char shexbuf[2*crypto_sign_SECRETKEYBYTES+1];
crypto_sign_keypair(public_key, secret_key);
printf("public_key: %s\n", to_hex(phexbuf, public_key, crypto_sign_PUBLICKEYBYTES));
printf("secret_key: %s\n", to_hex(shexbuf, secret_key, crypto_sign_SECRETKEYBYTES));
crypto_sign_recover_public_key(secret_key);
}
int main( int argc, char **argv )
{
printf("\ncrypto_sign_example:\n");
crypto_sign_example();
printf("\ncrypto_box_example:\n");
crypto_box_example();
}
编译运行
实践:发送/接收可验证的单个加密消息
gen_keypair将创建单个密钥对 。需要两个密钥对才能发送经过验证的消息。
gen_keypair:
...
int main(int argc, char *argv[])
{
...
if(argc != 2) {
fprintf(stderr, "%s: <NAME>\n", argv[0]);
return 1;
}
/* Generate Key Pair */
crypto_box_keypair(&public_key[0], &secret_key[0]);
snprintf(filename, sizeof(filename), "%s.pub", argv[1]);
err = savefile(filename, &public_key[0], crypto_box_PUBLICKEYBYTES);
if(err) {
printf("ERROR saving public key: %s\n", filename);
} else {
printf("Saved public key: %s\n", filename);
}
snprintf(filename, sizeof(filename), "%s.key", argv[1]);
err = savefile(filename, &secret_key[0], crypto_box_SECRETKEYBYTES);
if(err) {
printf("ERROR saving secret key: %s\n", filename);
} else {
printf("Saved secret key: %s\n", filename);
}
return 0;
}
编译运行:
ip_receptor:
...
int main(int argc, char *argv[])
{
...
parse_args(argc, argv);
g_publickey = (unsigned char *)get_file(g_pfilename);
if(!g_publickey) {
fprintf(stderr, "get_file(%s) failed!\n", g_pfilename);
exit(EXIT_FAILURE);
}
g_secretkey = (unsigned char *)get_file(g_sfilename);
if(!g_secretkey) {
fprintf(stderr, "get_file(%s) failed!\n", g_sfilename);
exit(EXIT_FAILURE);
}
ph = pcap_open_live(g_dev, 65535, 1, 1, pcap_errbuf);
if(!ph) {
fprintf(stderr, "pcap_open_live(%s, 65535, 1, 1): %s\n", g_dev, pcap_errbuf);
return 2;
}
...
printf("Starting capture on %s (%s)\n", (g_dev ? g_dev : "all"), "LINKTYPE");
printf("Looking for IP Protocol %u\n", g_proto);
...
pcap_close(ph);
...
printf("Packet Count: %lu\n", g_pktcount);
return 0;
}
struct options opts[] =
{
{ 1, "proto", "IP Protocol to listen for", "p", 1 },
{ 2, "dev", "Interface to listen on", "i", 1 },
{ 3, "public", "Public Key", NULL, 1 },
{ 4, "secret", "Secret Key", NULL, 1 },
{ 0, NULL, NULL, NULL, 0 }
};
static void parse_args(int argc, char **argv)
{
char *args;
int c;
while ((c = getopts(argc, argv, opts, &args)) != 0) {
switch(c) {
case -2:
fprintf(stderr, "Unknown Getopts Option: %s\n", args);
break;
case -1:
fprintf(stderr, "Unable to allocate memory for getopts().\n");
exit(EXIT_FAILURE);
break;
case 1:
g_proto = atoi(args);
break;
case 2:
g_dev = strdup(args);
break;
case 3:
g_pfilename = strdup(args);
break;
case 4:
g_sfilename = strdup(args);
break;
default:
fprintf(stderr, "Unexpected getopts Error! (%d)\n", c);
break;
}
free(args);
}
if(g_proto > 255) {
fprintf(stderr, "I need a protocol! (Fix with -p)\n");
exit(EXIT_FAILURE);
}
if(!g_pfilename) {
fprintf(stderr, "I need a Public Key! (Fix with --public)\n");
exit(EXIT_FAILURE);
}
if(!g_sfilename) {
fprintf(stderr, "I need a Secret Key! (Fix with --secret)\n");
exit(EXIT_FAILURE);
}
}
ip_knock:
...
int main(int argc, char *argv[])
{
...
parse_args(argc, argv);
publickey = (unsigned char *)get_file(g_pfilename);
if(!publickey) {
fprintf(stderr, "get_file(%s) failed!\n", g_pfilename);
exit(EXIT_FAILURE);
}
secretkey = (unsigned char *)get_file(g_sfilename);
if(!secretkey) {
fprintf(stderr, "get_file(%s) failed!\n", g_sfilename);
exit(EXIT_FAILURE);
}
l = libnet_init(LIBNET_RAW4, g_dev, errbuf);
if(l == NULL) {
fprintf(stderr, "libnet_init() failed: %s\n", errbuf);
exit(EXIT_FAILURE);
}
dst_ip = libnet_name2addr4(l, g_dst, LIBNET_RESOLVE);
if(dst_ip == -1) {
fprintf(stderr, "Bad destination IP address: %s\n", g_dst);
exit(EXIT_FAILURE);
}
src_ip = libnet_get_ipaddr4(l);
if(src_ip == -1) {
fprintf(stderr, "Couldn't get own IP address: %s\n", libnet_geterror(l));
exit(EXIT_FAILURE);
} else {
printf("Using: %s\n", libnet_addr2name4(src_ip, LIBNET_DONT_RESOLVE));
}
...
libnet_destroy(l);
return 0;
}
struct options opts[] =
{
{ 1, "dst", "Destination", "d", 1 },
{ 2, "proto", "Protocol", "p", 1 },
{ 3, "dev", "Device", "i", 1 },
{ 4, "public", "Public Key", NULL, 1 },
{ 5, "secret", "Secret Key", NULL, 1 },
{ 6, "message", "Message", "m", 1 },
{ 0, NULL, NULL, NULL, 0 }
};
...
运行结果:
ip_receptor:
ip_knock:
tcpdump抓包:
ip_knock将发送一个用ip封装的加密消息,协议值由你选择。
ip_receptor将监听具有特定ip协议值的加密消息,并使用提供的密钥对其进行认证。
验证消息后,ip_receptor将默认对消息运行系统命令。
If you need the complete source code of xxd, add your WeChat number (c17865354792)
总结
使用libsodium库头文件sodium.h 是唯一需要包含的头文件。编译时库的名字是 sodium (使用 -lsodium 来链接)。Libsodium 是一个用于加密,解密,数字签名,密码哈希,等的现代的易用的密码学库。更多了解,请参考官网文档。
Welcome to follow WeChat official account【程序猿编码】
参考:
1.https://doc.libsodium.org/installation#integrity-checking
2.https://learnku.com/articles/54114
3.https://www.itcodet.com/cpp/