不同平台的加密结果不同,使用 OpenSSL

Posted

技术标签:

【中文标题】不同平台的加密结果不同,使用 OpenSSL【英文标题】:Different encryption results between platforms, using OpenSSL 【发布时间】:2016-01-06 04:00:06 【问题描述】:

我正在使用 C 语言编写一段跨平台(Windows 和 Mac OS X)代码,该代码需要使用 AES-256 和 CBC 和 128 位块大小来加密/解密 blob。在各种库和 API 中,我选择了 OpenSSL。

然后,这段代码将使用多部分形式的 PUT 将 blob 上传到服务器,然后使用 .NET 加密框架(Aes、CryptoStream 等)中的相同设置对其进行解密。

我面临的问题是,在 Windows 上完成本地加密时服务器解密工作正常,但在 Mac OS X 上完成加密时它失败 - 服务器抛出“填充无效并且无法删除异常”。

我从多个角度看待这个问题:

    我验证了传输是正确的 - 在服务器的解密方法上接收到的字节数组与从 Mac OS X 和 Windows 发送的完全相同 对于同一个密钥,加密 blob 的实际内容在 Windows 和 Mac OS X 之间是不同的。我使用硬编码密钥对此进行了测试,并在 Windows 和 Mac OS X 上为同一个 blob 运行此补丁 我确定填充是正确的,因为它由 OpenSSL 处理,并且相同的代码适用于 Windows。即便如此,我尝试实现填充方案 因为它在 Microsoft 的 .NET 参考源中,但仍然不行 我验证了 Windows 和 Mac OS X 的 IV 相同(我认为可能出现在 IV 中的某些特殊字符(例如 ETB)存在问题,但没有) 我已经尝试过 LibreSSL 和 mbedtls,但没有任何积极的结果。在 mbedtls 中,我还必须实现填充,因为据我所知,填充是 API 用户的责任 我已经解决这个问题将近两个星期了,我开始拔掉我(曾经稀缺的)头发

作为参考,我将发布用于加密的 C 客户端代码和用于解密的服务器 C# 代码。服务器端的一些小细节将被省略(它们不会干扰加密代码)。

客户:

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
__setup_aes(EVP_CIPHER_CTX *ctx, const char *key, qvr_bool encrypt)

    static const char *iv = ""; /* for security reasons, the actual IV is omitted... */

    if (encrypt)
        EVP_EncryptInit(ctx, EVP_aes_256_cbc(), key, iv);
    else
        EVP_DecryptInit(ctx, EVP_aes_256_cbc(), key, iv);


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
__encrypt(void *buf,
    size_t buflen,
    const char *key,
    unsigned char **outbuf,
    size_t *outlen)

    EVP_CIPHER_CTX ctx;
    int blocklen = 0;
    int finallen = 0;
    int remainder = 0;

    __setup_aes(&ctx, key, QVR_TRUE);

    EVP_CIPHER *c = ctx.cipher;
    blocklen = EVP_CIPHER_CTX_block_size(&ctx);

    //*outbuf = (unsigned char *) malloc((buflen + blocklen - 1) / blocklen * blocklen);
    remainder = buflen % blocklen;
    *outlen = remainder == 0 ? buflen : buflen + blocklen - remainder;
    *outbuf = (unsigned char *) calloc(*outlen, sizeof(unsigned char));

    EVP_EncryptUpdate(&ctx, *outbuf, outlen, buf, buflen);
    EVP_EncryptFinal_ex(&ctx, *outbuf + *outlen, &finallen);

    EVP_CIPHER_CTX_cleanup(&ctx);
    //*outlen += finallen;

服务器:

