I am making a Rust based library for Python which implements different cryptographic protocols, and I'm having trouble working with EphemeralSecret, as I keep getting the error log:
"no method named to_bytes found for struct EphemeralSecret in the current scope
method not found in EphemeralSecret"
This is being used to create a Rust lib to use it in Python so I can implement different cryptographic protocols and I need to work with the created keys in my Python side of things. I understand I maybe shouldn´t need to have a privateKey serialized for security reasons, but this is my case, so how do I "pass" it? Or should I maybe convert it in another way to work with it and be able to pass it around? I also have the trouble when converting it "back" from python to rust as there is no conversion back to it from bytes. The trouble is in the following section:
// Elliptic Curve Diffie-Hellman
#[pyfunction]
fn generate_ecdh_key() -> PyResult<(Vec<u8>, Vec<u8>)> {
let private_key = EphemeralSecret::random_from_rng(OsRng);
let public_key = PublicKey::from(&private_key);
Ok((private_key.to_bytes().to_vec(), public_key.as_bytes().to_vec()))
}
#[pyfunction]
fn derive_ecdh_shared_key(private_key_bytes: Vec<u8>, server_public_key_bytes: Vec<u8>) -> PyResult<Vec<u8>> {
let private_key = EphemeralSecret::from(private_key_bytes.as_slice().try_into().unwrap());
let server_public_key = PublicKey::from(server_public_key_bytes.as_slice().try_into().unwrap());
let shared_secret = private_key.diffie_hellman(&server_public_key);
Ok(shared_secret.as_bytes().to_vec())
}
It is part of the complete code:
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use x25519_dalek::{EphemeralSecret, PublicKey};
use rand::Rng;
use rand::rngs::OsRng;
use num_bigint::{BigUint, RandBigInt};
use rsa::{RsaPrivateKey, RsaPublicKey, Pkcs1v15Encrypt};
use rsa::pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPrivateKey, EncodeRsaPublicKey};
use rsa::pkcs8::LineEnding;
// Diffie-Hellman
#[pyfunction]
fn generate_dh_key(p: &str, g: &str) -> PyResult<(String, String)> {
let p = BigUint::parse_bytes(p.as_bytes(), 10).ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid p"))?;
let g = BigUint::parse_bytes(g.as_bytes(), 10).ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid g"))?;
let mut rng = OsRng;
let private_key = rng.gen_biguint_below(&p);
let public_key = g.modpow(&private_key, &p);
Ok((private_key.to_str_radix(10), public_key.to_str_radix(10)))
}
#[pyfunction]
fn derive_dh_shared_key(private_key: &str, server_public_key: &str, p: &str) -> PyResult<String> {
let p = BigUint::parse_bytes(p.as_bytes(), 10).ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid p"))?;
let private_key = BigUint::parse_bytes(private_key.as_bytes(), 10).ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid private_key"))?;
let server_public_key = BigUint::parse_bytes(server_public_key.as_bytes(), 10).ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid server_public_key"))?;
let shared_secret = server_public_key.modpow(&private_key, &p);
Ok(shared_secret.to_str_radix(10))
}
// Elliptic Curve Diffie-Hellman
#[pyfunction]
fn generate_ecdh_key() -> PyResult<(Vec<u8>, Vec<u8>)> {
let private_key = EphemeralSecret::random_from_rng(OsRng);
let public_key = PublicKey::from(&private_key);
Ok((private_key.to_bytes().to_vec(), public_key.as_bytes().to_vec()))
}
#[pyfunction]
fn derive_ecdh_shared_key(private_key_bytes: Vec<u8>, server_public_key_bytes: Vec<u8>) -> PyResult<Vec<u8>> {
let private_key = EphemeralSecret::from(private_key_bytes.as_slice().try_into().unwrap());
let server_public_key = PublicKey::from(server_public_key_bytes.as_slice().try_into().unwrap());
let shared_secret = private_key.diffie_hellman(&server_public_key);
Ok(shared_secret.as_bytes().to_vec())
}
// RSA Functions
#[pyfunction]
fn generate_rsa_key() -> (String, String) {
let mut rng = OsRng;
let bits = 2048;
let private_key = RsaPrivateKey::new(&mut rng, bits).unwrap();
let public_key = RsaPublicKey::from(&private_key);
let private_pem = private_key.to_pkcs1_pem(LineEnding::LF).unwrap();
let public_pem = public_key.to_pkcs1_pem(LineEnding::LF).unwrap();
(private_pem.to_string(), public_pem)
}
#[pyfunction]
fn rsa_encrypt(public_key_pem: &str, message: &str) -> Vec<u8> {
let public_key = RsaPublicKey::from_pkcs1_pem(public_key_pem).unwrap();
let mut rng = OsRng;
public_key.encrypt(&mut rng, Pkcs1v15Encrypt, message.as_bytes()).unwrap()
}
#[pyfunction]
fn rsa_decrypt(private_key_pem: &str, encrypted_data: Vec<u8>) -> String {
let private_key = RsaPrivateKey::from_pkcs1_pem(private_key_pem).unwrap();
let decrypted_data = private_key.decrypt(Pkcs1v15Encrypt, &encrypted_data).unwrap();
String::from_utf8(decrypted_data).unwrap()
}
// Swoosh NIKE key generation and exchange
#[pyfunction]
fn swoosh_generate_keys(parameters: (usize, usize, usize)) -> PyResult<(Vec<i8>, Vec<i8>)> {
let (q, _d, n) = parameters;
let mut rng = rand::thread_rng();
let a: Vec<i8> = (0..n * n).map(|_| rng.gen_range(0..q) as i8).collect();
let s: Vec<i8> = (0..n).map(|_| rng.gen_range(-1..=1)).collect();
let e: Vec<i8> = (0..n).map(|_| rng.gen_range(-1..=1)).collect();
let public_key: Vec<i8> = a.chunks(n).zip(&s).map(|(row, &s)| (row.iter().sum::<i8>() + e[s as usize]) % q as i8).collect();
Ok((s, public_key))
}
#[pyfunction]
fn swoosh_derive_shared_key(private_key: Vec<i8>, public_key: Vec<i8>, q: usize) -> PyResult<Vec<i8>> {
let shared_key: Vec<i8> = private_key.iter().zip(&public_key).map(|(&s, &p)| (s * p) % q as i8).collect();
Ok(shared_key)
}
#[pymodule]
fn shadowCrypt(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(generate_dh_key, m)?)?;
m.add_function(wrap_pyfunction!(derive_dh_shared_key, m)?)?;
m.add_function(wrap_pyfunction!(generate_ecdh_key, m)?)?;
m.add_function(wrap_pyfunction!(derive_ecdh_shared_key, m)?)?;
m.add_function(wrap_pyfunction!(generate_rsa_key, m)?)?;
m.add_function(wrap_pyfunction!(rsa_encrypt, m)?)?;
m.add_function(wrap_pyfunction!(rsa_decrypt, m)?)?;
m.add_function(wrap_pyfunction!(swoosh_generate_keys, m)?)?;
m.add_function(wrap_pyfunction!(swoosh_derive_shared_key, m)?)?;
Ok(())
}
EphemeralSecret
does not have a to_bytes
method as Ephemeral Secrets cannot be serialized to Vec
as mentioned in the docs.
This type is identical to the
StaticSecret
type, except that theEphemeralSecret::diffie_hellman
method consumes and then wipes the secret key, and there are no serialization methods defined
To convert to Vec<u8>
you can use StaticSecret
which has a StaticSecret::to_bytes
method and can be used multiple times as well as serialized.
So the final code would be something like
use num_bigint::{BigUint, RandBigInt};
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use rand::rngs::OsRng;
use rand::Rng;
use rsa::pkcs1::{
DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPrivateKey, EncodeRsaPublicKey,
};
use rsa::pkcs8::LineEnding;
use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
use x25519_dalek::{PublicKey, StaticSecret};
// Diffie-Hellman
#[pyfunction]
fn generate_dh_key(p: &str, g: &str) -> PyResult<(String, String)> {
let p = BigUint::parse_bytes(p.as_bytes(), 10)
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid p"))?;
let g = BigUint::parse_bytes(g.as_bytes(), 10)
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid g"))?;
let mut rng = OsRng;
let private_key = rng.gen_biguint_below(&p);
let public_key = g.modpow(&private_key, &p);
Ok((private_key.to_str_radix(10), public_key.to_str_radix(10)))
}
#[pyfunction]
fn derive_dh_shared_key(private_key: &str, server_public_key: &str, p: &str) -> PyResult<String> {
let p = BigUint::parse_bytes(p.as_bytes(), 10)
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid p"))?;
let private_key = BigUint::parse_bytes(private_key.as_bytes(), 10)
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid private_key"))?;
let server_public_key = BigUint::parse_bytes(server_public_key.as_bytes(), 10)
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Invalid server_public_key"))?;
let shared_secret = server_public_key.modpow(&private_key, &p);
Ok(shared_secret.to_str_radix(10))
}
// Elliptic Curve Diffie-Hellman
#[pyfunction]
fn generate_ecdh_key() -> PyResult<(Vec<u8>, Vec<u8>)> {
let private_key = StaticSecret::random_from_rng(OsRng);
let public_key = PublicKey::from(&private_key);
Ok((
private_key.to_bytes().to_vec(),
public_key.as_bytes().to_vec(),
))
}
#[pyfunction]
fn derive_ecdh_shared_key(
private_key_bytes: Vec<u8>,
server_public_key_bytes: Vec<u8>,
) -> PyResult<Vec<u8>> {
let private_key = StaticSecret::from(private_key_bytes.as_slice().try_into().unwrap());
let server_public_key = PublicKey::from(server_public_key_bytes.as_slice().try_into().unwrap());
let shared_secret = private_key.diffie_hellman(&server_public_key);
Ok(shared_secret.as_bytes().to_vec())
}
// RSA Functions
#[pyfunction]
fn generate_rsa_key() -> (String, String) {
let mut rng = OsRng;
let bits = 2048;
let private_key = RsaPrivateKey::new(&mut rng, bits).unwrap();
let public_key = RsaPublicKey::from(&private_key);
let private_pem = private_key.to_pkcs1_pem(LineEnding::LF).unwrap();
let public_pem = public_key.to_pkcs1_pem(LineEnding::LF).unwrap();
(private_pem.to_string(), public_pem)
}
#[pyfunction]
fn rsa_encrypt(public_key_pem: &str, message: &str) -> Vec<u8> {
let public_key = RsaPublicKey::from_pkcs1_pem(public_key_pem).unwrap();
let mut rng = OsRng;
public_key
.encrypt(&mut rng, Pkcs1v15Encrypt, message.as_bytes())
.unwrap()
}
#[pyfunction]
fn rsa_decrypt(private_key_pem: &str, encrypted_data: Vec<u8>) -> String {
let private_key = RsaPrivateKey::from_pkcs1_pem(private_key_pem).unwrap();
let decrypted_data = private_key
.decrypt(Pkcs1v15Encrypt, &encrypted_data)
.unwrap();
String::from_utf8(decrypted_data).unwrap()
}
// Swoosh NIKE key generation and exchange
#[pyfunction]
fn swoosh_generate_keys(parameters: (usize, usize, usize)) -> PyResult<(Vec<i8>, Vec<i8>)> {
let (q, _d, n) = parameters;
let mut rng = rand::thread_rng();
let a: Vec<i8> = (0..n * n).map(|_| rng.gen_range(0..q) as i8).collect();
let s: Vec<i8> = (0..n).map(|_| rng.gen_range(-1..=1)).collect();
let e: Vec<i8> = (0..n).map(|_| rng.gen_range(-1..=1)).collect();
let public_key: Vec<i8> = a
.chunks(n)
.zip(&s)
.map(|(row, &s)| (row.iter().sum::<i8>() + e[s as usize]) % q as i8)
.collect();
Ok((s, public_key))
}
#[pyfunction]
fn swoosh_derive_shared_key(
private_key: Vec<i8>,
public_key: Vec<i8>,
q: usize,
) -> PyResult<Vec<i8>> {
let shared_key: Vec<i8> = private_key
.iter()
.zip(&public_key)
.map(|(&s, &p)| (s * p) % q as i8)
.collect();
Ok(shared_key)
}
#[pymodule]
fn shadow_crypt(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(generate_dh_key, m)?)?;
m.add_function(wrap_pyfunction!(derive_dh_shared_key, m)?)?;
m.add_function(wrap_pyfunction!(generate_ecdh_key, m)?)?;
m.add_function(wrap_pyfunction!(derive_ecdh_shared_key, m)?)?;
m.add_function(wrap_pyfunction!(generate_rsa_key, m)?)?;
m.add_function(wrap_pyfunction!(rsa_encrypt, m)?)?;
m.add_function(wrap_pyfunction!(rsa_decrypt, m)?)?;
m.add_function(wrap_pyfunction!(swoosh_generate_keys, m)?)?;
m.add_function(wrap_pyfunction!(swoosh_derive_shared_key, m)?)?;
Ok(())
}
Note: The Cargo.toml
file needs to be modified as follows:
x25519_dalek = { version = "2.0.1", features = ["static_secrets"] }