Index: third_party/google-endpoints/jwkest/aes_key_wrap.py |
diff --git a/third_party/google-endpoints/jwkest/aes_key_wrap.py b/third_party/google-endpoints/jwkest/aes_key_wrap.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..037f0fb0813c6e3b664dcae97fd5c059c2f5bce7 |
--- /dev/null |
+++ b/third_party/google-endpoints/jwkest/aes_key_wrap.py |
@@ -0,0 +1,93 @@ |
+""" |
+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. |
+""" |
+from __future__ import division |
+ |
+try: |
+ from builtins import hex |
+ from builtins import range |
+except ImportError: |
+ pass |
+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 b"".join(r[1:]), a |
+ |
+ |
+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 |
+ |
+ |
+def aes_unwrap_key_withpad(kek, wrapped): |
+ 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) + b''.join(r[1:]) |
+ |
+ |
+def aes_wrap_key_withpad(kek, plaintext): |
+ iv = 0xA65959A600000000 + len(plaintext) |
+ plaintext += "\0" * (8 - len(plaintext) % 8) |
+ 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 |