【发布时间】: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 视为文本,则需要执行编码/解码。如果你不这样做,那么你可能会在你提供的代码之外引入错误。
标签: c macos encryption openssl aes