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



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

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

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

我已经从很多角度看了这个问题:

  1. 我验证了传输是正确的-在服务器的解密方法上接收到的字节数组与从Mac OS X和Windows发送的字节数组完全相同
  2. 对于相同的密钥,加密blob的实际内容在Windows和Mac OS X之间是不同的。我使用硬编码密钥进行了测试,并在Windows和Mac OS X上运行此补丁,以获得相同的blob
  3. 我确信填充是正确的,因为它是由OpenSSL照顾的,因为相同的代码适用于Windows。即便如此,我还是试着实现了。net 的填充方案,就像它在微软的参考源代码中一样,但仍然没有去
  4. 我验证了IV是相同的Windows和Mac OS X(我认为可能有一些特殊字符,如ETB出现在IV的问题,但没有)
  5. 我试过LibreSSL和mbedtls,没有积极的结果。在mbedtls中,我还必须实现填充,因为据我所知,填充是API用户的责任。
  6. 我已经为这个问题困扰了将近两个星期了,我开始把我的头发拔出来
作为参考,我将发布用于加密的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

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

在Windows-Encryption和MacOS-Encryption变体中执行此操作,您将看到差异,最可能的是填充。

c++代码中的外填充看起来很奇怪。加密16字节长的明文会产生32字节的密文,但您只提供16字节长的缓冲区。这行不通。你会写越界的。也许它只是碰巧在Windows上工作,因为它有更大的内存布局,在MacOS上失败了。

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

最新更新