C 不同平台之间使用OpenSSL的不同加密结果

C 不同平台之间使用OpenSSL的不同加密结果,c,macos,encryption,openssl,aes,C,Macos,Encryption,Openssl,Aes,我正在用C编写一段跨平台(Windows和Mac OS X)代码,需要使用AES-256和CBC加密/解密blob,块大小为128位。在各种库和API中,我选择了OpenSSL 这段代码随后将使用一个多部分表单将blob上传到服务器,然后服务器使用.NET加密框架中的相同设置(Aes、CryptoStream等)对其进行解密 我面临的问题是,当在Windows上完成本地加密时,服务器解密工作正常,但当在Mac OS X上完成加密时,服务器解密失败-服务器抛出“填充无效且无法删除异常” 我从多个角

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

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

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

我从多个角度看了这一点:

  • 我验证了传输是正确的-在服务器的解密方法上接收的字节数组与从Mac OS X和Windows发送的字节数组完全相同
  • 对于同一密钥,加密blob的实际内容在Windows和Mac OS X之间是不同的。我使用硬编码密钥进行了测试,并在Windows和Mac OS X上为同一blob运行了此修补程序
  • 我确信填充是正确的,因为它是由OpenSSL处理的,并且同样的代码适用于Windows。尽管如此,我还是尝试实现了填充方案,就像微软的.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;
            }
    
        }
    
    服务器:

    /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
    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;
            }
    
        }
    
    静态字节[]解密(字节[]输入,字节[]密钥,字节[]iv)
    {
    尝试
    {
    //检查参数。
    
    如果(input==null | | input.LengthAES填充方案已在OpenSSL版本0.9.8*和1.0.1*之间更改(至少在0.9.8r和1.0.1j之间)如果你的两个模块使用了OpenSSL的这些不同版本,那么这可能是你的问题的原因。要验证这一点,首先检查OpenSSL版本。如果你碰到了所描述的情况,你可以考虑对齐填充方案是相同的。

    OpenSSL版本的差异是凌乱的。首先我建议你明确地强迫和理解。密钥长度、密钥、IVs和两边的加密模式。我在代码中看不到这些。然后我建议您在服务器端解密而不填充。这将始终成功,然后您可以检查最后一个块是否符合预期

    使用Windows Encryption和MacOS Encryption变体执行此操作,您将看到差异,很可能是在填充中


    <> P> C++代码中的外填充看起来很奇怪。加密16字节长明文会导致32字节密文,但是只提供一个16字节长的缓冲器。这将不起作用。您将写入边界。可能因为Windows更大的内存布局,Mac OS失败。d一年前的同一个问题,期待着对此的答案。我建议检查客户端的密钥和IV是否正确,以确保它们确实相同,在加密之前转储它们,包括
    EVP\u CIPHER\u IV\u length()
    EVP\u CIPHER\u key\u length()
    (主要是为了确保使用的数据不会超过key或IV数组)。我还建议使用普通值(例如key和IV设置为all 0或all~0)进行检查。您可能还希望尝试使用命令行
    openssl enc
    对其进行解密。此外,您还可以使用它加密并检查是否得到相同的结果。(顺便说一句,我怀疑Windows或OS X版本使用未初始化的数据作为IV或键的一部分,导致不同的结果。因此我建议检查键、IV及其长度。)您对
    outLen
    的计算失败,PKCS#7总是填充,因此您需要在大小上添加1到16个字节,而不是0到15个字节,也就是说,它应该始终是
    buflen+blocklen-余数
    您在问题中的评论:“我验证了IV对于Windows和Mac OS X是相同的(我想可能是IV中出现的一些特殊字符(如ETB)有问题,但没有问题)表示对编码工作方式的明显误解。如果您曾经将密文、密钥或IV视为文本,则需要执行编码/解码。如果不这样做,则可能会在提供的代码之外引入错误。