[botan-devel] Interoperability between Botan and Windows CNG/BCrypt with AES-128/GCM

Craig craig-crypto at contexsure.com
Wed Oct 18 19:32:02 EDT 2017


Greetings,

I am working on a project where I need confidentiality and authenticity
when communicating between a Windows client and a Linux server. I am
constrained on the Windows side into using Microsoft's CNG / BCrypt API
since I will be doing the crypto component in kernel-mode via a driver
(which CNG supports). I am using a pre-shared key for the communication
(which has been distributed out-of-band). On the Linux side, I am using
Botan because I've used it in the past and like it. I am using AES-128/GCM
on both for encryption and to authenticate.

I believe I have successfully implemented identical functionality on the
two systems, but the outputs are not matching despite using identical IV's
(all zeros). Each deterministically creates the same output across multiple
trials.

I've looked for examples of communication between these implementations,
but have not found success. I have included the output of the two programs
and the minimal code for each (omitting the Base64 encoding/decoding, which
is identical on both) for reference.

Given all this, I have a few questions that are more scoped than the
broader "how do I make this work?":

1. Are there any known cases of getting Botan and CNG to interoperate with
AES or other algorithms? I'm happy to switch to a tried-and-true path.

2. Is there a fundamental reason these two implementations would not
interoperate? Are there any obvious settings or changes that I may be
missing? I did not see a way to set the GCM nonce as part of a pipe in
Botan... is it possible that this could be messing me up?

3. I noticed the output of the Botan implementation is 192 bytes while its
only 144 for CNG, which appears to be 128 bytes of payload plus 16 bytes
for what I suspect is the GMAC authentication. Any idea what might be going
on there?

4. Assuming this actually works someday, am I correct in believing I should
be randomly generating an IV and/or GCM nonce for each message sending
those values along with the ciphertext/GMAC for the decryption on the other
side?

Any suggestions or feedback are welcome. If I can get this resolved, it is
my hope to publicly share a working version of these snippets for the
community.

Thank you for your time,


-- Craig

P.S., I do not believe library users can truly appreciate the Botan API
until they try to use CNG. Sheesh.


=== Begin Botan / Linux Output ===

Prompt> ./enctest


Key: PASSWORDpasswordPASSWA==

Initialization Vector: Buffer (size: 16): AAAAAAAAAAAAAAAAAAAAAA==

Plaintext (128 bytes): Your great-grandfather gave this watch to your
granddad for good luck. Unfortunately, Dane's luck wasn't as good as his
old man.

AES-128/GCM(16) ciphertext: Buffer (size: 192):
lAcnulFBU0diq9ogXMl2uh2tOAROOzTkI4/W6SB4JsvF0gzvIVTeNXpAT8Y7BlrzJ5H504+LRzH9JUgc7+zBNt/9Q6XSYUrxteDc7WnrH8P/DjUBJHTulK4KJfdOaD4jAIrYePEKL3McuMSKYtI366Uv2T7xOLA/IWa/0dP+VVib4MYxzd3WIn1WqHvTW1DG
Prompt>

=== End of Botan / Linux Output ===

=== Begin Windows CNG / BCrypt Output ===

Prompt> Project1.exe
Auth Tag Length (min): 12
Auth Tag Length (max): 16
Block Length: 16
Key: Buffer (size: 16): PASSWORDpasswordPASSWA==
Obtained algorithm name: AES
Obtained chaining mode: ChainingModeGCM

Initialization Vector: Buffer (size: 16): AAAAAAAAAAAAAAAAAAAAAA==

Nonce: Buffer (size: 12): AAAAAAAAAAAAAAAA

Plaintext (128 bytes): Your great-grandfather gave this watch to your
granddad for good luck. Unfortunately, Dane's luck wasn't as good as his
old man.

