AesGCM是微信底层通信协议中使用的一种最为重要的加解密算法,在尝试使用OpenSSL库实现这套算法的过程中,遇到了以下几个值得注意的点:
无补位
在ECB/CCB模式下,补位信息是算法实现必须考虑的一个维度。但在GCM模式下,补位信息是完全不需要考虑的,明文与密文有着相同的长度。
不同维度
在普通的Aes加解密算法中,需要从key/iv/padding/mode这四个维度来考虑算法的实现。而AesGcm算法中却需要从下面这几个维度来实现算法:
- Key,加解密Key
- IV,初始化向量
- aad,具体含义与作用不甚了了,但此维度无法忽略
- tag,可选维度,微信实现中,该数据为16个字节,被拼接到了密文的后面
java加密OpenSSL解密
微信底层通信协议中,服务端下发的密文数据总是比明文数据长了16个字节,导致这种现象的主要原因是微信实现时将加密使用的tag信息拼接到了密文数据中,客户端使用JAVA接口解密时,在接口的内部能够自动的截取tag信息后再解密出明文数据。我并不能确认这种方式是BouncyCastle的固有实现还是微信自己的独有实现。关于拼接这块的逻辑,可以参见这里。
当使用OpenSSL实现解密微信下发的密文数据时,如果不知道拼接tag这层逻辑时,是不可能成功解密出明文数据的。
最终代码
int encryptAesGcm(unsigned char* plaintext, int plaintextLen, unsigned char* add, int addLen, unsigned char* key, unsigned char* iv, int ivLen, unsigned char* ciphertext, unsigned char* tag)
{
int len = 0;
int ciphertextLen = 0;
int ret = 0;
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
do{
ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL);
if (ret != 1) {
break;
}
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivLen, NULL);
ret = EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv);
if (ret != 1) {
break;
}
ret = EVP_EncryptUpdate(ctx, NULL, &len, add, addLen);
if (ret != 1) {
break;
}
ret = EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintextLen);
if (ret != 1) {
break;
}
ciphertextLen = len;
ret = EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);
if (ret != 1) {
ciphertextLen = 0;
break;
}else{
ciphertextLen += len;
}
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
}while (0);
EVP_CIPHER_CTX_free(ctx);
return ciphertextLen;
}
int decryptAesGcm(unsigned char* ciphertext, int ciphertextLen, unsigned char* add, int addLen, unsigned char* key, unsigned char* iv, int ivLen, unsigned char* tag, int tagLen, unsigned char* plaintext)
{
int length = 0;
int tempLen = 0;
int ret = 0;
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
do{
ret = EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL);
if (ret != 1) {
break;
}
ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivLen, NULL);
if (ret != 1) {
break;
}
ret = EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv);
if (ret != 1) {
break;
}
ret = EVP_DecryptUpdate(ctx, NULL, &tempLen, add, addLen);
if (ret != 1) {
break;
}
ret = EVP_DecryptUpdate(ctx, plaintext, &tempLen, ciphertext, ciphertextLen);
if (ret != 1) {
break;
}
length = tempLen;
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag);
ret = EVP_DecryptFinal_ex(ctx, plaintext + tempLen, &tempLen);
if (ret != 1) {
length = 0;
break;
}else{
length += tempLen;
}
}while (0);
EVP_CIPHER_CTX_free(ctx);
return length;
}