今天我们来看看如何用OpenSSL的API来实现Blowfish对称加解密。
什么是对称加密?
对称加密:简单来说,“加密”就是把容易识别的信息变成不易识别的信息;而“对称”则表示加密者和解密者之间拥有相同的密钥。当然,既然有“对称”的加密,那肯定有“非对称”的加密,这个我们后续再详细讨论。
什么是Blowfish?
Blowfish是一个对称的块加密算法。它的块大小是64bit(8字节),同时Blowfish支持变长的密钥,Blowfish算法的主要优点是加解密速度快。
如何用OpenSSL来实现Blowfish加解密?
我们先看API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 1
2
3
4
5 #include <openssl/blowfish.h>
6
7 void BF_set_key(BF_KEY *key, int len, const unsigned char *data);
8
9 void BF_ecb_encrypt(const unsigned char *in, unsigned char *out,
10
11 BF_KEY *key, int enc);
12
13 void BF_cbc_encrypt(const unsigned char *in, unsigned char *out,
14
15 long length, BF_KEY *schedule, unsigned char *ivec, int enc);
16
17 void BF_cfb64_encrypt(const unsigned char *in, unsigned char *out,
18
19 long length, BF_KEY *schedule, unsigned char *ivec, int *num,
20
21 int enc);
22
23 void BF_ofb64_encrypt(const unsigned char *in, unsigned char *out,
24
25 long length, BF_KEY *schedule, unsigned char *ivec, int *num,
26
27 int enc);
28
29 const char *BF_options(void);
30
31 void BF_encrypt(BF_LONG *data,const BF_KEY *key);
32
33 void BF_decrypt(BF_LONG *data,const BF_KEY *key);
34
35
36
乍一看,感觉挺复杂,其实不复杂。
我们先看最先的那个BF_set_key函数,它是用来初始化密钥的,把密钥信息(首地址是data,长度为len的字节数组)设置到BF_KEY结构里。设置之后,我们只需要使用BF_KEY结构就可以表征密钥了,在后续调用加解密函数时,我们只需要把这个结构传递进去就可以了。这里需要注意的一点是Blowfish实际使用的密钥是根据用户的密钥信息进行运算产生的,这是一个比较耗时的过程,这个BF_set_key函数就进行了密钥初始化的过程,所以,我们应该调用一次BF_set_key,然后重复使用经过初始化后的BF_KEY结构,而不是每次需要加解密时都用data和len来重新初始化一把BF_KEY。
我们再来看最后那2个BF_encrypt和BF_decrypt函数,这2个函数是中间那4个函数调用的内部函数,我们一般无需关心,除非你想重新实现Blowfish算法。
接下来,我们把注意力集中到中间的那4个函数身上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1
2
3
4
5 void BF_ecb_encrypt(const unsigned char *in, unsigned char *out, BF_KEY *key, int enc);
6
7 void BF_cbc_encrypt(const unsigned char *in, unsigned char *out, long length, BF_KEY *schedule, unsigned char *ivec, int enc);
8
9 void BF_cfb64_encrypt(const unsigned char *in, unsigned char *out, long length, BF_KEY *schedule, unsigned char *ivec, int *num, int enc);
10
11 void BF_ofb64_encrypt(const unsigned char *in, unsigned char *out, long length, BF_KEY *schedule, unsigned char *ivec, int *num, int enc);
12
13
14
这4个函数里的enc、in、out参数的语义是一样的,enc填BF_ENCRYPT时表示加密,填BF_DECRYPT时表示解密,参数in为输入数据的地址(待加密或待解密的数据),out为输出数据的地址(加密后或解密后的数据)。
我们先看BF_ecb_encrypt,它也是一个中间函数,因为它只能加解密一个“块”的数据,也就是8个字节,所以这个函数不需要调用者传入待加解密数据的长度。我们一般不使用它。
接下来是BF_cbc_encrypt,它比BF_ecb_encrypt封装的层次更高,不过,它虽然有个length,但它要求待加解密的数据长度必须为块大小(8字节)的整数倍,这个对普通用户是不方便的,因为涉及到了尾部数据的填充策略,所以我们一般也不用这个函数。这个函数有个ivec参数,这个参数要求拥有8字节的空间,我们可以把这个ivec也看作是密钥的一部分,因为ivec指向的8字节的内容会影响到加解密的结果,虽然ivec具体是什么内容由调用者决定,但调用者必须保证加密者和解密者之间对ivec的取值达成一致,不然将会导致解密失败。比较高级的做法可以让加密者和解密者之间约定这个ivec指向的内容如何进行同步的变化,这样做可以增强安全性;比较简单的做法,可以让加密者和解密者都把这个参数所指向的内容设置为字节0,然后一直保持不变。
我们最常用,同时也是最好用的是最后那2个函数,它们提供了比前面几个函数更高层次的封装。
1
2
3
4
5
6
7
8
9
10 1
2
3
4
5 void BF_cfb64_encrypt(const unsigned char *in, unsigned char *out, long length, BF_KEY *schedule, unsigned char *ivec, int *num, int enc);
6
7 void BF_ofb64_encrypt(const unsigned char *in, unsigned char *out, long length, BF_KEY *schedule, unsigned char *ivec, int *num, int enc);
8
9
10
它们不要求length必须为8的整数倍,所以使用很方便。这2个函数多了个num参数,我们只需要保证这num指向一个int整数,初始化为0,后续一直把这个num传递给相应的函数即可。
至于这2个函数之间的区别,其实是个加密反馈的概念,简单的说,就是已经加密的数据对后续加密结果的影响策略。
其实前面BF_cbc_encrypt和BF_ecb_encrypt的加密反馈策略也是不同。
cbc、ecb、cfb、ofb这些名称都代表了一种加密反馈模式,这个我们后续再详细讨论。