AES-128/GCM(16) ciphertext: Buffer (size: 128):
3WIH6xoLOIQ31fAW+uMQCpY50BuSd2gEfE4M9KlsyQiY2FE1FsBUUaZWaAe7nKka5HD0Z9PnyLIIPSIuwM4Uu9CIA6als1ro1KJJBUkynwtvkcbPweHegzLwiiJ6R0U2YOJH7rambcMWeruWiXPniubgectCS014Cfpwmlq1OSQ=

Auth Tag: Buffer (size: 16): 1qTetD2JKSG4p+LL5fVTSw==

Prompt>

=== End of Windows CNG / BCrypt Output ===

=== Begin Botan / Linux Code ===

#include <botan/rng.h>
#include <botan/auto_rng.h>
#include <botan/cipher_mode.h>
#include <botan/block_cipher.h>
#include <botan/hex.h>
#include <botan/base64.h>
#include <botan/pipe.h>
#include <iostream>

#include <botan/key_filt.h>
#include <botan/cipher_filter.h>
#include <botan/b64_filt.h>
#include <botan/mac.h>

using namespace std;

int main() {
   std::string plaintext("Your great-grandfather gave this watch to your
granddad for good luck. Unfortunately, Dane's luck wasn't as good as his
old man.");

   uint8_t keyStorage[65];
   size_t consumedAmount;

   Botan::base64_decode(keyStorage, "PASSWORDpasswordPASSWORDpassword", 32,
consumedAmount, true, false);
   Botan::secure_vector<uint8_t> key(keyStorage, keyStorage+16);

   cout << "Key: " << base64_encode(&key[0], 16) << endl;

   std::vector<uint8_t> iv = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

   // We should really have a secure IV, but for testing, we need it to be
reproducible:
   //std::unique_ptr<Botan::RandomNumberGenerator> rng(new
Botan::AutoSeeded_RNG);
   //rng->randomize(iv.data(),iv.size());

   Botan::Pipe pipe(Botan::get_cipher("AES-128/GCM(16)", key, iv,
Botan::ENCRYPTION), new Botan::Base64_Encoder);
   pipe.process_msg(plaintext.c_str());
   std::string m1 = pipe.read_all_as_string(0);

   std::cout << "Initialization Vector: Buffer (size: 16): " <<
base64_encode(&iv[0], 16) << std::endl;

   std::cout << "Plaintext (" << plaintext.length() << " bytes): " <<
plaintext << std::endl;

   std::cout << "AES-128/GCM(16) ciphertext: Buffer (size: " << m1.length()
<< "): " << m1 << std::endl;

   return 0;
}

=== End of Botan / Linux Code ===

=== Begin Windows CNG / BCrypt Code ===

#include <windows.h>
#include <assert.h>
#include <vector>
#include <Bcrypt.h>
#include <iostream>
#include <string>
#include <string.h>
#include <wincrypt.h>
#include <ntstatus.h>
#pragma comment(lib, "bcrypt.lib")

int main(int argc, CHAR* argv[]) {
    NTSTATUS bcryptResult = 0;
    DWORD bytesDone = 0;

    // This gets us the AES algorithm:
    BCRYPT_ALG_HANDLE algHandle = 0;
    bcryptResult = BCryptOpenAlgorithmProvider(&algHandle,
BCRYPT_AES_ALGORITHM, 0, 0);
    assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptOpenAlgorithmProvider");

    // This sets up the GCM chaining mode:
    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)");

    // This tells us the length of the authentication tag:
    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)");

    std::cout << "Auth Tag Length (min): " << authTagLengths.dwMinLength <<
std::endl;
    std::cout << "Auth Tag Length (max): " << authTagLengths.dwMaxLength <<
std::endl;

    // This tells us the length of the block:
    DWORD blockLength = 0;
    bcryptResult = BCryptGetProperty(algHandle, BCRYPT_BLOCK_LENGTH,
(BYTE*) &blockLength, sizeof(blockLength), &bytesDone, 0);
    assert(BCRYPT_SUCCESS(bcryptResult) ||
!"BCryptGetProperty(BCRYPT_BLOCK_LENGTH)");

    std::cout << "Block Length: " << blockLength << std::endl;

    BCRYPT_KEY_HANDLE keyHandle = 0;

   std::string decodedKeyString =
