libgcrypt 使用

2014-07-16 security language

libgcrypt 是一个非常成熟的加密算法库,也是著名的开源加密软件 GnuPG 的底层库,支持多种对称、非对称加密算法,以及多种 Hash 算法。

接下来,看看该库的使用方式。

简介

现代加密算法中,对称密钥算法通常被分为 Stream CiphersBlock Ciphers

分组密码也被称分块密码,是一种对称密钥密码,会将明文分成多个等长的分组 (其中常见的有64 bits、128 bits、256 bits),并用相同的密码算法和密钥对每组分别进行加密和解密,常见的有 DES、3DES、AES、IDEA、Blowfish、Twofish 。

分块密码加密以 bit 为单位,用来处理固定长度的字符串,需要加密的比特长度必须和分块大小一样长,或者是分块的整数倍,而且输入 (明文) 和输出 (密文) 都是同样长度的。

之所以输出不能比输入短,是遵循 Pigeonhole Principle (鸽巢原理) 和密码必须是可逆的事实,然而,输出比输入更长又是不可取的。

流加密算法每次对 1bit 或者 1byte 进行加密,通过无线循环的伪随机数生成器 (PRG,pseudo-random generator) 作为 key ,所以需要保证 PRG 的不可预测性,不过其生成的密钥本身可能会比要加密的数据大,常见的有 RC4 ,不过一些块加密模式 CTR、OFB 有类似流加密的功能。

对称加密

简单介绍下常见的对称加密算法。

XOR

简单的异或就是对称加密最基本的操作。

01100001 --- a
01100010 --- b
------------ XOR
00000011

如果把 a 称作明文,b 称作密钥,XOR 则为加密,得到的结果是密文。

将明文与一串等长的随机比特序列进行XOR运算,则密码被称为一次性密码。

一次性密码是由 G.S.Vernam 与 1917 年提出的,因此又称维纳密码 (Vernam Cipher);1949 年香农 (C.E.Shannon) 通过数学方法证明一次性密码无法破解。

为什么说一次性密码无法破解?假设我们对其进行暴力破解,遍历所有可能的密钥只是时间问题,然而即使可以遍历所有密钥,你也无法判断解密出的数据哪个是正确的明文。

拿上文中的异或例子来说,遍历所有可能的 b 一共有 256 种可能,例如,通过暴力破解结果可能是 000000110000001000000110 等等,那么哪个是正确的呢?

之所以一次性密码并没有被使用,主要有两个原因:A) 只用一次的密钥如何配送;B) 密钥必须与明文等长。

RC4

Rivest Cipher 4, RC4 由美国密码学家罗纳德·李维斯特 (Ron Rivest) 在 1987 年设计,是一种流加密算法,密钥长度可变,它加解密使用相同的密钥,因此也属于对称加密算法。

RC4 是有线等效加密 (WEP) 中采用的加密算法,也曾经是 TLS 可采用的算法之一,由于 RC4 算法存在弱点,2015.02 所发布的 RFC 7465 规定禁止在 TLS 中使用 RC4 加密算法。

DES

Data Encryption Standard, DES 是一种对称密钥分组加密算法,1976 年被美国联邦政府的国家标准局确定为联邦资料处理标准 (FIPS),它基于 56 位密钥的对称算法。

DES 现在已经不再是一种安全的加密方法,主要因为它使用的 56 位密钥过短,而且在 1999.01 时,distributed.net 与电子前哨基金会合作,在 22 小时 15 分钟内即公开破解了一个 DES 密钥。

DES 是一种分组加密算法,一组长度为 64 位,不过只有 56 位被实际用于算法,其余 8 位可以被用于校验,并在算法中被丢弃,也就是说 DES 的有效密钥长度仅为 56 位。

Blowfish

Blowfish 是 1993 年布鲁斯·施奈尔 (Bruce Schneier) 开发的对称密钥区块加密算法,区块长为 64 位,密钥为 1 至 448 位的可变长度,是 Feistel Cipher 的一种,与 DES 等算法相比,其处理速度较快,而且可以免费使用。

cipher ecb mode encrypt

工作模式

