noah_crypto/basic/
hybrid_encryption.rs

1use aes::{
2    cipher::{generic_array::GenericArray, KeyIvInit, StreamCipher},
3    Aes256,
4};
5use curve25519_dalek::edwards::CompressedEdwardsY;
6use ed25519_dalek::{ExpandedSecretKey, PublicKey, SecretKey};
7use noah_algebra::errors::NoahError;
8use noah_algebra::prelude::*;
9use noah_algebra::ristretto::RistrettoScalar;
10use serde::Serializer;
11use sha2::Digest;
12use wasm_bindgen::prelude::*;
13
14type Aes256Ctr = ctr::Ctr64BE<Aes256>;
15
16#[wasm_bindgen]
17#[derive(Debug, Clone)]
18/// The public key for the hybrid encryption scheme.
19pub struct XPublicKey {
20    pub(crate) key: x25519_dalek::PublicKey,
21}
22
23impl NoahFromToBytes for XPublicKey {
24    fn noah_to_bytes(&self) -> Vec<u8> {
25        self.key.as_bytes().to_vec()
26    }
27
28    fn noah_from_bytes(bytes: &[u8]) -> Result<Self> {
29        if bytes.len() != 32 {
30            Err(eg!(NoahError::DeserializationError))
31        } else {
32            let mut array = [0u8; 32];
33            array.copy_from_slice(bytes);
34            Ok(XPublicKey {
35                key: x25519_dalek::PublicKey::from(array),
36            })
37        }
38    }
39}
40
41serialize_deserialize!(XPublicKey);
42
43impl XPublicKey {
44    /// Derive the public key from the secret key.
45    pub fn from(sk: &XSecretKey) -> XPublicKey {
46        XPublicKey {
47            key: x25519_dalek::PublicKey::from(&sk.key),
48        }
49    }
50}
51
52impl PartialEq for XPublicKey {
53    fn eq(&self, other: &Self) -> bool {
54        self.key.as_bytes() == other.key.as_bytes()
55    }
56}
57
58impl Eq for XPublicKey {}
59
60#[wasm_bindgen]
61#[derive(Clone)]
62/// The secret key for the hybrid encryption scheme.
63pub struct XSecretKey {
64    pub(crate) key: x25519_dalek::StaticSecret,
65}
66
67impl NoahFromToBytes for XSecretKey {
68    fn noah_to_bytes(&self) -> Vec<u8> {
69        self.key.to_bytes().to_vec()
70    }
71
72    fn noah_from_bytes(bytes: &[u8]) -> Result<Self> {
73        if bytes.len() != 32 {
74            Err(eg!(NoahError::DeserializationError))
75        } else {
76            let mut array = [0u8; 32];
77            array.copy_from_slice(bytes);
78            Ok(XSecretKey {
79                key: x25519_dalek::StaticSecret::from(array),
80            })
81        }
82    }
83}
84
85serialize_deserialize!(XSecretKey);
86
87impl XSecretKey {
88    /// Create a new secret key for x25519.
89    pub fn new<R: CryptoRng + RngCore>(prng: &mut R) -> XSecretKey {
90        XSecretKey {
91            key: x25519_dalek::StaticSecret::new(prng),
92        }
93    }
94}
95
96impl PartialEq for XSecretKey {
97    fn eq(&self, other: &Self) -> bool {
98        self.key.to_bytes() == other.key.to_bytes()
99    }
100}
101
102impl Eq for XSecretKey {}
103
104#[derive(Clone, Debug, Default, PartialEq, Eq)]
105/// The ciphertext from the symmetric encryption.
106pub struct Ctext(pub Vec<u8>);
107impl NoahFromToBytes for Ctext {
108    fn noah_to_bytes(&self) -> Vec<u8> {
109        self.0.clone()
110    }
111
112    fn noah_from_bytes(bytes: &[u8]) -> Result<Self> {
113        Ok(Ctext(bytes.to_vec()))
114    }
115}
116serialize_deserialize!(Ctext);
117
118#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
119/// A ciphertext of hybrid encryption.
120pub struct NoahHybridCiphertext {
121    pub(crate) ciphertext: Ctext,
122    pub(crate) ephemeral_public_key: XPublicKey,
123}
124
125impl NoahFromToBytes for NoahHybridCiphertext {
126    fn noah_to_bytes(&self) -> Vec<u8> {
127        let mut bytes = vec![];
128        bytes.append(&mut self.ephemeral_public_key.noah_to_bytes());
129        bytes.append(&mut self.ciphertext.noah_to_bytes());
130        bytes
131    }
132
133    fn noah_from_bytes(bytes: &[u8]) -> Result<Self> {
134        if bytes.len() < 32 {
135            Err(eg!(NoahError::DeserializationError))
136        } else {
137            let ephemeral_public_key = XPublicKey::noah_from_bytes(&bytes[0..32])?;
138            let ciphertext = Ctext::noah_from_bytes(&bytes[32..])?;
139            Ok(Self {
140                ciphertext,
141                ephemeral_public_key,
142            })
143        }
144    }
145}
146
147/// Encrypt a message over X25519
148pub fn hybrid_encrypt_x25519<R: CryptoRng + RngCore>(
149    prng: &mut R,
150    pub_key: &XPublicKey,
151    message: &[u8],
152) -> NoahHybridCiphertext {
153    let (key, ephemeral_key) = symmetric_key_from_x25519_public_key(prng, &pub_key.key);
154    let ciphertext = symmetric_encrypt(&key, message);
155    NoahHybridCiphertext {
156        ciphertext,
157        ephemeral_public_key: XPublicKey { key: ephemeral_key },
158    }
159}
160
161/// Encrypt a message over Ed25519
162pub fn hybrid_encrypt_ed25519<R: CryptoRng + RngCore>(
163    prng: &mut R,
164    pub_key: &PublicKey,
165    message: &[u8],
166) -> NoahHybridCiphertext {
167    let (key, ephemeral_key) = symmetric_key_from_ed25519_public_key(prng, pub_key);
168    let ciphertext = symmetric_encrypt(&key, message);
169
170    NoahHybridCiphertext {
171        ciphertext,
172        ephemeral_public_key: XPublicKey { key: ephemeral_key },
173    }
174}
175
176/// Decrypt a hybrid ciphertext over X25519
177pub fn hybrid_decrypt_with_x25519_secret_key(
178    ctext: &NoahHybridCiphertext,
179    sec_key: &XSecretKey,
180) -> Vec<u8> {
181    let key = symmetric_key_from_x25519_secret_key(&sec_key.key, &ctext.ephemeral_public_key.key);
182    symmetric_decrypt(&key, &ctext.ciphertext)
183}
184
185/// Decrypt a hybrid ciphertext over Ed25519
186pub fn hybrid_decrypt_with_ed25519_secret_key(
187    ctext: &NoahHybridCiphertext,
188    sec_key: &SecretKey,
189) -> Vec<u8> {
190    let key = symmetric_key_from_ed25519_secret_key(sec_key, &ctext.ephemeral_public_key.key);
191    symmetric_decrypt(&key, &ctext.ciphertext)
192}
193
194/// Convert the shared secret to a symmetric key
195fn shared_secret_to_symmetric_key(shared_secret: &x25519_dalek::SharedSecret) -> [u8; 32] {
196    let mut hasher = sha2::Sha256::new();
197    hasher.update(shared_secret.as_bytes());
198    let hash = hasher.finalize();
199    let mut symmetric_key = [0u8; 32];
200    symmetric_key.copy_from_slice(hash.as_slice());
201    symmetric_key
202}
203
204/// Derive a symmetric key from an X25519 public key
205fn symmetric_key_from_x25519_public_key<R: CryptoRng + RngCore>(
206    prng: &mut R,
207    public_key: &x25519_dalek::PublicKey,
208) -> ([u8; 32], x25519_dalek::PublicKey) {
209    // simulate a DH key exchange
210    let ephemeral = x25519_dalek::EphemeralSecret::new(prng);
211    let dh_pk = x25519_dalek::PublicKey::from(&ephemeral);
212
213    let shared = ephemeral.diffie_hellman(public_key);
214
215    let symmetric_key = shared_secret_to_symmetric_key(&shared);
216    (symmetric_key, dh_pk)
217}
218
219/// Derive a symmetric key from an Ed25519 public key
220fn symmetric_key_from_ed25519_public_key<R>(
221    prng: &mut R,
222    public_key: &PublicKey,
223) -> ([u8; 32], x25519_dalek::PublicKey)
224where
225    R: CryptoRng + RngCore,
226{
227    let pk_curve_point = CompressedEdwardsY::from_slice(public_key.as_bytes());
228    let pk_montgomery = pk_curve_point.decompress().unwrap().to_montgomery();
229    let x_public_key = x25519_dalek::PublicKey::from(pk_montgomery.to_bytes());
230
231    symmetric_key_from_x25519_public_key(prng, &x_public_key)
232}
233
234fn sec_key_as_scalar(sk: &SecretKey) -> RistrettoScalar {
235    let expanded: ExpandedSecretKey = sk.into();
236    //expanded.key is not public, I need to extract it via serialization
237    let mut key_bytes = [0u8; 32];
238    key_bytes.copy_from_slice(&expanded.to_bytes()[0..32]); //1st 32 bytes are key
239    RistrettoScalar::from_bytes(&key_bytes).unwrap() // safe unwrap
240}
241
242/// Derive a symmetric key from a secret key over X25519
243fn symmetric_key_from_x25519_secret_key(
244    sec_key: &x25519_dalek::StaticSecret,
245    ephemeral_public_key: &x25519_dalek::PublicKey,
246) -> [u8; 32] {
247    let shared_key = sec_key.diffie_hellman(ephemeral_public_key);
248    shared_secret_to_symmetric_key(&shared_key)
249}
250
251/// Derive a symmetric key from a secret key over Ed25519
252fn symmetric_key_from_ed25519_secret_key(
253    sec_key: &SecretKey,
254    ephemeral_public_key: &x25519_dalek::PublicKey,
255) -> [u8; 32] {
256    let scalar_sec_key = sec_key_as_scalar(sec_key);
257    let mut bytes = [0u8; 32];
258    bytes.copy_from_slice(scalar_sec_key.to_bytes().as_slice());
259    let x_secret = x25519_dalek::StaticSecret::from(bytes);
260    symmetric_key_from_x25519_secret_key(&x_secret, ephemeral_public_key)
261}
262
263fn symmetric_encrypt(key: &[u8; 32], plaintext: &[u8]) -> Ctext {
264    let kkey = GenericArray::from_slice(key);
265    let ctr = GenericArray::from_slice(&[0u8; 16]); // counter can be zero because key is fresh
266    let mut ctext_vec = plaintext.to_vec();
267    let mut cipher = Aes256Ctr::new(kkey, ctr);
268    cipher.apply_keystream(ctext_vec.as_mut_slice());
269    Ctext(ctext_vec)
270}
271
272fn symmetric_decrypt(key: &[u8; 32], ciphertext: &Ctext) -> Vec<u8> {
273    let kkey = GenericArray::from_slice(key);
274    let ctr = GenericArray::from_slice(&[0u8; 16]);
275    let mut plaintext_vec = ciphertext.0.clone();
276    let mut cipher = Aes256Ctr::new(kkey, ctr);
277    cipher.apply_keystream(plaintext_vec.as_mut_slice());
278    plaintext_vec
279}
280
281#[cfg(test)]
282mod test {
283    use super::*;
284    use ed25519_dalek::Keypair;
285
286    #[test]
287    fn key_derivation() {
288        let mut prng = test_rng();
289        let keypair = Keypair::generate(&mut prng);
290        let (from_pk_key, encoded_rand) =
291            symmetric_key_from_ed25519_public_key(&mut prng, &keypair.public);
292        let from_sk_key =
293            symmetric_key_from_ed25519_secret_key(&keypair.secret_key(), &encoded_rand);
294        assert_eq!(from_pk_key, from_sk_key);
295    }
296
297    #[test]
298    fn symmetric_encryption_fresh_key() {
299        let msg = b"this is a message";
300        let key: [u8; 32] = [0u8; 32];
301        let mut ciphertext = symmetric_encrypt(&key, msg);
302        let decrypted = symmetric_decrypt(&key, &ciphertext);
303        assert_eq!(msg, decrypted.as_slice());
304
305        ciphertext.0[0] = 0xFF - ciphertext.0[0];
306        let result = symmetric_decrypt(&key, &ciphertext);
307        assert_ne!(msg, result.as_slice());
308    }
309
310    #[test]
311    fn hybrid_cipher() {
312        let mut prng = test_rng();
313        let key_pair = Keypair::generate(&mut prng);
314        let msg = b"this is another message";
315
316        let cipherbox = hybrid_encrypt_ed25519(&mut prng, &key_pair.public, msg);
317        let plaintext = hybrid_decrypt_with_ed25519_secret_key(&cipherbox, &key_pair.secret_key());
318        assert_eq!(msg, plaintext.as_slice());
319    }
320}