OLD | NEW |
(Empty) | |
| 1 """ |
| 2 Key wrapping and unwrapping as defined in RFC 3394. |
| 3 Also a padding mechanism that was used in openssl at one time. |
| 4 The purpose of this algorithm is to encrypt a key multiple times to add an |
| 5 extra layer of security. |
| 6 |
| 7 Personally, I wouldn't recommend using this for most applications. |
| 8 Just use AES/mode CTR to encrypt your keys, the same as you would any other |
| 9 data. |
| 10 The time to use this code is when you need compatibility with another system |
| 11 that implements the RFC. |
| 12 (For example, these functions are compatible with the openssl functions of |
| 13 the same name.) |
| 14 |
| 15 Performance should be reasonable, since the heavy lifting is all done in |
| 16 PyCrypto's AES. |
| 17 """ |
| 18 from __future__ import division |
| 19 |
| 20 try: |
| 21 from builtins import hex |
| 22 from builtins import range |
| 23 except ImportError: |
| 24 pass |
| 25 import struct |
| 26 from Crypto.Cipher import AES |
| 27 |
| 28 QUAD = struct.Struct('>Q') |
| 29 |
| 30 |
| 31 def aes_unwrap_key_and_iv(kek, wrapped): |
| 32 n = (len(wrapped) // 8) - 1 |
| 33 #NOTE: R[0] is never accessed, left in for consistency with RFC indices |
| 34 r = [None] + [wrapped[i * 8:i * 8 + 8] for i in range(1, n + 1)] |
| 35 a = QUAD.unpack(wrapped[:8])[0] |
| 36 decrypt = AES.new(kek).decrypt |
| 37 for j in range(5, -1, -1): #counting down |
| 38 for i in range(n, 0, -1): #(n, n-1, ..., 1) |
| 39 ciphertext = QUAD.pack(a ^ (n * j + i)) + r[i] |
| 40 B = decrypt(ciphertext) |
| 41 a = QUAD.unpack(B[:8])[0] |
| 42 r[i] = B[8:] |
| 43 return b"".join(r[1:]), a |
| 44 |
| 45 |
| 46 def aes_unwrap_key(kek, wrapped, iv=0xa6a6a6a6a6a6a6a6): |
| 47 key, key_iv = aes_unwrap_key_and_iv(kek, wrapped) |
| 48 if key_iv != iv: |
| 49 raise ValueError( |
| 50 "Integrity Check Failed: " + hex(key_iv) + " (expected " + hex( |
| 51 iv) + ")") |
| 52 return key |
| 53 |
| 54 |
| 55 def aes_unwrap_key_withpad(kek, wrapped): |
| 56 key, key_iv = aes_unwrap_key_and_iv(kek, wrapped) |
| 57 key_iv = "{0:016X}".format(key_iv) |
| 58 if key_iv[:8] != "A65959A6": |
| 59 raise ValueError( |
| 60 "Integrity Check Failed: " + key_iv[:8] + " (expected A65959A6)") |
| 61 key_len = int(key_iv[8:], 16) |
| 62 return key[:key_len] |
| 63 |
| 64 |
| 65 def aes_wrap_key(kek, plaintext, iv=0xa6a6a6a6a6a6a6a6): |
| 66 n = len(plaintext) // 8 |
| 67 r = [None] + [plaintext[i * 8:i * 8 + 8] for i in range(0, n)] |
| 68 a = iv |
| 69 encrypt = AES.new(kek).encrypt |
| 70 for j in range(6): |
| 71 for i in range(1, n + 1): |
| 72 b = encrypt(QUAD.pack(a) + r[i]) |
| 73 a = QUAD.unpack(b[:8])[0] ^ (n * j + i) |
| 74 r[i] = b[8:] |
| 75 return QUAD.pack(a) + b''.join(r[1:]) |
| 76 |
| 77 |
| 78 def aes_wrap_key_withpad(kek, plaintext): |
| 79 iv = 0xA65959A600000000 + len(plaintext) |
| 80 plaintext += "\0" * (8 - len(plaintext) % 8) |
| 81 return aes_wrap_key(kek, plaintext, iv) |
| 82 |
| 83 |
| 84 def test(): |
| 85 #test vector from RFC 3394 |
| 86 import binascii |
| 87 |
| 88 KEK = binascii.unhexlify("000102030405060708090A0B0C0D0E0F") |
| 89 CIPHER = binascii.unhexlify( |
| 90 "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5") |
| 91 PLAIN = binascii.unhexlify("00112233445566778899AABBCCDDEEFF") |
| 92 assert aes_unwrap_key(KEK, CIPHER) == PLAIN |
| 93 assert aes_wrap_key(KEK, PLAIN) == CIPHER |
OLD | NEW |