前面说的 DES、AES 等分组密码,都只能加密固定长度的明文,例如 AES 输入是 128bit,而实际使用时明文长度不确定,那么此时如何加密呢?

工作模式本质上是分组密码迭代方式,如果模式选择不恰当,那么可能会带来安全隐患。我们需要寻找一种模式,至少得满足:A) 相同的明文分组加密后密文不同;B) 明文微小变化都能造成密文有很大变化。

详细可以查看 Block cipher mode of operation 中的介绍。

ECB 模式

也就是电子密码本模式 (Electronic CodeBook, ECB),这是最简单的块密码加密模式,加密前根据加密块大小 (如 AES 为 128 bit) 分成若干块,之后将每块使用相同的密钥单独加密,解密同理。

cipher ecb mode encrypt

cipher ecb mode decrypt

ECB 模式是最简单的一种,好处是每块数据的加解密可以并行计算;但是也有很严重的问题,就是相同的明文会得到同样的密文,在某些环境下不能提供严格的数据保密性。

CBC 模式

密码分组链接 (Cipher-Block Chaining, CBC),由 IBM 在 1976 年发明,每个明文块先与前一个密文块进行异或后,再进行加密,其中第一个明文块需要与一个叫初始化向量的数据块异或,一般来说初始化向量采用随机值。

cipher cbc mode encrypt

cipher cbc mode decrypt

CBC 相比 ECB 有更高的保密性,但由于对每个数据块的加密依赖与前一个数据块的加密,所以加密无法并行。而且与 ECB 一样在加密前可能需要对数据进行填充,不适合对流数据进行加密。

CFB 模式

密文反馈模式 (Cipher FeedBack, CFB),前一个密文分组会被送入密码算法的输入端,再将输出的结果与明文做异或。

与 ECB 和 CBC 模式只能够加密块数据不同,CFB 能够将块密文 (Block Cipher) 转换为流密文 (Stream Cipher)。CFB 的加密工作分为两部分:A) 将一前段加密得到的密文再加密;B) 将第 A 步加密得到的数据与当前段的明文异或。

cipher cfb mode encrypt

cipher cfb mode decrypt

注意:CFB、OFB 和 CTR 模式中解密也都是用的加密器而非解密器。

OFB 模式

输出反馈模式 (Output Feedback, OFB),前一组密码算法输出会输入到下一组密码算法输入。

cipher ofb mode encrypt

cipher ofb mode decrypt

CTR 模式

计数器模式 (Counter, CTR),每个分组对应一个累加的计数器,并通过计数器来生成加密密钥流。

cipher ctr mode encrypt

cipher ctr mode decrypt

图中 Nonce+Counter 是一个计数器,Nonce 和前面几种模式的 IV 类似,每次加密都需要随机生成,而计数器 Counter 是累加的。CTR 模式特点是每组加密都是独立的,不依赖前一组,这就意味着在生成计数器后,每个分组可以并行计算。

模式选择

简单介绍下各个模式的有缺点。

<<<<< ECB
优点
    简单
    快速
    支持并行计算(加密、解密)
缺点
    明文中的重复排列会反映在密文中
    通过删除、替换密文分组可以对明文进行操作
备注:不应使用

<<<<< CBC
优点
    明文的重复排列不会反映在密文中
    支持并行计算(解密)
缺点
    加密不支持并行计算
备注:推荐使用

<<<<< CFB
优点
    不需要填充
    支持并行计算(解密)
缺点
    加密不支持并行
备注:推荐使用CTR模式代替

<<<<< OFB
优点
    不需要填充
    可事先进行加密、解密准备
    加解密使用相同结构
缺点
    不支持并行
备注:推荐使用CTR模式代替

<<<<< CTR
优点
    不需要填充
    可事先进行加密、解密准备
    加解密使用相同结构
    支持并行计算(加密、解密)
备注:推荐使用

填充

对称加密要求输入明文长度必须是块长度的整数倍,当长度不满足的时候需要进行填充,其中使用比较多的有 PKCS ISO10126 Zero 几种方式。

PKCS

