Hazmat Key wrapping (RFC 3394 and RFC 5649)
[Hazmat Home][Home]
The protection of encryption keys is important, and where they often have to be protected. This is especially important for symmetric keys and the private key of a public key pair. One standard for this is RFC 5649 [here], and which supports Advanced Encryption Standard (AES) Key Wrap algorithm [AES-KW1, AES-KW2]. We then use an AES key-encryption key (KEK) with a length of 128, 192, or 256 bits, and where we will get 64-bit blocks as an output. We can then encrypt data with a key (\(K_1\)) and which will then be wrapped to give WRAP(\(K_1\)). To decrypt, we then need the KEK to recover \(K_1\). The unwrapping process also checks the integrity of the key. One method is to perhaps use a production environment, and where the keys are stored and wrapped within a Cloud-based system. The KEK will then be protected from access, and used to produce the actual encryption key [article]. This page uses the Hazmat implementation of [RFC 3394]. With RFC 3394, the length of the key to be wrapped needs to be a multiple of 64 bits, whereas RFC 5549 eliminates this. For the KEK, we either need 128 bits, 192 bits or 256 bits. For this we will use a key derivation function (HKDF) to generate a 16 byte (128 bit) key.
|
Outline
The protection of encryption keys is important, and where they often have to be protected. This is especially important for symmetric keys and the private key of a public key pair. For this, we can use key wrapping. One standard for this is RFC 5649 [1] is the Advanced Encryption Standard (AES) Key Wrap algorithm (AES-KW1, AES-KW2). With AES-KW, we use an AES key-encryption key (KEK) with a length of 128, 192, or 256 bits, and where we will get 64-bit blocks as an output. We can then encrypt data with a key (K1) and which will then be wrapped to give WRAP(K1). To decrypt, we then need the KEK to recover K1. The unwrapping process also checks the integrity of the key.
The protection of the keys by the KEK means that the wrapped keys could then be stored within a Cloud-based system (the red key in Figure 1), but where the KEK will then be protected from access. When the symmetric keys are required to be unwrapped, the KEK can be revealed within a trusted environment, and then produce the actual encryption key. Thus the actual encryption keys are never stored anywhere in the core form.
Figure 1: Key wrapping and unwrapping
Within the Cloud, AWS CloudHSM (hardware security module) supports AES key wrapping with the default initialization vector - 0xA6A6A6A6A6A6A6A6- or a user-defined value. This provides a FIPS 140–2 Level 3 environment and where the keys are handled within a trusted cloud instance. The wrapped keys can then exist outside this but only converted into their actual form within the CloudHSM. A key generated within the CloudHSM can then be wrapped for export from the environment, or imported from an external wrapped key. The AWS CLI is on the form which defines a key handle (with -k) and the wrapping key handle (with -w):
> wrapKey -k 7 -w 14 -out mykey.key -m 5 Key Wrapped. Wrapped Key written to file "mykey.key: length 612 Cfm2WrapKey returned: 0x00 : HSM Return: SUCCESS
The modes for the -m option are AES_KEY_WRAP_PAD_PKCS5 (4) NIST_AES_WRAP_NO_PAD (5) NIST_AES_WRAP_PAD ( 6) RSA_AES (7) RSA_OAEP (8) NIST_TDEA_WRAP (9), AES_GCM (10) and CLOUDHSM_AES_GCM (11). A -iv option supports the additional of a specific initialisation vector.
We can generate either a random KEK with:
wrappingkey=os.urandom(16)
or could use a password and a HKDF. In the following case we will generate a 128-bit (16 byte) key from HKDF:
hkdf = HKDF(algorithm=hashes.SHA256(), length=32,salt=b"", info=b"") wrappingkey=hkdf.derive(password)
Note, in real life, we would also generate a salt value for the HKDF.
Code
Hazmat supports core cryptographical primitives for Key wrapping:
from cryptography.hazmat.primitives import keywrap from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives import hashes import binascii import os import sys password="qwerty123" if (len(sys.argv)>1): password=str(sys.argv[1]) password=password.encode() hkdf = HKDF(algorithm=hashes.SHA256(), length=32,salt=b"", info=b"") wrappingkey=hkdf.derive(password) key=os.urandom(16) print ("\n==Now trying without padding (RFC 3394) ==") wrappedkey=keywrap.aes_key_wrap(wrappingkey,key, backend=None) print ("Password: ",password) print ("\nKey: ",binascii.b2a_hex(key)) print ("\nWrapped key: ",binascii.b2a_hex(wrappedkey)) rtn=keywrap.aes_key_unwrap(wrappingkey, wrappedkey, backend=None) print ("\nKey recovered: ",binascii.b2a_hex(rtn)) print ("\n==Now trying with padding (RFC 5549 ) ==") wrappedkey=keywrap.aes_key_wrap_with_padding(wrappingkey,key, backend=None) print ("\nKey: ",binascii.b2a_hex(key)) print ("\nWrapped key: ",binascii.b2a_hex(wrappedkey)) rtn=keywrap.aes_key_unwrap_with_padding(wrappingkey, wrappedkey, backend=None) print ("\nKey recovered: ",binascii.b2a_hex(rtn))
A sample run is:
==Now trying without padding (RFC 3394) == Password: b'qwerty123' Key: b'34040f8a29b6472410d23adb2ca003ca' Wrapped key: b'24c4f38320254be00ab690f119a63b8fe375256ef67c6e33' Key recovered: b'34040f8a29b6472410d23adb2ca003ca' ==Now trying with padding (RFC 5549 ) == Key: b'34040f8a29b6472410d23adb2ca003ca' Wrapped key: b'da84434fb7868cee34ebe64991170d8ac16c3e2ab0448765' Key recovered: b'34040f8a29b6472410d23adb2ca003ca'