base64_decode("PASSWORDpasswordPASSWORDpassword");

    const std::vector<BYTE> key(decodedKeyString.data(),
decodedKeyString.data() + blockLength);

    bcryptResult = BCryptGenerateSymmetricKey(algHandle, &keyHandle, 0, 0,
(PUCHAR) &key[0], key.size(), 0);
    assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGenerateSymmetricKey");

    std::cout << "Key: ";
    printB64((BYTE *) &key[0], key.size());

    UCHAR namebuff[256];
    ULONG tempSize = 256; // We should probably look at this, but frankly,
it's more than enough.
    const WCHAR *name = (const WCHAR *) namebuff;
    bcryptResult = BCryptGetProperty(algHandle, BCRYPT_ALGORITHM_NAME,
namebuff, sizeof(namebuff), &tempSize, 0);
    std::cout << "Obtained algorithm name: ";
    std::wcout << name << std::endl; // Outputs: AES

    bcryptResult = BCryptGetProperty(algHandle, BCRYPT_CHAINING_MODE,
namebuff, sizeof(namebuff), &tempSize, 0);
    std::cout << "Obtained chaining mode: ";
    std::wcout << name << std::endl; // Outputs: ChainingModeGCM

    // AES 128 (== 16 bytes for an IV):
    const size_t AES_IV_SIZE = 16;
    const std::vector<BYTE> origIV = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

    std::cout << "Initialization Vector: ";
    printB64((BYTE *) &origIV[0], origIV.size());

    // This must always be 96 bits (12 bytes):
    const size_t GCM_NONCE_SIZE = 12;
    const std::vector<BYTE> origNonce = {0,0,0,0,0,0,0,0,0,0,0,0};

    std::cout << "Nonce: ";
    printB64((BYTE *) &origNonce[0], origNonce.size());
    //const std::vector<BYTE> origNonce = MakeRandomBytes(GCM_NONCE_SIZE);

    std::string plaintext("Your great-grandfather gave this watch to your
granddad for good luck. Unfortunately, Dane's luck wasn't as good as his
old man.");
    const std::vector<BYTE> plaintextVector(plaintext.data(),
plaintext.data() + plaintext.length());

    // Encrypt data in one-go in place:
    std::vector<BYTE> encrypted = plaintextVector;
    std::vector<BYTE> authTag(authTagLengths.dwMaxLength);

    // This sets up our nonce and GCM authentication tag parameters:
    BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
    BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
    authInfo.pbNonce = (PUCHAR) &origNonce[0]; // A nonce is required for
GCM
    authInfo.cbNonce = origNonce.size(); // The size of the nonce is
provided here
    authInfo.pbTag = &authTag[0]; // The buffer that will gain the
authentication tag
    authInfo.cbTag = authTag.size(); // The size of the authentication tag

    std::cout << "Plaintext (" << plaintext.length() << " bytes): " <<
plaintext << std::endl;

    bcryptResult = BCryptEncrypt (
        keyHandle,
        &encrypted[0], encrypted.size(), // Plaintext and its associated
size
        &authInfo, // Must be a BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO for
GCM
        (PUCHAR) &origIV[0], 16, // No initialization vector provided. We'd
want to change this.
        &encrypted[0], encrypted.size(), // The buffer in which to store
the encrypted output and its size
        &bytesDone, 0
    );

    std::cout << "AES-128/GCM(16) ciphertext: ";
    printB64((BYTE *) &encrypted[0], encrypted.size());

    std::cout << "Auth Tag: ";
    printB64((BYTE *) &authTag[0], authTag.size());

    // Cleanup
    BCryptDestroyKey(keyHandle);
    BCryptCloseAlgorithmProvider(algHandle, 0);

    return 0;
}

=== End of Windows CNG / BCrypt Code ===
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.randombit.net/pipermail/botan-devel/attachments/20171018/c2d81cf4/attachment-0001.html>


More information about the botan-devel mailing list