一般使用的是 PKCS#5PKCS#7,两者是同种填充算法,只是前者要求块大小是确定的 (16字节),而后者不确定 (1~256)。

如果需要加密明文为块的整数倍,那么仍然需要填充一个完整的块,否则解密时会无法获取到实际 Padding 的长度。

例如,对于 PKCS#5 来说,现有 10 个字节,需要填充 (16-10=6) 个字节,而这 6 个字节填充的值,也就是其长度 0x06

ISO 10126

通过最后一个字节标示需要填充数据的长度,中间的都是随机数,这样的话加密更加具备迷惑性,这样也导致了如果是加密块整数倍的话需要多出来一个块。

Zero-Padding

这种方式比较适合于字符串的解密方式,获取的字符串不需要再进行 Padding 的逆向处理。

注意,如果是字符串,那么在计算长度时应该加上终止字符 \0

libgcrypt 编程

简单介绍常见的编程方式,详细内容可以参考官方文档 The Libgcrypt Reference Manual

常用概念

初始化向量

Initialization Vector, IV 也就是用于开始随机化加密的一块数据,因此可以由相同的明文,相同的密钥产生不同的密文,而无需重新产生密钥。

一般来说 IV 无需保密,而且在同一密钥的情况下不要使用相同的 IV ,否则可能会导致不安全。

填充

块密码只能对指定长度的数据块进行处理,而消息的长度通常是可变的,因此部分模式 (ECB、CBC) 需要将最后一块在加密前进行填充,以满足特定长度的需求。

对称加密

也就是直接使用 libgcrypt 对称加密。

1. 传入密钥

一般不会直接使用用户的输入直接作为密钥,而是通过一个密钥导出函数 (如PBKDF2) 生成,入参中有两个比较重要的函数:A) 输入密码,用户输入;B) 初始化向量,程序提供。

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. passphrase, passphraselen  传入的密钥明文和长度
  2. algo, subalgo, iterations 使用的 Key Derivation Function (KDF) 算法,及其迭代次数
  3. salt, saltlen 加盐的盐串和长度
  4. keysize, keybuffer 用于保存生成密钥的缓存长度,以及返回密钥内容

2. 初始化加密句柄

在获得密钥之后,就需要对加密的句柄进行设置,在后续的所有操作中都会使用该句柄操作。

接着,需要选定加密算法,以及加密模式,在 libgcrypt 中,加密算法用宏来标识,需要传递指定的宏,来告知它想用哪种加密算法。

size_t gcry_cipher_get_algo_keylen (int algo);
size_t gcry_cipher_get_algo_blklen (int algo);
gcry_error_t gcry_cipher_open (gcry_cipher_hd_t *hd, int algo, int mode, unsigned int flags);
gcry_error_t gcry_cipher_setkey (gcry_cipher_hd_t h, const void *k, size_t l);
gcry_error_t gcry_cipher_setiv (gcry_cipher_hd_t h, const void *k, size_t l);

这里需要注意的是,初始化向量一般是一个 Block Size,而后续准备加密的数据需要保证是 Block Size 的整数倍,否则会报错。

3. 加解密

接着就是通过如下函数加解密了。

gcry_error_t gcry_cipher_encrypt (gcry_cipher_hd_t h, unsigned char *out, size_t outsize,
             const unsigned char *in, size_t inlen)
gcry_error_t gcry_cipher_decrypt (gcry_cipher_hd_t h, unsigned char *out, size_t outsize,
             const unsigned char *in, size_t inlen)

其它

HMAC

HMAC 是密钥相关的哈希运算消息认证码,HMAC 运算时利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。

HMAC 的一个典型应用是用在 Challenge/Response 身份认证中,一般的处理流程如下:

  1. 客户端向服务器发出一个验证请求。
  2. 服务器接到此请求后生成一个随机数并通过网络传输给客户端 (Challenge)。
  3. 客户端将收到的随机数与客户保存的密码做 HMAC-MD5 计算,并将结果作为认证证据传给服务器 (Response)。
  4. 服务器同样执行 HMSC-MD5 运算,与客户端传回的响应结果比较,如果相同则认为客户端是一个合法用户。

参考