From bc178ef081ac623a3d7c44093ac84e62182ca1bf Mon Sep 17 00:00:00 2001 From: 007bsd <22483432+007bsd@users.noreply.github.com> Date: Fri, 6 Nov 2026 01:03:39 +0200 Subject: [PATCH] Reject encrypted PEM DEK-Info IVs of the wrong length A legacy encrypted PEM whose DEK-Info IV was 8-15 bytes long for an AES cipher passed the existing 8-byte check (needed for the key derivation salt) and then hit an assertion in rust-openssl, raising PanicException instead of ValueError. Check that the IV length matches the cipher's IV length before using it. --- CHANGELOG.rst | 4 ++++ src/rust/cryptography-key-parsing/src/pem.rs | 8 +++---- src/rust/src/error.rs | 8 ++++--- tests/hazmat/primitives/test_serialization.py | 21 +++++++++++++++++++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6ea571f6a9f8..f0c7c9082f05 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -31,6 +31,10 @@ Changelog curve's order, matching :func:`~cryptography.hazmat.primitives.asymmetric.ec.derive_private_key` and the private key loading functions. +* :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key` + now raises ``ValueError`` for an encrypted traditional PEM whose + ``DEK-Info`` IV does not match the cipher's IV length. Previously, some + lengths raised ``PanicException``. * Added :class:`~cryptography.hazmat.primitives.serialization.MLDSA44Ed25519PrivateKey` and diff --git a/src/rust/cryptography-key-parsing/src/pem.rs b/src/rust/cryptography-key-parsing/src/pem.rs index 26d7c9ce541d..cc522820201a 100644 --- a/src/rust/cryptography-key-parsing/src/pem.rs +++ b/src/rust/cryptography-key-parsing/src/pem.rs @@ -44,14 +44,14 @@ pub fn decrypt_pem<'a>( let iv = cryptography_crypto::encoding::hex_decode(iv) .ok_or(KeyParsingError::PemInvalidIv)?; + if Some(iv.len()) != cipher.iv_len() { + return Err(KeyParsingError::PemInvalidIv); + } let key = cryptography_crypto::pbkdf1::openssl_kdf( openssl::hash::MessageDigest::md5(), password, - iv.get(..8) - .ok_or(KeyParsingError::PemInvalidIv)? - .try_into() - .unwrap(), + iv[..8].try_into().unwrap(), cipher.key_len(), ) .map_err(|_| KeyParsingError::PemUnableToDeriveKey)?; diff --git a/src/rust/src/error.rs b/src/rust/src/error.rs index cd7c85063201..7818e89777cc 100644 --- a/src/rust/src/error.rs +++ b/src/rust/src/error.rs @@ -122,9 +122,11 @@ impl From for CryptographyError { "Encrypted PEM's DEK-Info header is not valid.", )) } - cryptography_key_parsing::KeyParsingError::PemInvalidIv => CryptographyError::Py( - pyo3::exceptions::PyValueError::new_err("DEK-Info IV is not valid hex"), - ), + cryptography_key_parsing::KeyParsingError::PemInvalidIv => { + CryptographyError::Py(pyo3::exceptions::PyValueError::new_err( + "DEK-Info IV is not valid hex or has the wrong length", + )) + } cryptography_key_parsing::KeyParsingError::PemUnableToDeriveKey => { CryptographyError::Py(pyo3::exceptions::PyValueError::new_err( "Unable to derive key from password (are you in FIPS mode?)", diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 6c280c10acc2..7ae7d87b2f33 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -1524,6 +1524,27 @@ def test_pem_encryption_short_iv(self): with pytest.raises(ValueError): load_pem_private_key(data, password=b"password") + @pytest.mark.parametrize( + ("cipher", "iv_len"), + [ + ("AES-128-CBC", 8), + ("AES-128-CBC", 15), + ("AES-256-CBC", 17), + ("DES-EDE3-CBC", 9), + ], + ) + def test_pem_encryption_wrong_iv_length(self, cipher, iv_len): + data = textwrap.dedent(f"""\ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: {cipher},{"00" * iv_len} + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + -----END RSA PRIVATE KEY----- + """).encode() + with pytest.raises(ValueError): + load_pem_private_key(data, password=b"password") + def test_pkcs8_key_with_wrong_pem_delimiter(self): data = load_vectors_from_file( os.path.join(