use crate::utils::bnclone; use num_bigint::BigInt; use num_bigint::BigUint; use num_bigint::ToBigInt; use openssl::bn::BigNum; use openssl::bn::BigNumContext; use openssl::bn::MsbOption; use openssl::error::ErrorStack; use openssl::sha::sha256; pub struct RsaPublicKey { pub e: BigNum, pub n: BigNum, } pub struct RsaPrivateKey { pub d: BigNum, pub n: BigNum, } fn generate_random_prime(bits: i32) -> Result { let mut p = BigNum::new()?; let mut ctx = BigNumContext::new()?; p.rand(bits, MsbOption::MAYBE_ZERO, true)?; while !p.is_prime_fasttest(10, &mut ctx, true)? { p.add_word(1)?; } Ok(p) } pub fn rsa_gen_keys_with_size( p_bits: i32, q_bits: i32, ) -> Result<(RsaPublicKey, RsaPrivateKey), ErrorStack> { let mut ctx = BigNumContext::new()?; loop { // Generate 2 random primes. let mut p = generate_random_prime(p_bits)?; let mut q = generate_random_prime(q_bits)?; // Let n be p * q. Your RSA math is modulo n. let mut n = BigNum::new()?; n.checked_mul(&p, &q, &mut ctx)?; // This is stupid but I couldn't figure out how to clone a bignum so we do this. // Let et be (p-1)*(q-1) (the "totient"). You need this value only for keygen. let mut et = BigNum::new()?; q.sub_word(1)?; p.sub_word(1)?; et.checked_mul(&p, &q, &mut ctx)?; // Let e be 3. // Compute d = invmod(e, et) let e = BigNum::from_u32(3)?; let d = match invmod(&e, &et) { Ok(i) => i, Err(_) => continue, }; // Your public key is [e, n]. Your private key is [d, n]. return Ok((RsaPublicKey { e, n: bnclone(&n) }, RsaPrivateKey { d, n })); } } pub fn rsa_gen_keys() -> Result<(RsaPublicKey, RsaPrivateKey), ErrorStack> { rsa_gen_keys_with_size(512, 512) } fn extended_gcd(a: &BigUint, b: &BigUint) -> (BigInt, BigInt, BigInt) { // https://brilliant.org/wiki/extended-euclidean-algorithm/ assert!(a < b, "a has to be smaller than b for extended GCD"); let mut a: BigInt = a.clone().into(); let mut b: BigInt = b.clone().into(); let mut x: BigInt = 0_i32.into(); let mut y: BigInt = 1_i32.into(); let mut u: BigInt = 1_i32.into(); let mut v: BigInt = 0_i32.into(); while a != 0_u32.into() { let (q, r) = (&b / &a, &b % &a); let m = &x - &u * &q; let n = &y - &v * &q; b = a; a = r; x = u; y = v; u = m; v = n; } (b, x, y) } pub fn invmod_biguint(a: &BigUint, n: &BigUint) -> Option { let r = extended_gcd(a, n); match r { (_, _, v1) if v1 == 0_i32.into() => None, (_, u1, _) => { let n = n.clone().to_bigint()?; let r = ((&u1 % &n) + &n) % &n; r.to_biguint() } } } pub fn invmod(a: &BigNum, n: &BigNum) -> Result { fn extended_gcd(a: BigNum, b: BigNum) -> Result<(BigNum, BigNum, BigNum), ErrorStack> { // credit: https://www.dcode.fr/extended-gcd // r1 = b, r2 = a, u1 = 0, v1 = 1, u2 = 1, v2 = 0 let mut r1 = b; let mut r2 = a; let mut u1 = BigNum::from_u32(0)?; let mut v1 = BigNum::from_u32(1)?; let mut u2 = BigNum::from_u32(1)?; let mut v2 = BigNum::from_u32(0)?; // while (r2! = 0) do while r2.num_bits() != 0 { // q = r1 รท r2 (integer division) let q = &r1 / &r2; // r3 = r1, u3 = u1, v3 = v1, let r3 = r1; let u3 = u1; let v3 = v1; // r1 = r2, u1 = u2, v1 = v2, r1 = r2; u1 = u2; v1 = v2; // r2 = r3 - q * r2, u2 = u3 - q * u2, v2 = v3 - q * v2 r2 = &r3 - &(&q * &r1); u2 = &u3 - &(&q * &u1); v2 = &v3 - &(&q * &v1); } // return (r1, u1, v1) (r1 natural integer and u1, v1 rational integers) Ok((r1, u1, v1)) } // if v1 == 0 there is no mod_inverse let (_, u1, _v1) = extended_gcd(bnclone(&a), bnclone(&n))?; let r_manual = &(&(&u1 % n) + n) % n; let mut ctx = BigNumContext::new()?; let mut r = BigNum::new()?; r.mod_inverse(&a, &n, &mut ctx)?; assert_eq!(r_manual, r, "invmod implementation incorrect"); Ok(r_manual) } pub fn rsa_padding_add_pkcs1(m: &BigNum, to_len: i32) -> Result { const PKCS_PADDING_SIZE: i32 = 11; let from_len = m.num_bytes(); assert!( from_len + PKCS_PADDING_SIZE <= to_len, "message too long for padding" ); let padding_str_len: usize = (to_len - 3 - from_len).try_into().unwrap(); let mut v = vec![0x0; 3 + padding_str_len]; v[0] = 0x0; v[1] = 0x2; for i in 2..padding_str_len + 2 { v[i] = 0xff; } v[padding_str_len + 2] = 0x0; v.append(&mut m.to_vec()); BigNum::from_slice(&v) } pub fn rsa_padding_remove_pkcs1(m: &BigNum, pad_to: i32) -> Result { // 00 || 01 || padding string || 00 || data let v = m.to_vec_padded(pad_to)?; let mut i = 2; // first byte is zero and therefore num_bytes is 1 smaller than expected assert!(m.num_bytes() + 1 == pad_to, "Padding length incorrect"); assert!(v[0] == 0, "PKCS1 padding incorrect"); assert!(v[1] == 2, "PKCS1 padding incorrect"); while v[i] == 0xff { i += 1; } assert!(v[i] == 0, "PKCS1 padding incorrect"); BigNum::from_slice(&v[i + 1..]) } pub fn rsa_encrypt(m: &BigNum, p: &RsaPublicKey) -> Result { assert!(m < &p.n, "message must be smaller than n"); let m = rsa_padding_add_pkcs1(&m, p.n.num_bytes())?; let c = rsa_encrypt_unpadded(&m, p)?; Ok(c) } pub fn rsa_decrypt(c: &BigNum, p: &RsaPrivateKey) -> Result { let m = rsa_decrypt_unpadded(c, p)?; let m = rsa_padding_remove_pkcs1(&m, p.n.num_bytes())?; Ok(m) } pub fn rsa_encrypt_unpadded(m: &BigNum, p: &RsaPublicKey) -> Result { assert!(m < &p.n, "message must be smaller than n"); let mut ctx = BigNumContext::new()?; let mut c = BigNum::new()?; c.mod_exp(&m, &p.e, &p.n, &mut ctx)?; Ok(c) } pub fn rsa_decrypt_unpadded(c: &BigNum, p: &RsaPrivateKey) -> Result { let mut ctx = BigNumContext::new()?; let mut m = BigNum::new()?; // To decrypt: m = c**d%n. m.mod_exp(&c, &p.d, &p.n, &mut ctx)?; Ok(m) } pub fn rsa_encrypt_str(m: &str, p: &RsaPublicKey) -> Result { // Finally, to encrypt a string, do something cheesy. let m = BigNum::from_slice(&m.as_bytes())?; assert!(m < p.n); rsa_encrypt(&m, p) } pub fn rsa_decrypt_str(c: &BigNum, p: &RsaPrivateKey) -> Result { let m = rsa_decrypt(c, p)?; Ok(String::from(std::str::from_utf8(&m.to_vec()).unwrap())) } pub fn rsa_sign(m: &str, p: &RsaPrivateKey) -> Result { let hash = sha256(m.as_bytes()); let m = BigNum::from_slice(&hash)?; let m = rsa_padding_add_pkcs1(&m, p.n.num_bytes())?; rsa_decrypt_unpadded(&m, p) } pub fn rsa_verify(m: &str, p: &RsaPublicKey, signature: &BigNum) -> Result { let hash = BigNum::from_slice(&sha256(m.as_bytes()))?; let m = rsa_encrypt_unpadded(signature, p)?; let m = rsa_padding_remove_pkcs1(&m, p.n.num_bytes())?; Ok(m == hash) } pub fn rsa_verify_insecure( m: &str, p: &RsaPublicKey, signature: &BigNum, ) -> Result { let hash = BigNum::from_slice(&sha256(m.as_bytes()))?; const SHA256_HASH_LEN: usize = 32; let pad_to = p.n.num_bytes(); let m = rsa_encrypt_unpadded(signature, p)?; assert!(m.num_bytes() + 1 == pad_to, "Padding length incorrect"); // There was, 7 years ago, a common implementation flaw with RSA verifiers: they'd verify // signatures by "decrypting" them (cubing them modulo the public exponent) and then "parsing" // them by looking for 00h 01h ... ffh 00h ASN.1 HASH. let v = m.to_vec_padded(pad_to)?; assert!(v[0] == 0, "PKCS1 padding incorrect"); assert!(v[1] == 2, "PKCS1 padding incorrect"); let mut i = 2; while i < v.len() - 1 { if v[i] == 0xff && v[i + 1] == 0x0 { break; } i += 1; } i += 2; let sig = BigNum::from_slice(&v[i..i + SHA256_HASH_LEN])?; Ok(sig == hash) }