-
-
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 |
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. 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.