Skip to content

AES-KWP allows wrapping empty key_to_wrap, inconsistent with RFC 5649 #14664

@Mrmaxmeier

Description

@Mrmaxmeier

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 🙂

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions