Skip to content

Commit 5038495

Browse files
alexreaperhulk
andauthored
backports for 45.0.3 release (#12979)
* Rename PBES1 types to better match their origins in the spec (#12976) PBEParams is defined in RFC 8018 as going with PBES1, but then its also used by PKCS#12 ciphers in RFC 7292. * add vector and test for longer salt length in PBE (#12977) * add vector and test for longer salt length in PBE * Update docs/development/test-vectors.rst Co-authored-by: Alex Gaynor <[email protected]> --------- Co-authored-by: Alex Gaynor <[email protected]> * fixes #12949 -- added support for decrypting des-cbc-md5 keys (#12978) * add missing 45.0.0 changelog entry (#12946) * add missing 45.0.0 changelog entry @Yossarian pointed out that we missed this breaking change #12110 * Update CHANGELOG.rst * oops * changelog + version bump * add pbeWithMD5AndDES-CBC test vector (#12956) * typo * fix documentation for the decrepit algorithms module (#12953) --------- Co-authored-by: Paul Kehrer <[email protected]>
1 parent f81c075 commit 5038495

File tree

18 files changed

+262
-30
lines changed

18 files changed

+262
-30
lines changed

CHANGELOG.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
Changelog
22
=========
33

4+
.. _v45-0-3:
5+
6+
45.0.3 - 2025-05-25
7+
~~~~~~~~~~~~~~~~~~~
8+
9+
* Fixed decrypting PKCS#8 files encrypted with long salts (this impacts keys
10+
encrypted by Bouncy Castle).
11+
* Fixed decrypting PKCS#8 files encrypted with DES-CBC-MD5. While wildly
12+
insecure, this remains prevalent.
13+
414
.. _v45-0-2:
515

616
45.0.2 - 2025-05-17
@@ -37,6 +47,24 @@ Changelog
3747
provided (previously no exception was raised), and raises a ``TypeError`` if
3848
the key is encrypted but no password is provided (previously a ``ValueError``
3949
was raised).
50+
* Added ``__copy__`` to the
51+
:class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`,
52+
:class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`,
53+
:class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`,
54+
:class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`,
55+
:class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`,
56+
:class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey`,
57+
:class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey`,
58+
:class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey`,
59+
:class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`,
60+
:class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey`,
61+
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`,
62+
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`,
63+
:class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`,
64+
:class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`,
65+
:class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`, and
66+
:class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`
67+
abstract base classes.
4068
* We significantly refactored how private key loading (
4169
:func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key`
4270
and

docs/development/test-vectors.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,11 @@ Custom asymmetric vectors
285285
RSA key in an encrypted PEM with a malformed IV (not valid hex).
286286
* ``asymmetric/Traditional_OpenSSL_Serialization/key1-short-iv.pem`` - An
287287
RSA key in an encrypted PEM with an IV that's too short (less than 8 bytes).
288+
* ``asymmetric/PKCS8/rsa-pbewithmd5anddescbc.pem`` - A PKCS8 encoded RSA key
289+
encrypted using the ``pbeWithMD5AndDES-CBC`` algorithm with the password
290+
``hunter2``.
291+
* ``asymmetric/PKCS8/rsa-pbe-3des-long-salt.pem`` - A PKCS8 encoded RSA key
292+
encrypted with a 20 byte salt with the password ``password``.
288293

289294
Key exchange
290295
~~~~~~~~~~~~

docs/hazmat/decrepit/ciphers.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Decrepit Symmetric algorithms
55
=============================
66

7-
.. module:: cryptography.hazmat.decrepit.ciphers
7+
.. module:: cryptography.hazmat.decrepit.ciphers.algorithms
88

99
This module contains decrepit symmetric encryption algorithms. These
1010
are algorithms that should not be used unless necessary for backwards

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ build-backend = "maturin"
1515

1616
[project]
1717
name = "cryptography"
18-
version = "45.0.2"
18+
version = "45.0.3"
1919
authors = [
2020
{ name = "The Python Cryptographic Authority and individual contributors", email = "[email protected]" },
2121
]
@@ -65,7 +65,7 @@ ssh = ["bcrypt >=3.1.5"]
6565
# All the following are used for our own testing.
6666
nox = ["nox >=2024.04.15", "nox[uv] >=2024.03.02; python_version >= '3.8'"]
6767
test = [
68-
"cryptography_vectors==45.0.2",
68+
"cryptography_vectors==45.0.3",
6969
"pytest >=7.4.0",
7070
"pytest-benchmark >=4.0",
7171
"pytest-cov >=2.10.1",

src/cryptography/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"__version__",
1111
]
1212

13-
__version__ = "45.0.2"
13+
__version__ = "45.0.3"
1414

1515

1616
__author__ = "The Python Cryptographic Authority and individual contributors"

src/cryptography/hazmat/decrepit/ciphers/algorithms.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ def key_size(self) -> int:
4040
return len(self.key) * 8
4141

4242

43+
# Not actually supported, marker for tests
44+
class _DES:
45+
key_size = 64
46+
47+
4348
class Blowfish(BlockCipherAlgorithm):
4449
name = "Blowfish"
4550
block_size = 64

src/rust/cryptography-crypto/src/pbkdf1.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,37 @@ pub fn openssl_kdf(
3030
Ok(key)
3131
}
3232

33+
/// PBKDF1 as defined in RFC 2898 for PKCS#5 v1.5 PBE algorithms
34+
pub fn pbkdf1(
35+
hash_alg: openssl::hash::MessageDigest,
36+
password: &[u8],
37+
salt: [u8; 8],
38+
iterations: u64,
39+
length: usize,
40+
) -> Result<Vec<u8>, openssl::error::ErrorStack> {
41+
if length > hash_alg.size() || iterations == 0 {
42+
return Err(openssl::error::ErrorStack::get());
43+
}
44+
45+
let mut h = openssl::hash::Hasher::new(hash_alg)?;
46+
h.update(password)?;
47+
h.update(&salt)?;
48+
let mut t = h.finish()?;
49+
50+
// Apply hash function for specified iterations
51+
for _ in 1..iterations {
52+
let mut h = openssl::hash::Hasher::new(hash_alg)?;
53+
h.update(&t)?;
54+
t = h.finish()?;
55+
}
56+
57+
// Return the first `length` bytes
58+
Ok(t[..length].to_vec())
59+
}
60+
3361
#[cfg(test)]
3462
mod tests {
35-
use super::openssl_kdf;
63+
use super::{openssl_kdf, pbkdf1};
3664

3765
#[test]
3866
fn test_openssl_kdf() {
@@ -98,4 +126,10 @@ mod tests {
98126
assert_eq!(key, expected);
99127
}
100128
}
129+
130+
#[test]
131+
fn test_pbkdf1() {
132+
assert!(pbkdf1(openssl::hash::MessageDigest::md5(), b"abc", [0; 8], 1, 20).is_err());
133+
assert!(pbkdf1(openssl::hash::MessageDigest::md5(), b"abc", [0; 8], 0, 8).is_err());
134+
}
101135
}

src/rust/cryptography-key-parsing/src/pkcs8.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
// for complete details.
44

5-
use cryptography_x509::common::{AlgorithmIdentifier, AlgorithmParameters, PBES1Params};
5+
use cryptography_x509::common::{
6+
AlgorithmIdentifier, AlgorithmParameters, PbeParams, Pkcs12PbeParams,
7+
};
68
use cryptography_x509::csr::Attributes;
79
use cryptography_x509::pkcs8::EncryptedPrivateKeyInfo;
810

@@ -122,27 +124,27 @@ pub fn parse_private_key(
122124
}
123125
}
124126

125-
fn pbes1_decrypt(
127+
fn pkcs12_pbe_decrypt(
126128
data: &[u8],
127129
password: &[u8],
128130
cipher: openssl::symm::Cipher,
129131
hash: openssl::hash::MessageDigest,
130-
params: &PBES1Params,
132+
params: &Pkcs12PbeParams<'_>,
131133
) -> KeyParsingResult<Vec<u8>> {
132134
let Ok(password) = std::str::from_utf8(password) else {
133135
return Err(KeyParsingError::IncorrectPassword);
134136
};
135137
let key = cryptography_crypto::pkcs12::kdf(
136138
password,
137-
&params.salt,
139+
params.salt,
138140
cryptography_crypto::pkcs12::KDF_ENCRYPTION_KEY_ID,
139141
params.iterations,
140142
cipher.key_len(),
141143
hash,
142144
)?;
143145
let iv = cryptography_crypto::pkcs12::kdf(
144146
password,
145-
&params.salt,
147+
params.salt,
146148
cryptography_crypto::pkcs12::KDF_IV_ID,
147149
params.iterations,
148150
cipher.block_size(),
@@ -153,6 +155,31 @@ fn pbes1_decrypt(
153155
.map_err(|_| KeyParsingError::IncorrectPassword)
154156
}
155157

158+
fn pkcs5_pbe_decrypt(
159+
data: &[u8],
160+
password: &[u8],
161+
cipher: openssl::symm::Cipher,
162+
hash: openssl::hash::MessageDigest,
163+
params: &PbeParams,
164+
) -> KeyParsingResult<Vec<u8>> {
165+
// PKCS#5 v1.5 uses PBKDF1 with iteration count
166+
// For PKCS#5 PBE, we need key + IV length
167+
let key_iv_len = cipher.key_len() + cipher.iv_len().unwrap();
168+
let key_iv = cryptography_crypto::pbkdf1::pbkdf1(
169+
hash,
170+
password,
171+
params.salt,
172+
params.iterations,
173+
key_iv_len,
174+
)?;
175+
176+
let key = &key_iv[..cipher.key_len()];
177+
let iv = &key_iv[cipher.key_len()..];
178+
179+
openssl::symm::decrypt(cipher, key, Some(iv), data)
180+
.map_err(|_| KeyParsingError::IncorrectPassword)
181+
}
182+
156183
pub fn parse_encrypted_private_key(
157184
data: &[u8],
158185
password: Option<&[u8]>,
@@ -164,15 +191,22 @@ pub fn parse_encrypted_private_key(
164191
};
165192

166193
let plaintext = match epki.encryption_algorithm.params {
167-
AlgorithmParameters::Pbes1WithShaAnd3KeyTripleDesCbc(params) => pbes1_decrypt(
194+
AlgorithmParameters::PbeWithMd5AndDesCbc(params) => pkcs5_pbe_decrypt(
195+
epki.encrypted_data,
196+
password,
197+
openssl::symm::Cipher::des_cbc(),
198+
openssl::hash::MessageDigest::md5(),
199+
&params,
200+
)?,
201+
AlgorithmParameters::PbeWithShaAnd3KeyTripleDesCbc(params) => pkcs12_pbe_decrypt(
168202
epki.encrypted_data,
169203
password,
170204
openssl::symm::Cipher::des_ede3_cbc(),
171205
openssl::hash::MessageDigest::sha1(),
172206
&params,
173207
)?,
174208
#[cfg(not(CRYPTOGRAPHY_OSSLCONF = "OPENSSL_NO_RC2"))]
175-
AlgorithmParameters::Pbe1WithShaAnd40BitRc2Cbc(params) => pbes1_decrypt(
209+
AlgorithmParameters::PbeWithShaAnd40BitRc2Cbc(params) => pkcs12_pbe_decrypt(
176210
epki.encrypted_data,
177211
password,
178212
openssl::symm::Cipher::rc2_40_cbc(),

src/rust/cryptography-x509/src/common.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,12 @@ pub enum AlgorithmParameters<'a> {
165165
#[defined_by(oid::RC2_CBC)]
166166
Rc2Cbc(Rc2CbcParams),
167167

168-
#[defined_by(oid::PBES1_WITH_SHA_AND_3KEY_TRIPLEDES_CBC)]
169-
Pbes1WithShaAnd3KeyTripleDesCbc(PBES1Params),
170-
#[defined_by(oid::PBES1_WITH_SHA_AND_40_BIT_RC2_CBC)]
171-
Pbe1WithShaAnd40BitRc2Cbc(PBES1Params),
168+
#[defined_by(oid::PBE_WITH_MD5_AND_DES_CBC)]
169+
PbeWithMd5AndDesCbc(PbeParams),
170+
#[defined_by(oid::PBE_WITH_SHA_AND_3KEY_TRIPLEDES_CBC)]
171+
PbeWithShaAnd3KeyTripleDesCbc(Pkcs12PbeParams<'a>),
172+
#[defined_by(oid::PBE_WITH_SHA_AND_40_BIT_RC2_CBC)]
173+
PbeWithShaAnd40BitRc2Cbc(Pkcs12PbeParams<'a>),
172174

173175
#[default]
174176
Other(asn1::ObjectIdentifier, Option<asn1::Tlv<'a>>),
@@ -529,12 +531,20 @@ pub struct ScryptParams<'a> {
529531
pub key_length: Option<u32>,
530532
}
531533

534+
// RFC 8018 Appendix A.3
532535
#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)]
533-
pub struct PBES1Params {
536+
pub struct PbeParams {
534537
pub salt: [u8; 8],
535538
pub iterations: u64,
536539
}
537540

541+
// From RFC 7202 Appendix C
542+
#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)]
543+
pub struct Pkcs12PbeParams<'a> {
544+
pub salt: &'a [u8],
545+
pub iterations: u64,
546+
}
547+
538548
#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Eq, Hash, Clone, Debug)]
539549
pub struct Rc2CbcParams {
540550
pub version: Option<u32>,

src/rust/cryptography-x509/src/oid.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,12 @@ pub const EKU_CERTIFICATE_TRANSPARENCY_OID: asn1::ObjectIdentifier =
152152

153153
pub const PBES2_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 13);
154154
pub const PBKDF2_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 12);
155+
pub const PBE_WITH_MD5_AND_DES_CBC: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 1, 5, 3);
155156
pub const SCRYPT_OID: asn1::ObjectIdentifier = asn1::oid!(1, 3, 6, 1, 4, 1, 11591, 4, 11);
156157

157-
pub const PBES1_WITH_SHA_AND_3KEY_TRIPLEDES_CBC: asn1::ObjectIdentifier =
158+
pub const PBE_WITH_SHA_AND_3KEY_TRIPLEDES_CBC: asn1::ObjectIdentifier =
158159
asn1::oid!(1, 2, 840, 113549, 1, 12, 1, 3);
159-
pub const PBES1_WITH_SHA_AND_40_BIT_RC2_CBC: asn1::ObjectIdentifier =
160+
pub const PBE_WITH_SHA_AND_40_BIT_RC2_CBC: asn1::ObjectIdentifier =
160161
asn1::oid!(1, 2, 840, 113549, 1, 12, 1, 6);
161162

162163
pub const AES_128_CBC_OID: asn1::ObjectIdentifier = asn1::oid!(2, 16, 840, 1, 101, 3, 4, 1, 2);

0 commit comments

Comments
 (0)