-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathaes_keywrap.py
94 lines (83 loc) · 3.77 KB
/
aes_keywrap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
'''
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.
'''
import struct
# TODO: dependency flexibility; make pip install aes_keywrap[cryptography], etc work
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, 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
def aes_unwrap_key(kek, wrapped, iv=0xa6a6a6a6a6a6a6a6):
'''
key wrapping as defined in RFC 3394
http://www.ietf.org/rfc/rfc3394.txt
'''
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
def aes_unwrap_key_withpad(kek, wrapped):
'''
alternate initial value for aes key wrapping, as defined in RFC 5649 section 3
http://www.ietf.org/rfc/rfc5649.txt
'''
if len(wrapped) == 16:
plaintext = AES.new(kek, AES.MODE_ECB).decrypt(wrapped)
key, key_iv = plaintext[8:], QUAD.unpack(plaintext[:8])[0]
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, 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:])
def aes_wrap_key_withpad(kek, plaintext):
iv = 0xA65959A600000000 + len(plaintext)
plaintext = plaintext + b"\0" * ((8 - len(plaintext)) % 8)
if len(plaintext) == 8:
return AES.new(kek, AES.MODE_ECB).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
#test vector from RFC 5649 - 20 octets
KEK = binascii.unhexlify("5840DF6E29B02AF1AB493B705BF16EA1AE8338F4DCC176A8")
CIPHER = binascii.unhexlify("138BDEAA9B8FA7FC61F97742E72248EE5AE6AE5360D1AE6A5F54F373FA543B6A")
PLAIN = binascii.unhexlify("C37B7E6492584340BED12207808941155068F738")
assert aes_unwrap_key_withpad(KEK, CIPHER) == PLAIN
assert aes_wrap_key_withpad(KEK, PLAIN) == CIPHER
#test vector from RFC 5649 - 7 octets
KEK = binascii.unhexlify("5840DF6E29B02AF1AB493B705BF16EA1AE8338F4DCC176A8")
CIPHER = binascii.unhexlify("AFBEB0F07DFBF5419200F2CCB50BB24F")
PLAIN = binascii.unhexlify("466F7250617369")
assert aes_unwrap_key_withpad(KEK, CIPHER) == PLAIN
assert aes_wrap_key_withpad(KEK, PLAIN) == CIPHER