-
-
Save kurtbrose/4243633 to your computer and use it in GitHub Desktop.
''' | |
Key wrapping and unwrapping as defined in RFC 3394. | |
Also a padding mechanism that was used in openssl at one time. | |
The purpose of this algorithm is to encrypt a key multiple times to add an extra layer of security. | |
Personally, I wouldn't recommend using this for most applications. | |
Just use AES/mode CTR to encrypt your keys, the same as you would any other data. | |
The time to use this code is when you need compatibility with another system that implements the RFC. | |
(For example, these functions are compatible with the openssl functions of the same name.) | |
Performance should be reasonable, since the heavy lifting is all done in PyCrypto's AES. | |
''' | |
import struct | |
from Crypto.Cipher import AES | |
QUAD = struct.Struct('>Q') | |
def aes_unwrap_key_and_iv(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).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 "".join(R[1:]), A | |
#key wrapping as defined in RFC 3394 | |
#http://www.ietf.org/rfc/rfc3394.txt | |
def aes_unwrap_key(kek, wrapped, iv=0xa6a6a6a6a6a6a6a6): | |
key, key_iv = aes_unwrap_key_and_iv(kek, wrapped) | |
if key_iv != iv: | |
raise ValueError("Integrity Check Failed: "+hex(key_iv)+" (expected "+hex(iv)+")") | |
return key | |
#alternate initial value for aes key wrapping, as defined in RFC 5649 section 3 | |
#http://www.ietf.org/rfc/rfc5649.txt | |
def aes_unwrap_key_withpad(kek, wrapped): | |
if len(wrapped) == 16: | |
plaintext = AES.new(kek).decrypt(wrapped) | |
key, key_iv = plaintext[:8], plaintext[8:] | |
else: | |
key, key_iv = aes_unwrap_key_and_iv(kek, wrapped) | |
key_iv = "{0:016X}".format(key_iv) | |
if key_iv[:8] != "A65959A6": | |
raise ValueError("Integrity Check Failed: "+key_iv[:8]+" (expected A65959A6)") | |
key_len = int(key_iv[8:], 16) | |
return key[:key_len] | |
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).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) + "".join(R[1:]) | |
def aes_wrap_key_withpad(kek, plaintext): | |
iv = 0xA65959A600000000 + len(plaintext) | |
plaintext = plaintext + "\0" * ((8 - len(plaintext)) % 8) | |
if len(plaintext) == 8: | |
return AES.new(kek).encrypt(QUAD.pack[iv] + plaintext) | |
return aes_wrap_key(kek, plaintext, iv) | |
def test(): | |
#test vector from RFC 3394 | |
import binascii | |
KEK = binascii.unhexlify("000102030405060708090A0B0C0D0E0F") | |
CIPHER = binascii.unhexlify("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5") | |
PLAIN = binascii.unhexlify("00112233445566778899AABBCCDDEEFF") | |
assert aes_unwrap_key(KEK, CIPHER) == PLAIN | |
assert aes_wrap_key(KEK, PLAIN) == CIPHER |
Good point, fixed the RFC 5649 support
Hello. I believe that in the aes_wrap_key_withpad
function the padding is incorrect. Given a 256bit key to wrap it should not pad additional zeros to the end. It should be something more along the lines of...
plaintext = plaintext + "\0" * int(8*math.ceil(float(len(plaintext))/8) - len(plaintext))
Using this line in my fork of your code, I was able to reproduce the test vectors from RFC 5649, and it doesn't pad if the key is 256bits in length.
I've pulled this expression from the following reference:
http://csrc.nist.gov/publications/drafts/800-38F/Draft-SP800-38F_Aug2011.pdf
But I believe that RFC 5649 does state that also. It would be nice to have some test vectors from NIST on this.
Thank you very much for tracking down that error! (Sorry I am not monitoring this closely; I am very pleased that this code has been useful to so many people.)
I will take a look soon, integrate your suggestion and add the test vectors from the RFC.
Maybe I should turn this into a real module. It is kind of silly how small/simple it is, but if it is proving useful that would make it easier to use.
Thanks, I think I was missing parens in that expression.
Broke this out into a pip install-able package for convenient usage: https://pypi.python.org/pypi/aes-keywrap
Available vis pip install aes-keywrap
Super useful. Thank you!
Note about the code comment: since keywrapping is commonly used to encrypt sensitive key material, it is recommended that an authenticated encryption mode be used instead of CTR mode. AES KeyWrap is considered such a mode even though it doesn't have a formal security proof. The other well known mechanism is AES-GCM.
I have tried running this example but it is giving me following error:
File "**********\Programs\Python\Python39\lib\site-packages\aes_keywrap.py", line 15, in aes_unwrap_key_and_iv
R = [None]+[wrapped[i8:i8+8] for i in range(1, n+1)]
TypeError: 'float' object cannot be interpreted as an integer
I believe that the module is not compatible with Python v3. Even after resolving float issue above, more issues are popped up subsequently.
Do you have a module which can work with Python 3?
Of Great help @kurtbrose - with IoT key management in Secure Element chips this work is instrumental!
Hello! Please note that RFC 3394 was later extended by RFC 5649. It would be nice to support both modes to improve compatibility. Patches for OpenSSL are on the mailing list and both modes are in upcomming PKCS#11 v2.40 standard.