Hazmat Key wrapping for ECC private key
[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. In this case we will wrap an ECC 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 ec 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): type=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 = ec.generate_private_key(ec.SECP256K1()) if (type==1): private_key = ec.generate_private_key(ec.SECP192R1()) elif (type==2): private_key = ec.generate_private_key(ec.SECP224R1()) elif (type==3): private_key = ec.generate_private_key(ec.SECP256K1()) elif (type==4): private_key = ec.generate_private_key(ec.SECP256R1()) elif (type==5): private_key = ec.generate_private_key(ec.SECP384R1()) elif (type==6): private_key = ec.generate_private_key(ec.SECP521R1()) elif (type==7): private_key = ec.generate_private_key(ec.BrainpoolP256R1()) elif (type==8): private_key = ec.generate_private_key(ec.BrainpoolP384R1()) elif (type==9): private_key = ec.generate_private_key(ec.BrainpoolP512R1()) public_key = private_key.public_key() vals=public_key.public_numbers() print ("Name of curve: ",vals.curve.name) pem = private_key.private_bytes(encoding=serialization.Encoding.PEM,format=serialization.PrivateFormat.PKCS8,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: qwerty123 Name of curve: secp256k1 Wrapping Key: b'5edc7cacd3fd7dc5c941ef84d4b89d0465bba40ad78c75d640d96d00cbef5ad4' Wrapped key: b'd0b67cbf30eafb56c8f8c244a9fe4ecbf31f457824e6f3b63f3e4170fae931bf56293b2ddaeedc533ede501d15c8ef9fac9619356334be594621ec7161ee8f5e3a4c2512928f03c9016c05f92414da69e8fea70431c0d1047e2dbcb58c7cdfb66a0bf13a95da7daa40c634680481c6e5e66e66953d5c29d7e1f2614a204ce8447acb8da1f6c9c8d6fd89c743c78dbf53faca8370f2c472d33ff93a26c4cd8accb10d20ca25bdb201839c6cb80358a223f7849d7edb375e5198a6404a06f610f36102758f0b90340db3f6ab0ef64e5a05cd2f7047ec304863610355e806cde00d4900f28d5e72a9e06b826b4c1197be24bd05f2e7f02b8631' Key recovered: -----BEGIN PRIVATE KEY----- MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgRseNobqPTGjbGWHJ+2Bg 1AaS8dtVvw5RtJoQepEVWAGhRANCAATTK8W45lmgE8IERHXSf0KomxjF/oOPrrGi zAqrErNFu08Qym6Kyoany+SGdwwdy0li6JDYY1EyYeXsinmjqxjx -----END PRIVATE KEY-----