From d2f45cec1ae4832baaba1cdcd36b2491043f3824 Mon Sep 17 00:00:00 2001 From: Jelle Vos Date: Fri, 6 May 2022 14:41:43 +0200 Subject: [PATCH 1/4] Precompute n_squared for Paillier --- scicrypt-he/src/cryptosystems/paillier.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scicrypt-he/src/cryptosystems/paillier.rs b/scicrypt-he/src/cryptosystems/paillier.rs index 6d2edda..9c899bd 100644 --- a/scicrypt-he/src/cryptosystems/paillier.rs +++ b/scicrypt-he/src/cryptosystems/paillier.rs @@ -19,6 +19,7 @@ pub struct Paillier { #[derive(PartialEq, Debug)] pub struct PaillierPK { n: Integer, + n_squared: Integer, g: Integer, } @@ -62,7 +63,9 @@ impl AsymmetricCryptosystem for Paillier { let g = &n + Integer::from(1); let mu = Integer::from(lambda.invert_ref(&n).unwrap()); - (PaillierPK { n, g }, PaillierSK { lambda, mu }) + let n_squared = Integer::from(n.square_ref()); + + (PaillierPK { n, g, n_squared }, PaillierSK { lambda, mu }) } } @@ -89,14 +92,13 @@ impl EncryptionKey for PaillierPK { plaintext: &Integer, rng: &mut GeneralRng, ) -> PaillierCiphertext { - let n_squared = Integer::from(self.n.square_ref()); - let r = gen_coprime(&n_squared, rng); + let r = gen_coprime(&self.n_squared, rng); - let first = Integer::from(self.g.pow_mod_ref(&plaintext.into(), &n_squared).unwrap()); - let second = r.secure_pow_mod(&self.n, &n_squared); + let first = Integer::from(self.g.pow_mod_ref(&plaintext.into(), &self.n_squared).unwrap()); + let second = r.secure_pow_mod(&self.n, &self.n_squared); PaillierCiphertext { - c: (first * second).rem(&n_squared), + c: (first * second).rem(&self.n_squared), } } } From 6b630aa498711a58ab5bb0fb9e7026b291ce6e05 Mon Sep 17 00:00:00 2001 From: Jelle Vos Date: Fri, 6 May 2022 14:54:07 +0200 Subject: [PATCH 2/4] Sample randomly instead of gen_coprime --- scicrypt-he/src/cryptosystems/paillier.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scicrypt-he/src/cryptosystems/paillier.rs b/scicrypt-he/src/cryptosystems/paillier.rs index 9c899bd..be828b8 100644 --- a/scicrypt-he/src/cryptosystems/paillier.rs +++ b/scicrypt-he/src/cryptosystems/paillier.rs @@ -1,5 +1,5 @@ use rug::Integer; -use scicrypt_numbertheory::{gen_coprime, gen_rsa_modulus}; +use scicrypt_numbertheory::gen_rsa_modulus; use scicrypt_traits::cryptosystems::{ Associable, AsymmetricCryptosystem, DecryptionKey, EncryptionKey, }; @@ -92,9 +92,15 @@ impl EncryptionKey for PaillierPK { plaintext: &Integer, rng: &mut GeneralRng, ) -> PaillierCiphertext { - let r = gen_coprime(&self.n_squared, rng); - - let first = Integer::from(self.g.pow_mod_ref(&plaintext.into(), &self.n_squared).unwrap()); + // r must be coprime with n_squared but this only fails with probability 2^(1 - n_in_bits) + // 0 also only occurs with extremely low probability, so we can simply sample randomly s.t. 0 < r < n + let r = Integer::from(self.n.random_below_ref(&mut rng.rug_rng())); + + let first = Integer::from( + self.g + .pow_mod_ref(&plaintext, &self.n_squared) + .unwrap(), + ); let second = r.secure_pow_mod(&self.n, &self.n_squared); PaillierCiphertext { From dca05d6d496bd828d3914f476e736ae1c52620fc Mon Sep 17 00:00:00 2001 From: Jelle Vos Date: Fri, 6 May 2022 15:17:03 +0200 Subject: [PATCH 3/4] Run cargo clippy and fmt --- scicrypt-he/src/cryptosystems/paillier.rs | 24 ++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/scicrypt-he/src/cryptosystems/paillier.rs b/scicrypt-he/src/cryptosystems/paillier.rs index be828b8..afb3c74 100644 --- a/scicrypt-he/src/cryptosystems/paillier.rs +++ b/scicrypt-he/src/cryptosystems/paillier.rs @@ -96,11 +96,7 @@ impl EncryptionKey for PaillierPK { // 0 also only occurs with extremely low probability, so we can simply sample randomly s.t. 0 < r < n let r = Integer::from(self.n.random_below_ref(&mut rng.rug_rng())); - let first = Integer::from( - self.g - .pow_mod_ref(&plaintext, &self.n_squared) - .unwrap(), - ); + let first = Integer::from(self.g.pow_mod_ref(plaintext, &self.n_squared).unwrap()); let second = r.secure_pow_mod(&self.n, &self.n_squared); PaillierCiphertext { @@ -135,6 +131,24 @@ impl DecryptionKey for PaillierSK { inner.rem(&public_key.n) } + + fn encrypt_fast_raw( + &self, + public_key: &PaillierPK, + plaintext: &::Plaintext, + rng: &mut GeneralRng, + ) -> PaillierCiphertext { + // r must be coprime with n_squared but this only fails with probability 2^(1 - n_in_bits) + // 0 also only occurs with extremely low probability, so we can simply sample randomly s.t. 0 < r < n + let r = Integer::from(public_key.n.random_below_ref(&mut rng.rug_rng())); + + let first = Integer::from(public_key.g.pow_mod_ref(plaintext, &public_key.n_squared).unwrap()); + let second = r.secure_pow_mod(&public_key.n, &public_key.n_squared); + + PaillierCiphertext { + c: (first * second).rem(&public_key.n_squared), + } + } } impl HomomorphicAddition for PaillierPK { From f05fab73a23df9b27f0f90c0a74ab2f763cb80da Mon Sep 17 00:00:00 2001 From: Jelle Vos Date: Sat, 7 May 2022 11:29:29 +0200 Subject: [PATCH 4/4] Implement fast encryption for RSA --- scicrypt-he/src/cryptosystems/paillier.rs | 9 ++- scicrypt-he/src/cryptosystems/rsa.rs | 74 ++++++++++++++++++++--- scicrypt-numbertheory/src/lib.rs | 8 +-- scicrypt-traits/src/cryptosystems.rs | 23 +++++++ 4 files changed, 101 insertions(+), 13 deletions(-) diff --git a/scicrypt-he/src/cryptosystems/paillier.rs b/scicrypt-he/src/cryptosystems/paillier.rs index afb3c74..17e9d23 100644 --- a/scicrypt-he/src/cryptosystems/paillier.rs +++ b/scicrypt-he/src/cryptosystems/paillier.rs @@ -58,7 +58,7 @@ impl AsymmetricCryptosystem for Paillier { /// let (public_key, secret_key) = paillier.generate_keys(&mut rng); /// ``` fn generate_keys(&self, rng: &mut GeneralRng) -> (PaillierPK, PaillierSK) { - let (n, lambda) = gen_rsa_modulus(self.modulus_size, rng); + let (n, lambda, _, _) = gen_rsa_modulus(self.modulus_size, rng); let g = &n + Integer::from(1); let mu = Integer::from(lambda.invert_ref(&n).unwrap()); @@ -142,7 +142,12 @@ impl DecryptionKey for PaillierSK { // 0 also only occurs with extremely low probability, so we can simply sample randomly s.t. 0 < r < n let r = Integer::from(public_key.n.random_below_ref(&mut rng.rug_rng())); - let first = Integer::from(public_key.g.pow_mod_ref(plaintext, &public_key.n_squared).unwrap()); + let first = Integer::from( + public_key + .g + .pow_mod_ref(plaintext, &public_key.n_squared) + .unwrap(), + ); let second = r.secure_pow_mod(&public_key.n, &public_key.n_squared); PaillierCiphertext { diff --git a/scicrypt-he/src/cryptosystems/rsa.rs b/scicrypt-he/src/cryptosystems/rsa.rs index 22bb489..e8569a3 100644 --- a/scicrypt-he/src/cryptosystems/rsa.rs +++ b/scicrypt-he/src/cryptosystems/rsa.rs @@ -22,9 +22,55 @@ pub struct RsaPK { e: Integer, } -/// Decryption key for RSA -pub struct RsaSK { +/// Precomputed decryption key for RSA +pub struct PrecomputedRsaSK { + p: Integer, + q: Integer, d: Integer, + d_p: Integer, + d_q: Integer, + q_inv: Integer, +} + +impl PrecomputedRsaSK { + /// Creates a new precomputed secret key using p, q, and d + pub fn new(p: Integer, q: Integer, d: Integer) -> Self { + let d_p = Integer::from(&d).rem(Integer::from(&p - 1)); + let d_q = Integer::from(&d).rem(Integer::from(&q - 1)); + let q_inv = Integer::from(q.invert_ref(&p).unwrap()); + + PrecomputedRsaSK { + p, + q, + d, + d_p, + d_q, + q_inv, + } + } + + /// Compresses the precomputed secret key to a smaller format without precomputations + pub fn compress(self) -> CompressedRsaSK { + CompressedRsaSK { + p: self.p, + q: self.q, + d: self.d, + } + } +} + +/// Small secret key for RSA, which is less fast than a `PrecomputedRsaSK`. +pub struct CompressedRsaSK { + p: Integer, + q: Integer, + d: Integer, +} + +impl CompressedRsaSK { + /// Creates a `PrecomputedRsaSK` from this key. + pub fn precompute(self) -> PrecomputedRsaSK { + PrecomputedRsaSK::new(self.p, self.q, self.d) + } } /// Ciphertext of the RSA cryptosystem, which is multiplicatively homomorphic. @@ -36,7 +82,7 @@ impl Associable for RsaCiphertext {} impl AsymmetricCryptosystem for Rsa { type PublicKey = RsaPK; - type SecretKey = RsaSK; + type SecretKey = PrecomputedRsaSK; fn setup(security_param: &BitsOfSecurity) -> Self { Rsa { @@ -44,13 +90,13 @@ impl AsymmetricCryptosystem for Rsa { } } - fn generate_keys(&self, rng: &mut GeneralRng) -> (RsaPK, RsaSK) { - let (n, lambda) = gen_rsa_modulus(self.modulus_size, rng); + fn generate_keys(&self, rng: &mut GeneralRng) -> (RsaPK, PrecomputedRsaSK) { + let (n, lambda, p, q) = gen_rsa_modulus(self.modulus_size, rng); let e = Integer::from(65537); let d = Integer::from(e.invert_ref(&lambda).unwrap()); - (RsaPK { n, e }, RsaSK { d }) + (RsaPK { n, e }, PrecomputedRsaSK::new(p, q, d)) } } @@ -70,12 +116,26 @@ impl EncryptionKey for RsaPK { } } -impl DecryptionKey for RsaSK { +impl DecryptionKey for CompressedRsaSK { fn decrypt_raw(&self, public_key: &RsaPK, ciphertext: &RsaCiphertext) -> Integer { Integer::from(ciphertext.c.secure_pow_mod_ref(&self.d, &public_key.n)) } } +impl DecryptionKey for PrecomputedRsaSK { + fn decrypt_raw( + &self, + _public_key: &RsaPK, + ciphertext: &::Ciphertext, + ) -> ::Plaintext { + let m_p = Integer::from(ciphertext.c.secure_pow_mod_ref(&self.d_p, &self.p)); + let m_q = Integer::from(ciphertext.c.secure_pow_mod_ref(&self.d_q, &self.q)); + let h = (&self.q_inv * (m_p - &m_q)).rem(&self.p); + + m_q + h * &self.q + } +} + impl HomomorphicMultiplication for RsaPK { fn mul( &self, diff --git a/scicrypt-numbertheory/src/lib.rs b/scicrypt-numbertheory/src/lib.rs index 8bf861e..debd573 100644 --- a/scicrypt-numbertheory/src/lib.rs +++ b/scicrypt-numbertheory/src/lib.rs @@ -107,20 +107,20 @@ pub fn gen_safe_prime(bit_length: u32, rng: &mut GeneralRng) -> } /// Generates a uniformly random RSA modulus, which is the product of two safe primes $p$ and $q$. -/// This method returns both the modulus and $\lambda$, which is the least common multiple of +/// This method returns the modulus, $\lambda$, $p$, and $q$. $\lambda$ is the least common multiple of /// $p - 1$ and $q - 1$. pub fn gen_rsa_modulus( bit_length: u32, rng: &mut GeneralRng, -) -> (Integer, Integer) { +) -> (Integer, Integer, Integer, Integer) { let p = gen_safe_prime(bit_length / 2, rng); let q = gen_safe_prime(bit_length / 2, rng); let n = Integer::from(&p * &q); - let lambda: Integer = (p - Integer::from(1)).lcm(&(q - Integer::from(1))); + let lambda: Integer = (&p - Integer::from(1)).lcm(&(&q - Integer::from(1))); - (n, lambda) + (n, lambda, p, q) } /// Generates a uniformly random coprime $x$ to the `other` integer $y$. This means that diff --git a/scicrypt-traits/src/cryptosystems.rs b/scicrypt-traits/src/cryptosystems.rs index 3511f02..7737235 100644 --- a/scicrypt-traits/src/cryptosystems.rs +++ b/scicrypt-traits/src/cryptosystems.rs @@ -67,6 +67,29 @@ pub trait DecryptionKey { /// Decrypt the ciphertext using the secret key and its related public key. fn decrypt_raw(&self, public_key: &PK, ciphertext: &PK::Ciphertext) -> PK::Plaintext; + + /// Uses both the secret material from the decryption key and the encryption key to encrypt faster than with only the encryption key. + /// This method is always implemented, but it defaults to simply encrypting without the secret key. + fn encrypt_fast<'pk, R: SecureRng>( + &'pk self, + public_key: &'pk PK, + plaintext: &PK::Plaintext, + rng: &mut GeneralRng, + ) -> AssociatedCiphertext<'pk, PK::Ciphertext, PK> { + self.encrypt_fast_raw(public_key, plaintext, rng) + .associate(public_key) + } + + /// Uses both the secret material from the decryption key and the encryption key to encrypt faster than with only the encryption key. + /// This method is always implemented, but it defaults to simply encrypting without the secret key. + fn encrypt_fast_raw( + &self, + public_key: &PK, + plaintext: &PK::Plaintext, + rng: &mut GeneralRng, + ) -> PK::Ciphertext { + public_key.encrypt_raw(plaintext, rng) + } } #[derive(PartialEq, Debug)]