有关AesGCM算法的一些总结

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;
}

你可能感兴趣的:(有关AesGCM算法的一些总结)