What is the point of the “AES Key Wrap” algorithm prescribed for use with CMS-like contexts in IETF RFC 3394?
Just look at the algorithm (tacked on to the end of this post), informally, it smells a lot like a "home-brew" cryptosystem. And it's only used in a few, limited contexts.
Checking the document for any design rationale, I saw at the bottom this note:
Most of the text in this document is taken from AES-WRAP. The authors of that document are responsible for the development of the AES key wrap algorithm.
I already checked the upstream document, too, for rationale. The closest thing I could find is the following excerpt; its authors didn't cite anything except the raw definitions of "the AES codebook".
It is sufficient to approximate an ideal pseudorandom permutation to the degree that exploitation of undesirable phenomena is as unlikely as guessing the AES engine key. This key wrap algorithm needs to provide ample security to protect keys in the context of a prudently designed key management architecture.
Are there any reasons (other than legacy system compatibility) to use "AES-WRAP" as a key-encryption algorithm, rather than a general-purpose mode like GCM?
def aes_wrap(kek, key_towrap, iv=b'\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6'): "https://datatracker.ietf.org/doc/html/rfc3394.html#section-2.2.1" if len(key_towrap) % 8 != 0: raise NotImplementedError("IETF RFC 5649") n = len(key_towrap)//8 A = iv R = bytearray(key_towrap) c = AES.new(kek, AES.MODE_ECB) ctr = 0 for j in range(6): for i in range(n): ctr += 1 B = c.encrypt(A + R[i*8:i*8+8]) A = bxor(B[:8], ctr.to_bytes(8, 'big')) R[i*8:i*8+8] = B[8:] return A + R def aes_unwrap(kek, wrapped_key, iv=b'\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6'): "https://datatracker.ietf.org/doc/html/rfc3394.html#section-2.2.2" n = len(wrapped_key)//8 - 1 if len(wrapped_key) % 8 != 0: raise NotImplementedError("IETF RFC 5649") R = bytearray(wrapped_key[8:]) A = bytes(wrapped_key[:8]) c = AES.new(kek, AES.MODE_ECB) ctr = 6*n for j in reversed(range(6)): for i in reversed(range(n)): B = c.decrypt(bxor(A, ctr.to_bytes(8, 'big')) + R[i*8:i*8+8]) A = B[:8] R[i*8:i*8+8] = B[8:] ctr -= 1 if A != iv: raise ValueError("integrity check failed") return bytes(R) from operator import xor as _xor from itertools import starmap as _starmap def bxor(a, b): return a.__class__(_starmap(_xor, zip(a, b, strict=True)))