openssl AES加密以及padding
好习惯,先上代码再说事
加密
void AesEncrypt(unsigned char* pchIn, int nInLen, unsigned char *ciphertext, int &ciphertext_len, unsigned char * pchKey) { EVP_CIPHER_CTX en; EVP_CIPHER_CTX_init(&en); const EVP_CIPHER *cipher_type; unsigned char *passkey, *passiv, *plaintxt; unsigned char *plaintext = NULL; unsigned char iv[] = { 0x00}; cipher_type = EVP_aes_128_ecb(); EVP_EncryptInit_ex(&en, cipher_type, NULL, pchKey, iv); //当长度正好为16字节的倍数时,同样需要padding static const int MAX_PADDING_LEN = 16; if(!EVP_EncryptInit_ex(&en, NULL, NULL, NULL, NULL)) { printf("ERROR in EVP_EncryptInit_ex \n"); return; } int bytes_written = 0; ciphertext_len = 0; if(!EVP_EncryptUpdate(&en, ciphertext, &bytes_written, (unsigned char *) pchIn, nInLen) ) { printf("ERROR in EVP_EncryptUpdate \n"); return; } ciphertext_len += bytes_written; if(!EVP_EncryptFinal_ex(&en, ciphertext + bytes_written, &bytes_written)) { printf("ERROR in EVP_EncryptFinal_ex \n"); return; } ciphertext_len += bytes_written; ciphertext[ciphertext_len] = 0; EVP_CIPHER_CTX_cleanup(&en); return; }
解密
void AesDecrypt(unsigned char* pchInPut, int nInl, unsigned char *pchOutPut, unsigned char *pchKey) { unsigned char achIv[8]; EVP_CIPHER_CTX ctx; EVP_CIPHER_CTX_init(&ctx); EVP_DecryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, pchKey, achIv); int nLen = 0; int nOutl = 0; EVP_DecryptUpdate(&ctx, pchOutPut+nLen, &nOutl, pchInPut+nLen, nInl); nLen += nOutl; EVP_DecryptFinal_ex(&ctx, pchOutPut+nLen, &nOutl); nLen+=nOutl; pchOutPut[nLen]=0; EVP_CIPHER_CTX_cleanup(&ctx); return; }
下面开始说事,纯A代码的下面不用看了。
首先明确以下概念,AES是加密的算法,使用128、192 和 256 位密钥,将被加密数据划分为128位(16字节)一块,然后使用某种加密模式进行加密。
1、主要的加密模式有以下几种:
ECB模式
按照块密码的块大小被分为数个块,并对每个块进行独立加密。
优点:
1.简单;
2.有利于并行计算;
3.误差不会被传送;
缺点:
1.不能隐藏明文的模式;
2.可能对明文进行主动攻击;
CBC模式:
每个平文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有平文块。
同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。
优点:
1.不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
缺点:
1.不利于并行计算;
2.误差传递;
3.需要初始化向量IV
CFB模式:
模式类似于CBC,可以将块密码变为自同步的流密码。
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
缺点:
1.不利于并行计算;
2.误差传送:一个明文单元损坏影响多个单元;
3.唯一的IV;
OFB模式:
可以将块密码变成同步的流密码。它产生密钥流的块,然后将其与平文块进行异或,得到密文。
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
缺点:
1.不利于并行计算;
2.对明文的主动攻击是可能的;
3.误差传送:一个明文单元损坏影响多个单元;
PCBC模式
略
CTR模式
略
在我的代码中使用的是最简单的ecb模式。
2、由于被加密数据分组时,有可能不会正好为128bit的整数倍,所以需要padding(填充补齐),而padding模式有以下几种:
None //不填充。
PKCS7 //填充字符串由一个字节序列组成,每个字节填充该字节序列的长度。
Zeros //填充字符串由设置为零的字节组成。
ANSIX923 //ANSIX923 填充字符串由一个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节均填充数字零。
ISO10126 //ISO10126 填充字符串由一个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节填充随机数据。
根据官方文档地址(https://www.openssl.org/docs/crypto/EVP_CIPHER_CTX_set_padding.html)中描述:
If padding is enabled (the default) then EVP_EncryptFinal_ex() encrypts the "final" data, that is any data that remains in a partial block. It uses standard block padding (aka PKCS padding) as described in the NOTES section, below. The encrypted final data is written to out which should have sufficient space for one cipher block. The number of bytes written is placed in outl. After this function is called the encryption operation is finished and no further calls to EVP_EncryptUpdate() should be made.
可以知道在调用EVP_EncryptFinal_ex函数时,padding是默认启用的。
是否使用padding可以通过EVP_CIPHER_CTX_set_padding函数设置,第二个参数为0则禁用padding,为1则启用padding。
文档中说padding的模式为PKCS padding,具体指的是PKCS#7。
这里用例子表述一下PKCS#7:
假设块长度为128bit,也就是16个字节。
那么当需要padding的字符串长度为11时(假设字符源串16进制为 : DD DD DD DD DD DD DD DD | DD DD DD DD ),则padding结果为:
| DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
也就是缺4个字节,就是用04来padding,缺5个,就用05来Padding。
然后再将padding后的字符串进行加密。
我如何知道Padding模式是PKCS#7的?如果你不质疑我的分析,此部分可以跳过。
1.先使用一个key对长度为16的源串(随意)进行加密
EVP_EncryptFinal_ex函数调用后,得到结果如图1中红色部分,
红色部分就是padding内容的加密结果。
因为在PKCS#7模式下,padding的内容为 | 16 16 16 16 16 16 16 16 | 16 16 16 16 16 16 16 16 |
那么我构造一个字符串,如下图
经过加密后,如下图
可以看到此处16进制字符串与上面的红色字符串一致,所以能说明此处使用的Padding模式为PKCS#7。
为什么要研究这个padding?
1.当在使用openssl库与其他不同的库进行通信时,padding与加密模式不同(这都能搞错?)会导致让你蛋疼的问题。
2.理论是指导实践的标准,实践是检验理论的方法。
关于padding更详细的信息可以参考:
http://en.wikipedia.org/wiki/Padding_(cryptography)
最后hex与char互转的代码
char SupperToLower(char in) { if(in >= 65 && in <= 90) return in + 32; else return in; } char LowerToSupper(char in) { if(in >= 97 && in <= 122) return in - 32; else return in; } static const char *tohex = "0123456789ABCDEF"; void CharToHex(unsigned char *pchOut, const unsigned char *pchIn, int nLen) { int i = 0; while(i < nLen) { *pchOut++ = tohex[pchIn[i] >> 4]; *pchOut++ = tohex[pchIn[i] & 0x0F]; ++i; } return; } void HexToChar(unsigned char *pchOut, const unsigned char *pchIn, int nInLen) { int an=1,num=0,st,q,tmp,kk = 0; for(q = nInLen - 1,st = nInLen / 2 - 1; q >= 0,st >= 0; q --) { if(kk==2) { pchOut[st] = (unsigned char)num; st --; num = 0; an = 1; kk = 0; } if(LowerToSupper(pchIn[q]) >= 'A' && LowerToSupper(pchIn[q]) <= 'F') tmp = pchIn[q] - 'A' + 10; else tmp = pchIn[q] - '0'; num += an * tmp; an *= 16; kk ++; } return; }