【问题标题】:How to chain BCryptEncrypt and BCryptDecrypt calls using AES in GCM mode?如何在 GCM 模式下使用 AES 链接 BCryptEncrypt 和 BCryptDecrypt 调用?
【发布时间】:2015-08-23 14:01:59
【问题描述】:

使用 Windows CNG API,我能够在 GCM 模式下使用 AES,通过身份验证加密和解密单个数据块。我现在想连续加密和解密多个缓冲区。

根据documentation for CNG,支持以下场景:

如果加密或解密的输入分散在多个 缓冲区,那么您必须将调用链接到 BCryptEncrypt 和 BCryptDecrypt 函数。链接是通过设置 dwFlags 成员中的 BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG 标志。

如果我理解正确,这意味着我可以在多个缓冲区上顺序调用BCryptEncrypt,并在最后获取组合缓冲区的身份验证标签。同样,我可以在多个缓冲区上按顺序调用BCryptDecrypt,同时将实际的身份验证检查推迟到最后。我无法让它工作,看起来dwFlags 的值被忽略了。每当我使用BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG 时,我都会得到0xc000a002 的返回值,它等于STATUS_AUTH_TAG_MISMATCH 中定义的ntstatus.h

即使参数pbIV 被标记为输入/输出,参数pbIV 指向的元素不会被BCryptEncrypt() 修改。这是预期的吗?我还查看了BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO 结构中的pbNonce 字段,由pPaddingInfo 指针指向,但该字段也没有被修改。我也尝试过“手动”推进IV,根据计数器方案自己修改内容,但这也没有帮助。

成功链接BCryptEncrypt 和/或BCryptDecrypt 函数的正确过程是什么?

