如何在 GCM 模式下使用 AES 链接 BCryptEncrypt 和 BCryptDecrypt 调用?
How to chain BCryptEncrypt and BCryptDecrypt calls using AES in GCM mode?
使用 Windows CNG API,我能够在 GCM 模式下使用 AES 加密和解密带有身份验证的单个数据块。我现在想连续加密和解密多个缓冲区。
根据documentation for CNG,支持以下场景:
If the input to encryption or decryption is scattered across multiple
buffers, then you must chain calls to the BCryptEncrypt and
BCryptDecrypt functions. Chaining is indicated by setting the
BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG flag in the dwFlags member.
如果我理解正确,这意味着我可以在多个缓冲区上顺序调用 BCryptEncrypt
,最后获取组合缓冲区的身份验证标记。同样,我可以在多个缓冲区上顺序调用 BCryptDecrypt
,同时将实际的身份验证检查推迟到最后。但是我无法让它工作,看起来 dwFlags
的值被忽略了。每当我使用 BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG
时,我都会得到 0xc000a002
的 return 值,它等于 ntstatus.h
.
中定义的 STATUS_AUTH_TAG_MISMATCH
即使参数 pbIV
被标记为 in/out,参数 pbIV
指向的元素不会被 BCryptEncrypt()
修改。这是预期的吗?我还查看了 BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构中的字段 pbNonce
,由 pPaddingInfo
指针指向,但该字段也没有被修改。我也试过"manually"推进IV,根据计数器方案自己修改内容,但这也没有帮助。
成功链接 BCryptEncrypt
and/or BCryptDecrypt
函数的正确程序是什么?
我设法让它工作了。看来问题出在 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;
}
@Codeguard 的回答让我完成了我正在研究的 project,这让我首先找到了这个 question/answer;然而,我仍然遇到了一些问题。下面是我遵循的过程,其中提到了棘手的部分。您可以在上面的link处查看实际代码:
- 使用
BCryptOpenAlgorithmProvider
打开算法提供程序使用BCRYPT_AES_ALGORITHM
。
- 使用
BCryptSetProperty
将BCRYPT_CHAINING_MODE
设置为BCRYPT_CHAIN_MODE_GCM
。
- 使用
BCryptGetProperty
获取 BCRYPT_OBJECT_LENGTH
分配给 BCrypt 库用于 encrypt/decrypt 操作。根据您的实施,您可能还想:
- 使用
BCryptGetProperty
确定BCRYPT_BLOCK_SIZE
并为IV分配scratch space。 Windows API 每次调用都会更新 IV,调用者负责为该用法提供内存。
- 使用
BCryptGetProperty
确定BCRYPT_AUTH_TAG_LENGTH
并为最大可能的标签分配临时space。与 IV 一样,调用者负责提供此 space,API 每次都会更新。
- 初始化
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构:
- 用
BCRYPT_INIT_AUTH_MODE_INFO()
初始化结构
- 初始化
pbNonce
和 cbNonce
字段。请注意,对于 BCryptEncrypt
/BCryptDecrypt
的第一次调用,IV 作为输入被忽略,此字段用作“IV”。但是,IV 参数将由第一次调用更新并由后续调用使用,因此仍必须为其提供 space。此外,对于 BCryptEncrypt
/BCryptDecrypt
的所有调用,pbNonce
和 cbNonce
字段必须保持设置(即使它们在第一次调用后未使用),否则这些调用将报错.
- 初始化
pbAuthData
和 cbAuthData
。在我的项目中,我在第一次调用 BCryptEncrypt
/BCryptDecrypt
之前设置这些字段,然后立即将它们重置为 NULL
/0
。您可以在这些调用期间将 NULL
/0
作为输入和输出参数传递。
- 初始化
pbTag
和 cbTag
。 pbTag
可以是 NULL
直到最终调用 BCryptEncrypt
/BCryptDecrypt
时检索或检查标签,但必须设置 cbTag
否则 BCryptEncrypt
/BCryptDecrypt
会抱怨。
- 初始化
pbMacContext
和 cbMacContext
。这些指向 BCryptEncrypt
/BCryptDecrypt
的划痕 space,用于跟踪 tag/mac. 的当前状态
- 将
cbAAD
和cbData
初始化为0
。 API 使用这些字段,因此您可以随时阅读它们,但在最初将它们设置为 0
后不应更新它们。
- 将
dwFlags
初始化为 BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG
。初始化后,应使用 |=
或 &=
更改此字段。 Windows 还在该字段中设置调用者需要注意不要更改的标志。
- 使用
BCryptGenerateSymmetricKey
导入用于 encryption/decryption 的密钥。请注意,您需要向此调用提供与 BCRYPT_OBJECT_LENGTH
关联的内存,以供 BCryptEncrypt
/BCryptDecrypt
在操作期间使用。
- 使用您的 AAD(如果有)调用
BCryptEncrypt
/BCryptDecrypt
;不需要为此调用提供输入或 space 输出。 (如果调用成功,您可以在 BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构的 cbAAD
字段中看到 AAD 的大小。)
- 设置
pbAuthData
和 cbAuthData
以反映 AAD。
- 致电
BCryptEncrypt
或BCryptDecrypt
。
- 将
pbAuthData
和 cbAuthData
设置回 NULL
和 0
。
- 调用
BCryptEncrypt
/BCryptDecrypt
"N - 1" 次
- 传递给每次调用的数据量必须是算法块大小的倍数。
- 不要 将调用的
dwFlags
参数设置为 0
以外的任何参数。
- 输出space必须等于或大于输入的大小
- 最后一次呼叫
BCryptEncrypt
/BCryptDecrypt
(有或没有 plain/cipher 文本 input/output)。输入的大小不必是此调用的算法块大小的倍数。 dwFlags
仍设置为 0
。
- 将
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构的 pbTag
字段设置为存储生成的标记的位置或要验证的标记的位置,具体取决于操作是否是加密或解密。
- 使用
&=
语法从 BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构的 dwFlags
字段中删除 BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG
。
- 致电
BCryptDestroyKey
- 致电
BCryptCloseAlgorithmProvider
在这一点上,清除与 BCRYPT_OBJECT_LENGTH
关联的 space 是明智的。
使用 Windows CNG API,我能够在 GCM 模式下使用 AES 加密和解密带有身份验证的单个数据块。我现在想连续加密和解密多个缓冲区。
根据documentation for CNG,支持以下场景:
If the input to encryption or decryption is scattered across multiple buffers, then you must chain calls to the BCryptEncrypt and BCryptDecrypt functions. Chaining is indicated by setting the BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG flag in the dwFlags member.
如果我理解正确,这意味着我可以在多个缓冲区上顺序调用 BCryptEncrypt
,最后获取组合缓冲区的身份验证标记。同样,我可以在多个缓冲区上顺序调用 BCryptDecrypt
,同时将实际的身份验证检查推迟到最后。但是我无法让它工作,看起来 dwFlags
的值被忽略了。每当我使用 BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG
时,我都会得到 0xc000a002
的 return 值,它等于 ntstatus.h
.
STATUS_AUTH_TAG_MISMATCH
即使参数 pbIV
被标记为 in/out,参数 pbIV
指向的元素不会被 BCryptEncrypt()
修改。这是预期的吗?我还查看了 BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构中的字段 pbNonce
,由 pPaddingInfo
指针指向,但该字段也没有被修改。我也试过"manually"推进IV,根据计数器方案自己修改内容,但这也没有帮助。
成功链接 BCryptEncrypt
and/or BCryptDecrypt
函数的正确程序是什么?
我设法让它工作了。看来问题出在 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;
}
@Codeguard 的回答让我完成了我正在研究的 project,这让我首先找到了这个 question/answer;然而,我仍然遇到了一些问题。下面是我遵循的过程,其中提到了棘手的部分。您可以在上面的link处查看实际代码:
- 使用
BCryptOpenAlgorithmProvider
打开算法提供程序使用BCRYPT_AES_ALGORITHM
。 - 使用
BCryptSetProperty
将BCRYPT_CHAINING_MODE
设置为BCRYPT_CHAIN_MODE_GCM
。 - 使用
BCryptGetProperty
获取BCRYPT_OBJECT_LENGTH
分配给 BCrypt 库用于 encrypt/decrypt 操作。根据您的实施,您可能还想:- 使用
BCryptGetProperty
确定BCRYPT_BLOCK_SIZE
并为IV分配scratch space。 Windows API 每次调用都会更新 IV,调用者负责为该用法提供内存。 - 使用
BCryptGetProperty
确定BCRYPT_AUTH_TAG_LENGTH
并为最大可能的标签分配临时space。与 IV 一样,调用者负责提供此 space,API 每次都会更新。
- 使用
- 初始化
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构:- 用
BCRYPT_INIT_AUTH_MODE_INFO()
初始化结构
- 初始化
pbNonce
和cbNonce
字段。请注意,对于BCryptEncrypt
/BCryptDecrypt
的第一次调用,IV 作为输入被忽略,此字段用作“IV”。但是,IV 参数将由第一次调用更新并由后续调用使用,因此仍必须为其提供 space。此外,对于BCryptEncrypt
/BCryptDecrypt
的所有调用,pbNonce
和cbNonce
字段必须保持设置(即使它们在第一次调用后未使用),否则这些调用将报错. - 初始化
pbAuthData
和cbAuthData
。在我的项目中,我在第一次调用BCryptEncrypt
/BCryptDecrypt
之前设置这些字段,然后立即将它们重置为NULL
/0
。您可以在这些调用期间将NULL
/0
作为输入和输出参数传递。 - 初始化
pbTag
和cbTag
。pbTag
可以是NULL
直到最终调用BCryptEncrypt
/BCryptDecrypt
时检索或检查标签,但必须设置cbTag
否则BCryptEncrypt
/BCryptDecrypt
会抱怨。 - 初始化
pbMacContext
和cbMacContext
。这些指向BCryptEncrypt
/BCryptDecrypt
的划痕 space,用于跟踪 tag/mac. 的当前状态
- 将
cbAAD
和cbData
初始化为0
。 API 使用这些字段,因此您可以随时阅读它们,但在最初将它们设置为0
后不应更新它们。 - 将
dwFlags
初始化为BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG
。初始化后,应使用|=
或&=
更改此字段。 Windows 还在该字段中设置调用者需要注意不要更改的标志。
- 用
- 使用
BCryptGenerateSymmetricKey
导入用于 encryption/decryption 的密钥。请注意,您需要向此调用提供与BCRYPT_OBJECT_LENGTH
关联的内存,以供BCryptEncrypt
/BCryptDecrypt
在操作期间使用。 - 使用您的 AAD(如果有)调用
BCryptEncrypt
/BCryptDecrypt
;不需要为此调用提供输入或 space 输出。 (如果调用成功,您可以在BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构的cbAAD
字段中看到 AAD 的大小。)- 设置
pbAuthData
和cbAuthData
以反映 AAD。 - 致电
BCryptEncrypt
或BCryptDecrypt
。 - 将
pbAuthData
和cbAuthData
设置回NULL
和0
。
- 设置
- 调用
BCryptEncrypt
/BCryptDecrypt
"N - 1" 次- 传递给每次调用的数据量必须是算法块大小的倍数。
- 不要 将调用的
dwFlags
参数设置为0
以外的任何参数。 - 输出space必须等于或大于输入的大小
- 最后一次呼叫
BCryptEncrypt
/BCryptDecrypt
(有或没有 plain/cipher 文本 input/output)。输入的大小不必是此调用的算法块大小的倍数。dwFlags
仍设置为0
。- 将
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构的pbTag
字段设置为存储生成的标记的位置或要验证的标记的位置,具体取决于操作是否是加密或解密。 - 使用
&=
语法从BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
结构的dwFlags
字段中删除BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG
。
- 将
- 致电
BCryptDestroyKey
- 致电
BCryptCloseAlgorithmProvider
在这一点上,清除与 BCRYPT_OBJECT_LENGTH
关联的 space 是明智的。