Libgcrypt是著名的开源加密软件GnuPG的底层库,是一个非常成熟的加密算法库,支持多种对称和非对称加密算法。现在自己随便造轮子地写一个加密算法程序显然是非常不安全的,虽然OpenSSL出现了Heartbleed漏洞,但是用已经成熟的加密算法库还是会比不成熟的东西好很多的。最近看了看它最基本的一些功能,尝试写了一个AES的demo,中间还是学到蛮多东西的,所以写这篇手记。本文是以我写的一个gcrypt_demo为例,也可以当作这个demo的解释。
写个加密程序的第一步是开始加密,第二步是解密?你太天真了!第一步实际上是传入密钥。Libgcrypt的对称加密需要两个重要的参数,一个是密钥Key,一个是初始化向量Initialization Vector。后者一般由加密程序决定,而前者则需要用户来提供。如果我们直接拿用户输入的明文作为加密和解密的密钥,实在太不安全了,必须要经过一个搞乱的过程。而Libgcrypt提供的用来搞乱密钥的函数是gcry_kdf_derive。
gpg_error_t gcry_kdf_derive ( const void *passphrase, size_t passphraselen, int algo, int subalgo, const void *salt, size_t saltlen, unsigned long iterations, size_t keysize, void *keybuffer )
别看它很长很复杂,其实参数只有四部分:1.传入的密钥明文和长度;2.打乱用的算法;3.盐串和盐串的长度、迭代次数等打乱用的参数;4.生成的打乱后的密钥串。利用这个函数就能得到一个很好的密钥串了。举个例子,我把明文密钥放在buf里面,而输出的密钥放在buf里面,选用PBKDF2和SHA512算法进行散列,盐串存在SALT里面,迭代次数为ITER。
gpg_error_t err = gcry_kdf_derive(buf, strlen(buf), GCRY_KDF_PBKDF2, GCRY_MD_SHA512,SALT, sizeof(SALT), ITER, LEN_OF_RETKEY, outbuf);
基本上所有Libgcrypt的函数返回值都是错误句柄,接收它用来判错就好。
在获得密钥之后,我们就需要对加密的句柄进行设置。我们需要选定加密算法,顺便开好用来加密的缓存区间。我们采用比较简单的块模式进行加密,所以首先我们需要知道我们选定的加密算法所接受的密钥长度和块长度。在Libgcrypt中,加密算法用宏来标识,你需要传递指定的宏,来告知它你想用哪种加密算法。我的demo程序用的是AES256算法,但是为了通用起见,我们还是用一个CIPHER_ALGO来指代我们用的加密算法的具体名字。
首先我们需要得到基本的数据:
size_t key_size = gcry_cipher_get_algo_keylen(CIPHER_ALGO);
size_t block_size = gcry_cipher_get_algo_blklen(CIPHER_ALGO);
size_t block_required=file_size/block_size;
if (file_size % block_size != 0){
block_required++;
}
file_size是要被加密的数据文件的总大小。利用get_algo_keylen()和get_algo_blklen()两个函数我们可以得到选定的算法的密钥长度和块长度,然后计算总共有多少个块。
有了这些信息,我们就可以建一个句柄:
cipher_err=gcry_cipher_open(&cipher_hd, CIPHER_ALGO, GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_CBC_CTS);
cipher_err=gcry_cipher_setkey(cipher_hd,key,key_size);
cipher_err=gcry_cipher_setiv(cipher_hd, iv, block_size);
第一句建立加密句柄。第一个参数cipher_hd是加密用的句柄,类型是gcry_cipher_hd_t;第二个参数是加密算法,第三个参数和第四个参数指定加密的模式是块模式,详细的内容可以看libgcrypt的文档。第二句设置密钥,第三句设置初始化向量。这里接收的初始化向量是随便的一个字符串,只要足够长,超过block_size一般就没什么问题了,当然,能和block_size一样长就最好好。最后建立好读入缓存和输出缓存区就好。
char *input_buf = (char*)malloc(file_size);
char *cipher_buffer = malloc(block_size*block_required);
memset(cipher_buffer, 0, block_size*block_required);
做了这么多准备工作,终于开始加密了呢!假设fin是源数据流,而fout是输出流,都是文件,那么我们可以这样写加密的程序:
fread(input_buf,1,file_size,fin);
//将数据读入输入缓存
memcpy(cipher_buffer,input_buf,block_required*block_size);
//将数据复制到输出缓存中
cipher_err=gcry_cipher_encrypt(cipher_hd,cipher_buffer,
//在输出缓存上进行加密
block_required*block_size,NULL,0);
fwrite(cipher_buffer,1,block_required*block_size,fout);
//讲输出缓存输出到文件
gcry_cipher_close(cipher_hd);
fclose(fin);
fclose(fout);
//关闭加密句柄和文件流
要注意的是加密用的函数gcry_cipher_encrypt():
gcry_error_t gcry_cipher_encrypt (gcry_cipher_hd_t h, unsigned char *out, size_t outsize, const unsigned char *in, size_t inlen)
它接收的参数分为输出缓存和源数据。但是当源数据的指针为NULL时,它会直接以输出缓存中的数据为源数据,加密后放回去。加密到此结束,戛然而止。准备工作占了大头,真正的最核心的工作其实就这么一点。
对称加密完自然要解密,不然就没有意义了。解密的初始化工作和加密基本一致,只要用相同的密钥和初始化向量,就可以解密了。只要用gcry_cipher_decrypt()替代加密用的函数就可以了。我这里简单化处理了,如果最后全是0,那么就截取尾端。其实这么做是有问题的,应该加一个魔数标志结束啥的,不过demo嘛,就偷懒一下吧。加密纯文本文件的时候也没有这个问题。
fread(decry_buf,1,file_size,fin);
cipher_err=gcry_cipher_decrypt(cipher_hd,decry_buf,file_size,NULL,0);
while (decry_buf[file_size-1]==0){
file_size--;
}
fwrite(decry_buf,1,file_size,fout);
gcry_cipher_close(cipher_hd);
fclose(fin);
fclose(fout);
利用AES对称加密可以建造一个最基本的安全的数据交换体系。到目前为止,AES256算法还没有有效的破解手段,应该说还是很安全的。这个demo很简陋,有很多问题,不过作为demo已经足够了。不造轮子,而是使用成熟的库,应该算是优秀工程师的基本素养吧。最后希望大家喜欢(*^^)v