Key WrappingThe 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][Key wrapping with password]. |
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.
The following is the code:
import binascii import struct from Crypto.Cipher import AES import sys QUAD = struct.Struct('>Q') # code from https://github.com/kurtbrose/aes_keywrap/blob/master/aes_keywrap.py def aes_unwrap_key(kek, wrapped): n = len(wrapped)//8 - 1 #NOTE: R[0] is never accessed, left in for consistency with RFC indices R = [None]+[wrapped[i*8:i*8+8] for i in range(1, n+1)] A = QUAD.unpack(wrapped[:8])[0] decrypt = AES.new(kek, AES.MODE_ECB).decrypt for j in range(5,-1,-1): #counting down for i in range(n, 0, -1): #(n, n-1, ..., 1) ciphertext = QUAD.pack(A^(n*j+i)) + R[i] B = decrypt(ciphertext) A = QUAD.unpack(B[:8])[0] R[i] = B[8:] return b"".join(R[1:]), A # code from https://github.com/kurtbrose/aes_keywrap/blob/master/aes_keywrap.py def aes_wrap_key(kek, plaintext, iv=0xa6a6a6a6a6a6a6a6): n = len(plaintext)//8 R = [None]+[plaintext[i*8:i*8+8] for i in range(0, n)] A = iv encrypt = AES.new(kek, AES.MODE_ECB).encrypt for j in range(6): for i in range(1, n+1): B = encrypt(QUAD.pack(A) + R[i]) A = QUAD.unpack(B[:8])[0] ^ (n*j + i) R[i] = B[8:] return QUAD.pack(A) + b"".join(R[1:]) kek="000102030405060708090A0B0C0D0E0F" key="00112233445566778899AABBCCDDEEFF" if (len(sys.argv)>1): kek=str(sys.argv[1]) if (len(sys.argv)>2): key=str(sys.argv[2]) KEK = binascii.unhexlify(kek) KEY = binascii.unhexlify(key) wrapped=aes_wrap_key(KEK,KEY) rtn,iv=aes_unwrap_key(KEK,wrapped) print ("KEK: ",kek) print ("Key: ",key) print ("Wrapped Key: ",binascii.hexlify(wrapped)) print ("Unwrapped key: ",binascii.hexlify(rtn)) # Test from RFC5649 # KEK: 000102030405060708090A0B0C0D0E0F # Key: 00112233445566778899AABBCCDDEEFF # Wrap: 1FA68B0A8112B447 AEF34BD8FB5A7B82 9D3E862371D2CFE5
A sample run is:
KEK: 000102030405060708090A0B0C0D0E0F Key: 00112233445566778899AABBCCDDEEFF Wrapped Key: b'1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5' Unwrapped key: b'00112233445566778899aabbccddeeff'
Presentation
References
[1] Advanced Encryption Standard (AES) Key Wrap with Padding Algorithm [here].