static Byte[] Decrypt(byte[] input, byte[] key, byte[] iv)
    
        try
        
            // Check arguments.
            if (input == null || input.Length <= 0)
                throw new ArgumentNullException("input");
            if (key == null || key.Length <= 0)
                throw new ArgumentNullException("key");
            if (iv == null || iv.Length <= 0)
                throw new ArgumentNullException("iv");

            byte[] unprotected;


            using (var encryptor = Aes.Create())
            
                encryptor.Key = key;
                encryptor.IV = iv;
                using (var msInput = new MemoryStream(input))
                
                    msInput.Position = 0;
                    using (
                        var cs = new CryptoStream(msInput, encryptor.CreateDecryptor(),
                            CryptoStreamMode.Read))
                    using (var data = new BinaryReader(cs))
                    using (var outStream = new MemoryStream())
                    
                        byte[] buf = new byte[2048];
                        int bytes = 0;
                        while ((bytes = data.Read(buf, 0, buf.Length)) != 0)
                            outStream.Write(buf, 0, bytes);

                        return outStream.ToArray();
                    
                
            
        
        catch (Exception ex)
        
            throw ex;
        

    

有没有人知道为什么会发生这种情况?作为参考,这是来自微软参考源 .sln 的 .NET 方法(我认为)进行解密:https://gist.github.com/Metaluim/fcf9a4f1012fdeb2a44f#file-rijndaelmanagedtransform-cs

【问题讨论】:

好问题。一年前我也遇到过同样的问题,期待得到答案。 我建议检查客户端的密钥和 IV 以确保它们确实相同,在加密之前转储它们,包括 EVP_CIPHER_iv_length()EVP_CIPHER_key_length() (主要是为了确保您是'不使用通过键或 IV 数组的数据)。我还建议使用微不足道的值进行检查(例如,键和 IV 设置为全 0,或全为 ~0) 您可能还想尝试使用命令行openssl enc 对其进行解密。此外,您也可以使用它来加密并检查您是否得到相同的结果。 (顺便说一句,我怀疑 Windows 或 OS X 构建使用未初始化的数据作为 IV 或密钥的一部分,导致不同的结果。因此我建议检查密钥、IV 及其长度。) 你计算outLen失败,PKCS#7总是填充,所以你需要在大小上增加1到16个字节,而不是0到15,即它应该总是buflen + blocklen - remainder跨度> 您在问题中的评论:“我已验证 Windows 和 Mac OS X 的 IV 相同(我认为可能出现在IV,但没有)”表示对 encoding 的工作原理存在明显的误解。如果您曾经将密文、密钥或 IV 视为文本,则需要执行编码/解码。如果你不这样做,那么你可能会在你提供的代码之外引入错误。 【参考方案1】:

OpenSSL 版本差异很混乱。首先,我建议您明确强制并验证双方的密钥长度、密钥、IV 和加密模式。我在代码中没有看到。然后我建议你在服务器端解密而不用填充。这将总是成功,然后你可以检查最后一个块是否是你所期望的。

使用 Windows-Encryption 和 MacOS-Encryption 变体执行此操作,您会发现差异,很可能在填充中。

C++ 代码中的 outlen 填充看起来很奇怪。加密 16 字节长的明文会产生 32 字节的密文,但您只提供 16 字节长的缓冲区。这行不通。你会写越界。也许它在 Windows 上是偶然的,因为内存布局更宽敞,而在 MacOS 上却失败了。

【讨论】:

【参考方案2】:

AES 填充方案已在 OpenSSL 版本 0.9.8* 和 1.0.1* 之间更改(至少在 0.9.8r 和 1.0.1j 之间)。如果您的两个模块使用这些不同版本的 OpenSSL,那么这可能是您的问题的原因。要验证这一点,请首先检查 OpenSSL 版本。如果您遇到所描述的情况,您可以考虑将填充方案对齐为相同。

【讨论】:

以上是关于不同平台的加密结果不同,使用 OpenSSL的主要内容,如果未能解决你的问题,请参考以下文章

AES加密算法

为啥 RSA 加密文本给我相同文本的不同结果

为啥 .NET 中的 AEC 加密产生与 JavaScript 不同的结果?

加密算法在linux下相同输入每次加密结果都不同的问题

AES 加密在 iOS 和 Android 中产生不同的结果

API接口签名验证_MD5加密出现不同结果的解决方法