From 0cb804065e0d1caf440acb0e33c58ad51e4b6f68 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 12:24:35 +0000 Subject: [PATCH 1/3] Release the GIL during core EC and RSA cryptographic operations Wrap CPU-intensive OpenSSL calls in py.detach() to release the GIL, allowing other Python threads to run during cryptographic operations. Operations covered: - EC key generation, ECDH key exchange, ECDSA sign/verify - RSA key generation, sign/verify, encrypt/decrypt https://claude.ai/code/session_01TbN4mmRwmt1boDWU8Gwb5E --- src/rust/src/backend/ec.rs | 29 +++++++++++++++-------- src/rust/src/backend/rsa.rs | 46 +++++++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/rust/src/backend/ec.rs b/src/rust/src/backend/ec.rs index 54d1cc74c3c8..da9fe847524f 100644 --- a/src/rust/src/backend/ec.rs +++ b/src/rust/src/backend/ec.rs @@ -149,10 +149,13 @@ fn generate_private_key( let _ = backend; let ossl_curve = curve_from_py_curve(py, curve)?; - let key = openssl::ec::EcKey::generate(&ossl_curve)?; + let pkey = py.detach(|| { + let key = openssl::ec::EcKey::generate(&ossl_curve)?; + openssl::pkey::PKey::from_ec_key(key) + })?; Ok(ECPrivateKey { - pkey: openssl::pkey::PKey::from_ec_key(key)?, + pkey, curve: py_curve_from_curve(py, &ossl_curve)?.into(), }) } @@ -248,9 +251,11 @@ impl ECPrivateKey { let len = deriver.len()?; Ok(pyo3::types::PyBytes::new_with(py, len, |b| { - let n = deriver.derive(b).map_err(|_| { - pyo3::exceptions::PyValueError::new_err("Error computing shared key.") - })?; + let n = py + .detach(|| deriver.derive(b)) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err("Error computing shared key.") + })?; assert_eq!(n, b.len()); Ok(()) })?) @@ -302,8 +307,12 @@ impl ECPrivateKey { // `PyBytes::new_with` because the exact length of the signature isn't // easily known a priori (if `r` or `s` has a leading 0, the signature // will be a byte or two shorter than the maximum possible length). - let mut sig = vec![]; - signer.sign_to_vec(data.as_bytes(), &mut sig)?; + let data_bytes = data.as_bytes(); + let sig = py.detach(|| { + let mut sig = vec![]; + signer.sign_to_vec(data_bytes, &mut sig)?; + Ok::<_, openssl::error::ErrorStack>(sig) + })?; Ok(pyo3::types::PyBytes::new(py, &sig)) } @@ -423,9 +432,9 @@ impl ECPublicKey { let mut verifier = openssl::pkey_ctx::PkeyCtx::new(&self.pkey)?; verifier.verify_init()?; - let valid = verifier - .verify(data.as_bytes(), signature.as_bytes()) - .unwrap_or(false); + let data_bytes = data.as_bytes(); + let sig_bytes = signature.as_bytes(); + let valid = py.detach(|| verifier.verify(data_bytes, sig_bytes).unwrap_or(false)); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), diff --git a/src/rust/src/backend/rsa.rs b/src/rust/src/backend/rsa.rs index 0302de084130..6db350eabaae 100644 --- a/src/rust/src/backend/rsa.rs +++ b/src/rust/src/backend/rsa.rs @@ -63,10 +63,16 @@ pub(crate) fn public_key_from_pkey( } #[pyo3::pyfunction] -fn generate_private_key(public_exponent: u32, key_size: u32) -> CryptographyResult { +fn generate_private_key( + py: pyo3::Python<'_>, + public_exponent: u32, + key_size: u32, +) -> CryptographyResult { let e = openssl::bn::BigNum::from_u32(public_exponent)?; - let rsa = openssl::rsa::Rsa::generate_with_e(key_size, &e)?; - let pkey = openssl::pkey::PKey::from_rsa(rsa)?; + let pkey = py.detach(|| { + let rsa = openssl::rsa::Rsa::generate_with_e(key_size, &e)?; + openssl::pkey::PKey::from_rsa(rsa) + })?; Ok(RsaPrivateKey { pkey }) } @@ -307,13 +313,16 @@ impl RsaPrivateKey { })?; setup_signature_ctx(py, &mut ctx, padding, &algorithm, self.pkey.size(), true)?; - let length = ctx.sign(data.as_bytes(), None)?; + let data_bytes = data.as_bytes(); + let length = ctx.sign(data_bytes, None)?; Ok(pyo3::types::PyBytes::new_with(py, length, |b| { - let length = ctx.sign(data.as_bytes(), Some(b)).map_err(|_| { - pyo3::exceptions::PyValueError::new_err( - "Digest or salt length too long for key size. Use a larger key or shorter salt length if you are specifying a PSS salt", - ) - })?; + let length = py + .detach(|| ctx.sign(data_bytes, Some(b))) + .map_err(|_| { + pyo3::exceptions::PyValueError::new_err( + "Digest or salt length too long for key size. Use a larger key or shorter salt length if you are specifying a PSS salt", + ) + })?; assert_eq!(length, b.len()); Ok(()) })?.into_any()) @@ -351,9 +360,12 @@ impl RsaPrivateKey { // // Once OpenSSL 3.2.0 is out, this can be simplified, as OpenSSL will // have its own mitigations for Bleichenbacher's attack. - let length = ctx.decrypt(ciphertext, None).unwrap(); - let mut plaintext = vec![0; length]; - let result = ctx.decrypt(ciphertext, Some(&mut plaintext)); + let (result, plaintext, length) = py.detach(|| { + let length = ctx.decrypt(ciphertext, None).unwrap(); + let mut plaintext = vec![0; length]; + let result = ctx.decrypt(ciphertext, Some(&mut plaintext)); + (result, plaintext, length) + }); let py_result = pyo3::types::PyBytes::new(py, &plaintext[..*result.as_ref().unwrap_or(&length)]); @@ -464,9 +476,9 @@ impl RsaPublicKey { ctx.verify_init()?; setup_signature_ctx(py, &mut ctx, padding, &algorithm, self.pkey.size(), false)?; - let valid = ctx - .verify(data.as_bytes(), signature.as_bytes()) - .unwrap_or(false); + let data_bytes = data.as_bytes(); + let sig_bytes = signature.as_bytes(); + let valid = py.detach(|| ctx.verify(data_bytes, sig_bytes).unwrap_or(false)); if !valid { return Err(CryptographyError::from( exceptions::InvalidSignature::new_err(()), @@ -489,8 +501,8 @@ impl RsaPublicKey { let length = ctx.encrypt(plaintext, None)?; Ok(pyo3::types::PyBytes::new_with(py, length, |b| { - let length = ctx - .encrypt(plaintext, Some(b)) + let length = py + .detach(|| ctx.encrypt(plaintext, Some(b))) .map_err(|_| pyo3::exceptions::PyValueError::new_err("Encryption failed"))?; assert_eq!(length, b.len()); Ok(()) From 79657ca75f412db7d83a85174408a75e65a24ab6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 12:40:00 +0000 Subject: [PATCH 2/3] Apply cargo fmt formatting fix https://claude.ai/code/session_01TbN4mmRwmt1boDWU8Gwb5E --- src/rust/src/backend/ec.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/rust/src/backend/ec.rs b/src/rust/src/backend/ec.rs index da9fe847524f..2dd595c493d1 100644 --- a/src/rust/src/backend/ec.rs +++ b/src/rust/src/backend/ec.rs @@ -251,11 +251,9 @@ impl ECPrivateKey { let len = deriver.len()?; Ok(pyo3::types::PyBytes::new_with(py, len, |b| { - let n = py - .detach(|| deriver.derive(b)) - .map_err(|_| { - pyo3::exceptions::PyValueError::new_err("Error computing shared key.") - })?; + let n = py.detach(|| deriver.derive(b)).map_err(|_| { + pyo3::exceptions::PyValueError::new_err("Error computing shared key.") + })?; assert_eq!(n, b.len()); Ok(()) })?) From 824a0aac0e4eadb8f89358c3e1b8199eb4cb8398 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 14:07:07 +0000 Subject: [PATCH 3/3] Remove GIL release from EC key generation EC key generation is fast enough that releasing the GIL adds unnecessary overhead. https://claude.ai/code/session_01TbN4mmRwmt1boDWU8Gwb5E --- src/rust/src/backend/ec.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/rust/src/backend/ec.rs b/src/rust/src/backend/ec.rs index 2dd595c493d1..e5b427033e73 100644 --- a/src/rust/src/backend/ec.rs +++ b/src/rust/src/backend/ec.rs @@ -149,10 +149,8 @@ fn generate_private_key( let _ = backend; let ossl_curve = curve_from_py_curve(py, curve)?; - let pkey = py.detach(|| { - let key = openssl::ec::EcKey::generate(&ossl_curve)?; - openssl::pkey::PKey::from_ec_key(key) - })?; + let key = openssl::ec::EcKey::generate(&ossl_curve)?; + let pkey = openssl::pkey::PKey::from_ec_key(key)?; Ok(ECPrivateKey { pkey,