不同平台的加密结果不同,使用 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的主要内容,如果未能解决你的问题,请参考以下文章
为啥 .NET 中的 AEC 加密产生与 JavaScript 不同的结果?