Hazmat Key wrapping for RSA private keyThe 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. In this case we will wrap an RSA private 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 from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization import binascii import sys size=512 password="qwerty123" if (len(sys.argv)>1): password=str(sys.argv[1]) if (len(sys.argv)>2): size=int(sys.argv[2]) print ("Password: ",password) password=password.encode() hkdf = HKDF(algorithm=hashes.SHA256(), length=32,salt=b"", info=b"") wrappingkey=hkdf.derive(password) private_key = rsa.generate_private_key(public_exponent=65537,key_size=size) pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() ) wrappedkey=keywrap.aes_key_wrap_with_padding(wrappingkey,pem, backend=None) print ("\nWrapping Key: ",binascii.b2a_hex(wrappingkey)) print ("\nWrapped key: ",binascii.b2a_hex(wrappedkey)) rtn=keywrap.aes_key_unwrap_with_padding(wrappingkey, wrappedkey, backend=None) print ("\nKey recovered:\n",rtn.decode())
A sample run is:
Password: b'qwerty1234' Wrapping Key: b'3e605c68297fe8599108de9866321e87a445ef8af6dc73ee7d27b611c491ea8d' Wrapped key: b'98fadea1f79a67d0b7ae59e3eac31963dcad1d0735c6522a900e5e25bedaacc8b9e1beca6ee07a840cef3f831ab9a35c7ef02991fce41f07a1d0a9c39eefbb03ecbd67a15b2783199ac82aebca6361b0d5c9c0997d070bbb0b1331c63fe77f62d15379373d446cb033beb2c76ae002902ffe4a740cb1bd879d57b2bcb48ee2c68d7fee0c5b50654b6d27f7414a56aafaec6b36ecdd0cb027299eafede09603c951ff3c183e94397513d7279f5419cf55652b07a40df905385ec17708f3467c096d7d114eeb63bbf2ce1fcb4a40570dfdddf2f410ca89fa5cb69944a0f634501b3f325108e9d5faea7bafd66b63c233c275c975a476df9c0d7d0e4ef905640e46c45a2c00a8dea5e89f5ea011a08565d29cfdf704f2ae276e9dcea1853224097ad20d7d8fcab35fd82131de0f727581e8819aa5d568a76a641edf0c9fd28028a52baf73301c3d6f8b57272a967eec7e56b85d92ab7ac0a6b5809d6ec78d6c6aa112875968a2d308ec840f11bd81d1f9c0ad8158449c4ae4284cc02dd6dfa761b7c391223b15ada4a1d6edb01ff3ce5c1bef854e1cdee5fa740e45b98dd366cdc1a6dd5526ab0ea7e65f929ace6f972c7a7c41a6847741b665864305359556efaefda8bb777f5a0147980b4fa87fe40176aa5ee1f5de95a6a0c6dac87143c31cb50000fafc218ad2f8f31006e6dedb25f29bd4923db7ae10e6' Key recovered: -----BEGIN RSA PRIVATE KEY----- MIIBOgIBAAJBAMruUt9Zfd0E+h0hlAXx6W2NHLdOI/Cak+r1iEOAyQdxoWJvh+Tq W8xIGA6txrh7q7YxP3bH00cSduOsk02fwwECAwEAAQJADYGzTl5cfDt6kjnL6mFh kgMUaDbxOXBwa/EPsr59otsU74ScVZ4WUSrny5/Lje1txk/TPmQ+qEk7e5+BbZKE 4QIhAO0FPKkXPM7Ssk6v9Cl8rCU+cYyrnZ5due/nj+ZC2wrdAiEA2y5GPwoaPvZ2 J9PkRTeHAOhWUdPDcKAb21Q0UxFYPHUCIH10tSwHSb9rlMfDqKhA/llkWPQNbouB rsaGOgu2PXzdAiEAkIi+V/nAfv6lwfPP1vkb9LRBn+omOlHKrOKlMpYAerUCIH+F S2HePw5nI6gRF8V09wFFwgG3bFfeOX002Rl59mjX -----END RSA PRIVATE KEY-----