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

Jack Lloyd jack at randombit.net
Thu Oct 19 12:08:09 EDT 2017


On Wed, Oct 18, 2017 at 07:32:02PM -0400, Craig wrote:

> 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.

I'm not familiar with anything specific, sorry.

> 2. Is there a fundamental reason these two implementations would not
> interoperate?

No, GCM is pretty straighforward and there are no optional features or variants
that might cause two implementations to process inputs differently.

> 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?

The correct length is 144 bytes (128 bytes of ciphertext + 16 bytes of tag).
But I think that's because you're comparing the length of the actual ciphertext,
with the length of the base64 ciphertext (144 / 6 * 8 == 192).

> 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?

GCM nonces are problematic because if a nonce is ever duplicated it completely
destroys GCM (it not only leaks information about the plaintext, but also allows
an attacker to forge new messages.) Especially that can be bad news for a long
term shared key, since a nonce repeated even across multiple sessions has the
same effect.

It's not actually neccessary that a GCM nonce be random, just that it never
repeat. So if it's possible to store a counter in persistent memory that is
actually better. However it can be difficult to ensure the counter never repeats
in the event of crashes, restarts, etc.

If it's possible, one fix would be to start each session with a handshake that
derives a session-specific GCM key. I will now do something dumb and suggest
a crypto protocol off the top of my head which means there is probably something
wrong here but it gives a general idea:

# client initiates
 client->server: client_random
# server chooses random field, computes shared key and authenticates itself
 server->client: server_random, GCM(aad=client_random || server_random || 1, text=<empty>)
# client computes shared key, verifies server authentication, sends client authentication
 client->server: GCM(aad=client_random || server_random || 2, text=<empty>)

With the random fields being some fixed length say 16 bytes.

Both sides compute HMAC(key=shared_secret, input=client_random || server_random)
[*] which is the session specific GCM key, Then each side authenticates the
exchange using the newly derived session key. You could also include the first
protocol messages as the ciphertext in the authenticator messages to save round
trips.

At that point a random nonce is probably fine (unless your sessions are
particularly long lived), on a 96 bit random nonce you'd see a repeat only after
~~ 2**48 messages. And in the event it occured, it would only compromise that
session, instead of the long term shared key.

(Of course the optimal thing here would be TLS v1.2 with a PSK ciphersuite,
since that does everything for you in a well studied protocol, but probably
Windows doesn't support that in the kernel.)

As to the different outputs, I think the problem is here:

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

vs

>     const size_t GCM_NONCE_SIZE = 12;
>     const std::vector<BYTE> origNonce = {0,0,0,0,0,0,0,0,0,0,0,0};

With GCM the nonces must be identical including in length, you are using a
19-byte IV on Botan side (which causes GCM to hash the nonce) vs a 12 byte nonce
on Bcrypt side.

>    Botan::Pipe pipe(Botan::get_cipher("AES-128/GCM(16)", key, iv,
> Botan::ENCRYPTION), new Botan::Base64_Encoder);

To set a new nonce, use this

Botan::Keyed_Filter* gcm = Botan::get_cipher("AES-128/GCM(16)", key, Botan::ENCRYPTION);
// pipe takes ownership of gcm but it can still be accessed
Botan::Pipe pipe(gcm, new Botan::Base64_Encoder);
gcm->set_iv(iv);
pipe.process_msg(msg1);
gcm->set_iv(iv2);
pipe.process_msg(msg2);
...

This is not super convenient (IMO) but it's how message specific IVs are handled
in the filter interface. You can also use AEAD_Mode which handles this a bit
more cleanly.

Jack


More information about the botan-devel mailing list