Hi,
while testing the pyca/cryptography library I noticed that aes_key_wrap_with_padding allows wrapping an empty key. The resulting wrapped key is invalid and is rejected by aes_key_unwrap_with_padding:
>>> import cryptography; cryptography.__version__
'46.0.5'
>>> from cryptography.hazmat.primitives.keywrap import *
>>> kek = b"A"*16
>>> wrapped = aes_key_wrap_with_padding(kek, b"")
>>> wrapped.hex()
'a65959a600000000'
>>> aes_key_unwrap_with_padding(kek, wrapped)
Traceback (most recent call last):
File "<python-input>", line 1, in <module>
aes_key_unwrap_with_padding(kek, wrapped)
~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
File ".../cryptography/hazmat/primitives/keywrap.py", line 112, in aes_key_unwrap_with_padding
raise InvalidUnwrap("Must be at least 16 bytes")
cryptography.hazmat.primitives.keywrap.InvalidUnwrap: Must be at least 16 bytes
RFC 5649 specifies that »input data may be as short as one octet« in the introduction and also mentions »the plaintext consists of between one and 2^32 octets« in Section 4.1.
The current implementation in pyca/cryptography doesn't check for this:
|
def aes_key_wrap_with_padding( |
|
wrapping_key: bytes, |
|
key_to_wrap: bytes, |
|
backend: typing.Any = None, |
|
) -> bytes: |
|
if len(wrapping_key) not in [16, 24, 32]: |
|
raise ValueError("The wrapping key must be a valid AES key length") |
|
|
|
aiv = b"\xa6\x59\x59\xa6" + len(key_to_wrap).to_bytes( |
|
length=4, byteorder="big" |
|
) |
|
# pad the key to wrap if necessary |
|
pad = (8 - (len(key_to_wrap) % 8)) % 8 |
|
key_to_wrap = key_to_wrap + b"\x00" * pad |
|
if len(key_to_wrap) == 8: |
|
# RFC 5649 - 4.1 - exactly 8 octets after padding |
|
encryptor = Cipher(AES(wrapping_key), ECB()).encryptor() |
|
b = encryptor.update(aiv + key_to_wrap) |
|
assert encryptor.finalize() == b"" |
|
return b |
|
else: |
|
r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)] |
|
return _wrap_core(wrapping_key, aiv, r) |
When called with key_to_wrap=b"", aes_key_wrap_with_padding ends up calling _wrap_core with r=[], which then just returns the Alternative IV. The unwrapping function correctly requires two AES blocks of input.
Other implementations like OpenSSL's wrap128.c do check for this and reject empty plaintexts:
https://github.com/openssl/openssl/blob/9e26ae32a757178d6862e0951c76615fe10a3d1d/crypto/modes/wrap128.c#L208-L210
Notably, this issue only affects the padded variant of AES key wrapping. It also doesn't seem like a security vulnerability to me, as the edge case doesn't reveal the KEK and the discrepancy between wrapping succeeding and unwrapping failing doesn't seem like it could cause issues in real-world use to me.
I'll send a PR to fix 🙂
Hi,
while testing the pyca/cryptography library I noticed that
aes_key_wrap_with_paddingallows wrapping an empty key. The resulting wrapped key is invalid and is rejected byaes_key_unwrap_with_padding:RFC 5649 specifies that »input data may be as short as one octet« in the introduction and also mentions »the plaintext consists of between one and 2^32 octets« in Section 4.1.
The current implementation in pyca/cryptography doesn't check for this:
cryptography/src/cryptography/hazmat/primitives/keywrap.py
Lines 81 to 103 in cfe93c5
When called with
key_to_wrap=b"",aes_key_wrap_with_paddingends up calling_wrap_corewithr=[], which then just returns the Alternative IV. The unwrapping function correctly requires two AES blocks of input.Other implementations like OpenSSL's wrap128.c do check for this and reject empty plaintexts:
https://github.com/openssl/openssl/blob/9e26ae32a757178d6862e0951c76615fe10a3d1d/crypto/modes/wrap128.c#L208-L210
Notably, this issue only affects the padded variant of AES key wrapping. It also doesn't seem like a security vulnerability to me, as the edge case doesn't reveal the KEK and the discrepancy between wrapping succeeding and unwrapping failing doesn't seem like it could cause issues in real-world use to me.
I'll send a PR to fix 🙂