13

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

根据CNG的文档,支持以下场景:

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

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

即使参数pbIV被标记为 in/out,参数指向的元素pbIV也不会被BCryptEncrypt(). 这是预期的吗?我还查看pbNonceBCRYPT_AUTHENTICATED_CIPHER_MODE_INFO结构中由pPaddingInfo指针指向的字段,但该字段也没有被修改。我还尝试“手动”推进 IV,根据计数器方案自己修改内容,但这也无济于事。

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

4

2 回答 2

13

我设法让它工作。似乎问题出在 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;
}
于 2015-06-20T19:32:17.367 回答
4

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

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

在这一点上,清除与BCRYPT_OBJECT_LENGTH.

于 2020-11-16T19:29:20.333 回答