【问题讨论】:

    标签: c windows aes-gcm cng


    【解决方案1】:

    我设法让它工作。似乎问题出在 MSDN 中,应该提到设置 BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG 而不是 BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG

    #include <windows.h>
    #include <assert.h>
    #include <vector>
    #include <Bcrypt.h>
    #pragma comment(lib, "bcrypt.lib")
    
    std::vector<BYTE> MakePatternBytes(size_t a_Length)
    {
        std::vector<BYTE> result(a_Length);
        for (size_t i = 0; i < result.size(); i++)
        {
            result[i] = (BYTE)i;
        }
    
        return result;
    }
    
    std::vector<BYTE> MakeRandomBytes(size_t a_Length)
    {
        std::vector<BYTE> result(a_Length);
        for (size_t i = 0; i < result.size(); i++)
        {
            result[i] = (BYTE)rand();
        }
    
        return result;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        NTSTATUS bcryptResult = 0;
        DWORD bytesDone = 0;
    
        BCRYPT_ALG_HANDLE algHandle = 0;
        bcryptResult = BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_AES_ALGORITHM, 0, 0);
        assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptOpenAlgorithmProvider");
    
        bcryptResult = BCryptSetProperty(algHandle, BCRYPT_CHAINING_MODE, (BYTE*)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
        assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptSetProperty(BCRYPT_CHAINING_MODE)");
    
        BCRYPT_AUTH_TAG_LENGTHS_STRUCT authTagLengths;
        bcryptResult = BCryptGetProperty(algHandle, BCRYPT_AUTH_TAG_LENGTH, (BYTE*)&authTagLengths, sizeof(authTagLengths), &bytesDone, 0);
        assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGetProperty(BCRYPT_AUTH_TAG_LENGTH)");
    
        DWORD blockLength = 0;
        bcryptResult = BCryptGetProperty(algHandle, BCRYPT_BLOCK_LENGTH, (BYTE*)&blockLength, sizeof(blockLength), &bytesDone, 0);
        assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGetProperty(BCRYPT_BLOCK_LENGTH)");
    
        BCRYPT_KEY_HANDLE keyHandle = 0;
        {
            const std::vector<BYTE> key = MakeRandomBytes(blockLength);
            bcryptResult = BCryptGenerateSymmetricKey(algHandle, &keyHandle, 0, 0, (PUCHAR)&key[0], key.size(), 0);
            assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGenerateSymmetricKey");
        }
    
        const size_t GCM_NONCE_SIZE = 12;
        const std::vector<BYTE> origNonce = MakeRandomBytes(GCM_NONCE_SIZE);
        const std::vector<BYTE> origData  = MakePatternBytes(256);
    
        // Encrypt data as a whole
        std::vector<BYTE> encrypted = origData;
        std::vector<BYTE> authTag(authTagLengths.dwMinLength);
        {
            BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
            BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
            authInfo.pbNonce = (PUCHAR)&origNonce[0];
            authInfo.cbNonce = origNonce.size();
            authInfo.pbTag   = &authTag[0];
            authInfo.cbTag   = authTag.size();
    
            bcryptResult = BCryptEncrypt
                (
                keyHandle,
                &encrypted[0], encrypted.size(),
                &authInfo,
                0, 0,
                &encrypted[0], encrypted.size(),
                &bytesDone, 0
                );
    
            assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptEncrypt");
            assert(bytesDone == encrypted.size());
        }
    
        // Decrypt data in two parts
        std::vector<BYTE> decrypted = encrypted;
        {
            DWORD partSize = decrypted.size() / 2;
    
            std::vector<BYTE> macContext(authTagLengths.dwMaxLength);
    
            BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
            BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
            authInfo.pbNonce = (PUCHAR)&origNonce[0];
            authInfo.cbNonce = origNonce.size();
            authInfo.pbTag   = &authTag[0];
            authInfo.cbTag   = authTag.size();
            authInfo.pbMacContext = &macContext[0];
            authInfo.cbMacContext = macContext.size();
    
            // IV value is ignored on first call to BCryptDecrypt.
            // This buffer will be used to keep internal IV used for chaining.
            std::vector<BYTE> contextIV(blockLength);
    
            // First part
            authInfo.dwFlags = BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;
            bcryptResult = BCryptDecrypt
                (
                keyHandle,
                &decrypted[0*partSize], partSize,
                &authInfo,
                &contextIV[0], contextIV.size(),
                &decrypted[0*partSize], partSize,
                &bytesDone, 0
                );
    
            assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptDecrypt");
            assert(bytesDone == partSize);
    
            // Second part
            authInfo.dwFlags &= ~BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG;
            bcryptResult = BCryptDecrypt
                (
                keyHandle,
                &decrypted[1*partSize], partSize,
                &authInfo,
                &contextIV[0], contextIV.size(),
                &decrypted[1*partSize], partSize,
                &bytesDone, 0
                );
    
            assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptDecrypt");
            assert(bytesDone == partSize);
        }
    
        // Check decryption
        assert(decrypted == origData);
    
        // Cleanup
        BCryptDestroyKey(keyHandle);
        BCryptCloseAlgorithmProvider(algHandle, 0);
    
        return 0;
    }
    

    【讨论】:

    • 感谢您的建议,这是有道理的。但它似乎不起作用,我在我的问题中添加了一些关于此的附加文本。
    • 我已经完全改变了我的答案。
    • 太棒了,非常感谢。一句话:trashIV 实际上拥有高级 IV,因此在链接模式下正常运行是必需的。我的错误是我使 IV 的大小与 nonce 相同(除了使用“错误”dwFlags 值)。
    • 好的,我没有意识到 IV 值在第一次调用时被忽略,但随后被填充并用于链接。编辑答案以向未来的读者反映这一点。
    • 很好的例子,最好的例子之一可以在 google 中找到。唯一的建议是不要将密钥长度设置为与块大小相同。我完全不明白为什么你需要检索块大小,对于 AES,它总是 16。
    【解决方案2】:

    @Codeguard 的回答让我通过了我正在研究的project,这让我首先找到了这个问题/答案;但是,我仍然遇到了一些问题。下面是我所遵循的过程,其中提到了棘手的部分。您可以在上面的链接中查看实际代码:

    1. 使用BCryptOpenAlgorithmProvider打开算法提供程序,使用BCRYPT_AES_ALGORITHM
    2. 使用BCryptSetPropertyBCRYPT_CHAINING_MODE 设置为BCRYPT_CHAIN_MODE_GCM
    3. 使用BCryptGetProperty 获取BCRYPT_OBJECT_LENGTH 分配给BCrypt 库用于加密/解密操作。根据您的实施,您可能还希望:
      • 使用BCryptGetProperty 确定BCRYPT_BLOCK_SIZE 并为IV 分配暂存空间。 Windows API 会在每次调用时更新 IV,调用者负责为该使用提供内存。
      • 使用BCryptGetProperty 确定BCRYPT_AUTH_TAG_LENGTH 并为可能的最大标签分配暂存空间。与 IV 一样,调用者负责提供这个空间,API 每次都会更新。
    4. 初始化BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO 结构:
      • BCRYPT_INIT_AUTH_MODE_INFO()初始化结构
      • 初始化pbNoncecbNonce 字段。请注意,对于第一次调用BCryptEncrypt/BCryptDecrypt,IV 作为输入被忽略,该字段用作“IV”。但是,IV 参数将由第一次调用更新并由后续调用使用,因此仍必须为其提供空间。此外,pbNoncecbNonce 字段必须保持设置状态(即使它们在第一次调用后未使用),以用于对 BCryptEncrypt/BCryptDecrypt 的所有调用,否则这些调用会报错。
      • 初始化pbAuthDatacbAuthData。在我的项目中,我在第一次调用BCryptEncrypt/BCryptDecrypt 之前设置了这些字段,然后立即将它们重置为NULL/0。您可以在这些调用期间将NULL/0 作为输入和输出参数传递。
      • 初始化pbTagcbTagpbTag 可以是 NULL 直到最后调用 BCryptEncrypt/BCryptDecrypt 时检索或检查标签,但必须设置 cbTag 否则 BCryptEncrypt/BCryptDecrypt 将抱怨。
      • 初始化pbMacContextcbMacContext。这些指向BCryptEncrypt/BCryptDecrypt 的暂存空间,用于跟踪标签/mac 的当前状态。
      • cbAADcbData 初始化为0。 API 使用这些字段,因此您可以随时阅读它们,但在最初将它们设置为 0 后不应更新它们。
      • dwFlags 初始化为BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG。初始化后,应使用|=&amp;= 对该字段进行更改。 Windows 还在此字段中设置了调用者需要注意不要更改的标志。
    5. 使用BCryptGenerateSymmetricKey 导入用于加密/解密的密钥。请注意,您需要将与BCRYPT_OBJECT_LENGTH 关联的内存提供给此调用,以供BCryptEncrypt/BCryptDecrypt 在操作期间使用。
    6. 使用您的 AAD 致电 BCryptEncrypt/BCryptDecrypt(如果有);无需为此调用提供输入或输出空间。 (如果调用成功,您可以看到您的 AAD 的大小反映在 BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO 结构的 cbAAD 字段中。)
      1. 设置 pbAuthDatacbAuthData 以反映 AAD。
      2. 致电BCryptEncryptBCryptDecrypt
      3. pbAuthDatacbAuthData 设置回NULL0
    7. 拨打BCryptEncrypt/BCryptDecrypt“N - 1”次
      • 传递给每个调用的数据量必须是算法块大小的倍数。
      • 不要不要将调用的dwFlags 参数设置为0 以外的任何参数。
      • 输出空间必须等于或大于输入的大小
    8. 最后一次致电BCryptEncrypt/BCryptDecrypt(有或没有明文/密文输入/输出)。输入的大小不必是该调用的算法块大小的倍数。 dwFlags 仍设置为 0
      1. 根据操作是加密还是解密,将BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO 结构的pbTag 字段设置为存储生成的标签的位置或要验证的标签的位置。李>
      2. 使用&amp;= 语法从BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO 结构的dwFlags 字段中删除BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG
    9. 致电BCryptDestroyKey
    10. 致电BCryptCloseAlgorithmProvider

    此时,清除与BCRYPT_OBJECT_LENGTH 关联的空间是明智的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-31
      • 1970-01-01
      • 1970-01-01
      • 2011-12-18
      • 1970-01-01
      • 1970-01-01
      • 2016-03-03
      • 1970-01-01
      相关资源
      最近更新 更多