【问题标题】:Windows CryptoAPI symmetric encoding/decoding block that already aligning已经对齐的 Windows CryptoAPI 对称编码/解码块
【发布时间】:2017-03-07 09:59:23
【问题描述】:

我需要从 OpenSSL 迁移到 MS CryptoAPI。

当我使用 OpenSSL 时,我得到了 N 字节(已经对齐 - 例如,假设数据长度 = 32 字节)的数据,并将其从一个缓冲区编码/解码到另一个缓冲区。一切正常。

现在我必须“使用 CryptoAPI!”。 所以我写了这样的东西:

    // Import AES key
    if(!CryptImportKey(m_hProvider, (CONST BYTE*)&aeskey, structsize, NULL, 0, &m_hAesKey ) ) {
        throw WinAESException("SetKey: Import key failed");
    }

    // Set Mode
    DWORD dwMode = CRYPT_MODE_ECB; //I can also use CRYPT_MODE_CBC with set of IV but let simple the code;
    if(!CryptSetKeyParam(m_hAesKey, KP_MODE, (BYTE*)&dwMode, 0)) {
        throw WinAESException("SetKey: Set ECB mode failed");
    }

一切正常, 现在我尝试对数据进行编码

    DWORD d = (DWORD)psize;
    result = CryptEncrypt( m_hAesKey, NULL, TRUE, 0, buffer, &d, (DWORD)bsize );

我看到了什么?数据的编码部分大于源。 我知道 CryptEncrypt 函数以某种方式填充了已经填充的数据?

无论如何,是否有可能获得与普通数据相同大小的编码结果?

我不使用未对齐的块,我希望在加密 32 字节时得到准确的 32 字节,而不是 48...

当我测试预期尺寸时

    DWORD d = 16;
    result = CryptEncrypt( m_hAesKey, NULL, TRUE, 0, 0, &d, (DWORD)bsize );  //need 32

    d = 32;
    result = CryptEncrypt( m_hAesKey, NULL, TRUE, 0, 0, &d, (DWORD)bsize );  //need 48 

    d = 48;
    result = CryptEncrypt( m_hAesKey, NULL, TRUE, 0, 0, &d, (DWORD)bsize );   //need 64

    d = 64;
    result = CryptEncrypt( m_hAesKey, NULL, TRUE, 0, 0, &d, (DWORD)bsize );  //need 80

WTHF 我做错了吗? 附言如果我将加密的结果截断为所需的大小,然后尝试对其进行解密,则使用返回的预期错误代码完成解密,但数据已正确解密。但这个 hack 不适合我...

【问题讨论】:

  • 数据被填充,因为 AES 只加密整个块。 AES 假设在解密时最后一个加密的数据块有填充。这意味着如果要加密的最后一个数据块的大小与 AES 块大小相同,则必须添加填充块。如果不添加此填充,AES 无法确定最后一个块是否包含填充。
  • 当我使用 OpenSSL 或 LibTomCrypt 时,我可以关闭 AES ECB/CBC 的填充。在 MS cryptoAPI 中似乎没有这样的选项......我没有看到“NO_PADDING”标志:msdn.microsoft.com/en-us/library/windows/desktop/…(参见 KP_PADDING 值)
  • 这不是你的问题,但ECB是一种可怕模式。 即使如果您的消息总是少于一个块,它会泄露您发送相同消息两次的事实。我怀疑 CAPI 只是不支持你想要的。然而,一切都没有丢失:使用 BCryptEncrypt(这是一个 CNG API)。它确实支持不填充整个块消息。
  • 不要使用 ECB 模式,它不安全,请参阅ECB mode,向下滚动到 Penguin。

标签: c++ aes padding cryptoapi ecb


【解决方案1】:

首先,它不是编码和解码——它是加密和解密。

Microsoft 可能会默认添加 PKCS5_PADDING - 没有像 NO_PADDING 这样的选项。 这很奇怪,尤其是对于 ECB 模式,但它解释了为什么 CryptEncrypt 返回扩展缓冲区大小 - 它增加了一个块用于填充。

另一方面,您在上述答案中使用 bFinal == FALSE 的“hack”实际上很好,因为 ECB 模式不需要任何最终确定过程。

如果您打算使用其他加密模式,那么您应该始终使用 bFinal == TRUE 进行加密。

此外,如果您想正确操作,您应该分两步进行加密:

DWORD dwEncryptedDataLen = dwRealDataLen; 
result = CryptEncrypt(m_hAesKey, NULL, TRUE, 0, NULL, &dwEncryptedDataLen, 0);
...
pbData = (BYTE*)malloc(dwEncryptedDataLen);
memcpy(pbData, pbRealData, dwRealDataLen);
...
result = CryptEncrypt(m_hAesKey, NULL, TRUE, 0, pbData, &dwRealDataLen, dwEncryptedDataLen);
...

不要忘记检查 Crypt 函数的结果值。

【讨论】:

    【解决方案2】:

    看来我找到了临时解决方案。 当我需要加密/解密整个块时 - 我需要的只是函数 - “它不是最后一个/唯一的块”: 所以不要输入:

    result = CryptEncrypt( m_hAesKey, NULL, TRUE, 0, buffer, &d, (DWORD)bsize );
    

    但是:

    result = CryptEncrypt( m_hAesKey, NULL, FALSE, 0, buffer, &d, (DWORD)bsize );
    

    现在可以了。并希望他们不要在最后一块的末尾更改填充数据的概念,以便在块的开头添加对齐...

    【讨论】:

    • 这样的修复只是一场等待发生的灾难。您需要充分了解问题和解决方案。您需要研究填充以及为什么需要填充,请参阅Padding (cryptography),尤其是 PKCS#7 填充。如果输入数据不总是块大小的倍数,则必须向输入数据添加填充;指定填充,PKCS#75 是 AES 的首选填充。
    • 正如我在最上面写的 - 我的数据总是已经对齐并且不能是其他的。
    • 如果使用填充,它必须应用于每条消息,即使输入数据已经是块大小的精确倍数,在这种情况下会添加一个完整的填充块。考虑一下,仅查看解密数据,如果最后一个字节是 0x01,您将如何判断是否存在填充,因此必须始终添加它。唯一的例外是,如果数据总是是块大小的精确倍数,则不需要填充并且不指定填充。
    • :) 这不是问题。我不想也不想使用填充。如果我有一个正好 32 字节的插槽,并且所有 32 字节必须被编码(转移到某个地方)然后解码 - 为什么我需要扩展插槽来存储额外的垃圾?如果存在硬件尺寸限制如何处理?
    • 正如我所说:如果数据总是是块大小的精确倍数,则不需要填充,也不需要指定填充。然后不要指定填充。请注意,填充可能是 CryptoAPI 的默认设置,您可能必须指定无填充:PaddingMode.None。你需要在每个调用中使用相同的填充模式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 2012-09-20
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    相关资源
    最近更新 更多