问题
I am trying to use AES-GMAC with BCrypt (Microsoft CNG if that helps) but the documentation seems contradictory to what I would expect.
RFC 4543 states that AES-GMAC requires a secret key (which I was expecting), but BCryptCreateHash function states that pbSecret
is not used unless the BCRYPT_ALG_HANDLE_HMAC
is provided.
I've tried using the BCRYPT_ALG_HANDLE_HMAC
on BcryptOpenAlgorithmProvider
and as I expected, I get a NOT_SUPPORTED
when using BCRYPT_AES_GMAC_ALGORITHM
with BCRYPT_ALG_HANDLE_HMAC
.
The process I was expecting was something like:
BCryptOpenAlgorithmProvider(*with no HMAC flag*)
BCryptCreateHash(*including secret*)
BCryptHashData()
BCryptFinaliseHash()
Am I misunderstanding something here?
回答1:
I've tried using the
BCRYPT_ALG_HANDLE_HMAC
onBcryptOpenAlgorithmProvider
and as I expected, I get aNOT_SUPPORTED
when usingBCRYPT_AES_GMAC_ALGORITHM
withBCRYPT_ALG_HANDLE_HMAC
.
BCRYPT_AES_GMAC_ALGORITHM
seems broken. Don't use it.
Use AES/GCM via BCRYPT_AES_ALGORITHM
. Don't encrypt any data. Only authenticate the aad. The resulting tag is the GMAC over the AAD.
How do I use AES-GMAC with a secret in BCrypt?
The steps to create the GMAC are:
- Get AES algorithm
- Set GCM mode
- Key the cipher
- Set the nonce
- Authenticate the aad
"aad" is "additionally authenticated data". It is different than plain text data that is traditionally encrypted.
Programs to MAC the aad are below for both Bcrypt and Crypto++. Since you don't encrypt data, the call to BCryptEncrypt
looks like:
status = BCryptEncrypt(
hKey,
NULL, 0,
(PVOID)&aadInfo,
NULL, 0,
NULL, 0,
&ulWritten,
0
);
Note well: SP800-38D specifies max nonce of 264-1, but Microsoft limits the nonce to 12 bytes. If you try to use 16 bytes, which is AES blocksize, then the result is 0xc000000d
or STATUS_INVALID_PARAMETER
. The Microsoft docs fail to document the limitations.
Here is the Microsoft Bcrypt program.
#include <Windows.h>
#include <bcrypt.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <memory>
#include <stdexcept>
#pragma comment (lib, "bcrypt.lib")
std::string NtStatusToString(const CHAR* operation, NTSTATUS status)
{
std::ostringstream oss;
oss << operation << ", 0x" << std::hex << status;
switch(status)
{
case 0xc0000000:
oss << " (STATUS_SUCCESS)";
break;
case 0xC0000008:
oss << " (STATUS_INVALID_HANDLE)";
break;
case 0xc000000d:
oss << " (STATUS_INVALID_PARAMETER)";
break;
case 0xc00000bb:
oss << " (STATUS_NOT_SUPPORTED)";
break;
case 0xC0000225:
oss << " (STATUS_NOT_FOUND)";
break;
}
return oss.str();
}
std::string ArrayToHexString(const UCHAR arr[], size_t size)
{
std::ostringstream oss;
for (size_t i=0; i<size; ++i)
{
oss << std::hex << std::setw(2) << std::setfill('0');
oss << (unsigned int)arr[i];
}
return oss.str();
}
int main(int argc, char* argv[])
{
BCRYPT_ALG_HANDLE hAlgorithm = 0;
BCRYPT_KEY_HANDLE hKey = 0;
UCHAR* pbKeyObject = 0;
ULONG cbKeyObjectLength = 0;
// SP800-38D specifies max nonce of 2^64-1, but
// Microsoft limits the nonce to 12 bytes.
UCHAR key[16] = {0};
UCHAR iv[12] = {0};
UCHAR tag[16] = {0};
// The data to be GMAC'd. It is not encrypted.
std::string aad("Not so secret additionally authenticated data");
try
{
NTSTATUS status = 0;
ULONG ulWritten = 0;
////////////////////////////////////////
status = BCryptOpenAlgorithmProvider(
&hAlgorithm,
BCRYPT_AES_ALGORITHM,
NULL, 0
);
if (!BCRYPT_SUCCESS(status))
throw std::runtime_error(
NtStatusToString("BCryptOpenAlgorithmProvider", status));
status = BCryptSetProperty(
hAlgorithm,
BCRYPT_CHAINING_MODE,
(UCHAR*)BCRYPT_CHAIN_MODE_GCM,
sizeof(BCRYPT_CHAIN_MODE_GCM),
0
);
if (!BCRYPT_SUCCESS(status))
throw std::runtime_error(
NtStatusToString("BCryptSetProperty (BCRYPT_CHAINING_MODE)", status));
////////////////////////////////////////
status = BCryptGetProperty(
hAlgorithm,
BCRYPT_OBJECT_LENGTH,
(PUCHAR)&cbKeyObjectLength,
sizeof(cbKeyObjectLength),
&ulWritten,
0
);
if (!BCRYPT_SUCCESS(status))
throw std::runtime_error(
NtStatusToString("BCryptGetProperty (BCRYPT_OBJECT_LENGTH)", status));
pbKeyObject = new UCHAR[cbKeyObjectLength];
if (!pbKeyObject)
throw std::runtime_error("pbKeyObject");
////////////////////////////////////////
status = BCryptGenerateSymmetricKey(
hAlgorithm,
&hKey,
pbKeyObject,
cbKeyObjectLength,
key,
sizeof(key),
0
);
if (!BCRYPT_SUCCESS(status))
throw std::runtime_error(
NtStatusToString("BCryptGenerateSymmetricKey", status));
////////////////////////////////////////
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO aadInfo;
BCRYPT_INIT_AUTH_MODE_INFO(aadInfo);
aadInfo.pbNonce = iv;
aadInfo.cbNonce = sizeof(iv);
// Awful API design; non-const pointer.
aadInfo.pbAuthData = reinterpret_cast<UCHAR*>(&aad[0]);
aadInfo.cbAuthData = static_cast<ULONG>(aad.size());
aadInfo.cbAAD = static_cast<ULONG>(aad.size());
aadInfo.pbTag = tag;
aadInfo.cbTag = sizeof(tag);
////////////////////////////////////////
status = BCryptEncrypt(
hKey,
NULL, 0,
(PVOID)&aadInfo,
NULL, 0,
NULL, 0,
&ulWritten,
0
);
if (!BCRYPT_SUCCESS(status))
throw std::runtime_error(
NtStatusToString("BCryptEncrypt", status));
std::cout << "Message: " << aad << std::endl;
std::cout << "GMAC: " << ArrayToHexString(tag, sizeof(tag));
}
catch (const std::exception& ex)
{
std::cerr << "Exception: " << ex.what() << std::endl;
}
if (hKey)
BCryptDestroyKey(hKey);
if (hAlgorithm)
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
// Destroy after handles
if (pbKeyObject)
delete [] pbKeyObject;
return 0;
}
And here is the result.
>cl.exe /DWINVER=0x0600 /TP /GR /EHsc bcrypt-gmac.cpp /link
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24210 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
bcrypt-gmac.cpp
Microsoft (R) Incremental Linker Version 14.00.24210.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:bcrypt-gmac.exe
bcrypt-gmac.obj
>.\bcrypt-gmac.exe
Message: Not so secret additionally authenticated data
GMAC: 3a1158d288cd796899f0366cdf594020
Here is the code for Crypto++. You can do the same in Botan or OpenSSL.
#include "cryptlib.h"
#include "filters.h"
#include "files.h"
#include "hex.h"
#include "aes.h"
#include "gcm.h"
#include <iostream>
#include <string>
int main(int argc, char* argv[])
{
using namespace CryptoPP;
byte key[16] = {0};
byte iv[12] = {0};
byte tag[16] = {0};
std::string aad("Not so secret additionally authenticated data");
try
{
GCM< AES >::Encryption enc;
enc.SetKeyWithIV(key, sizeof(key), iv, sizeof(iv));
// AuthenticatedEncryptionFilter defines two
// channels: DEFAULT_CHANNEL and AAD_CHANNEL
// DEFAULT_CHANNEL is encrypted and authenticated,
// AAD_CHANNEL is authenticated.
AuthenticatedEncryptionFilter ef(enc,
new ArraySink(tag, sizeof(tag)),
false, 16 /* Tag size, MAC_AT_END */
); // AuthenticatedEncryptionFilter
// Authenticated data *must* be pushed before
// Confidential/Authenticated data. Otherwise
// we must catch the BadState exception
ef.ChannelPut(AAD_CHANNEL, (const byte*)aad.data(), aad.size());
ef.ChannelMessageEnd(AAD_CHANNEL);
// Confidential data comes after authenticated data.
// This is a limitation due to CCM mode, not GCM mode.
//ef.ChannelPut(DEFAULT_CHANNEL, pdata.data(), pdata.size());
//ef.ChannelMessageEnd(DEFAULT_CHANNEL);
// Signal end of message
ef.MessageEnd();
std::cout << "Message: " << aad << std::endl;
std::cout << "GMAC: ";
StringSource(tag, sizeof(tag), true, new HexEncoder(new FileSink(std::cout)));
std::cout << std::endl;
}
catch(CryptoPP::Exception& ex)
{
std::cerr << "Exception: " << ex.what() << std::endl;
}
return 0;
}
And here is the result.
$ g++ test.cxx ./libcryptopp.a -o test.exe
$ ./test.exe
Message: Not so secret additionally authenticated data
GMAC: 3A1158D288CD796899F0366CDF594020
来源:https://stackoverflow.com/questions/57456546/how-do-i-use-aes-gmac-with-a-secret-in-bcrypt