timed_release_crypto/lib.rs
1#![deny(dead_code, unused_variables, missing_docs, unused_doc_comments)]
2#![allow(unused)]
3
4/*!
5# AES-GCM Authenticated Encryption
6AES (Advanced Encryption Standard) is a widely used encryption algorithm for
7securing data. AES has several _operator modes_, of which we have selected GCM
8(Galois/Counter Mode). GCM combines encryption with authentication.
9This ensures that the data is confidential, but also mechanisms to verify that
10the data hasn’t been tampered with.
11
12# Large Numbers
13Working with large cryptographically secure numbers in Rust involves using
14crates that provide efficient, secure, and accurate arithmetic for
15numbers far beyond the size of standard primitive data types like u64 or
16u128. This is essential in cryptographic contexts where numbers can be
17hundreds or even thousands of bits long.
18
19Rust does not provide these capabilities natively for large numbers, so we
20are going to use the crates; `num-bigint` and `rug`.
21num-bigint is beginner-friendly and well-documented.
22
23We will lean heavily on `Biguint` crate to handle common cryptography primitives
24and modular operations, such as:
25+ large prime numbers : secure generation of large primes
26+ modular arithmetic : a mod n in order to ensure that comps stay inside n
27+ modular exponentiation : a^b mod n
28+ modulo inverse : computing (x^-1 mod n)
29
30# Notes on the use of S, modular exponentiation by squaring
31Computing `x^2 mod p` is generally assumed (TODO: citation needed) to be a
32_single operation_ that takes _constant time_. For example, one could just look
33up the multiplication table, which has only p^2 entries and can be precomputed.
34For further elaborations on this important topic:
351. https://en.m.wikipedia.org/wiki/Exponentiation_by_squaring
362. https://math.stackexchange.com/questions/2944032/
37why-is-the-algorithm-for-modular-exponentiation-by-squaring-considered-as-poly-t
38
39# Primality Testing
40... TODO: Elaborate on the use of rug.
41We use the `rug` crate to perform primatlity testing.
42
43# References:
44[1] R. L. Rivest, A. Shamir, and D. A. Wagner. 1996. Time-lock Puzzles and
45 Timed-release Crypto. Technical Report. Cambridge, MA, USA.
46
47[2] Timothy C. May. Timed-release crypto, February 1993.
48 https://cypherpunks.venona.com/date/1993/02/msg00306.html
49 and https://cypherpunks.venona.com/date/1993/02/msg00129.html
50*/
51
52////////////////////////////////////////////////////////////////////////////////
53// Imports
54use aes_gcm::{
55 aead::{Aead, OsRng},
56 Aes256Gcm, Key, KeyInit, Nonce,
57};
58use num_bigint::{BigUint, RandBigInt};
59use num_traits::One;
60use rand::{thread_rng, RngCore};
61
62////////////////////////////////////////////////////////////////////////////////
63// Utilities
64// TODO: consider removing this or moving it to a separate module.
65/// Utility function which does modular exponentiation of Biguint:
66/// a^b mod n == (base^exponent) % n
67#[allow(dead_code)]
68fn mod_exp(base: &BigUint, exponent: &BigUint, modulo: &BigUint) -> BigUint {
69 // TODO: Remove the two lines below if base.modpow is Copy.
70 // let mod_exp = base.modpow(exponent, modulo);
71 // println!("(a^e) mod n = {}", mod_exp);
72
73 base.modpow(exponent, modulo)
74}
75
76/// For primality test we use the classic Rabin-Miller test.
77#[derive(PartialEq, Debug)]
78pub enum Primality {
79 /// The number has been confirmed, deterministically, to be prime.
80 Prime,
81 /// The number is _only probably_ prime.
82 /// Using Rabin-Miller we can get the probability as low as we want, but we
83 /// can't get it to 0 (Prime).
84 /// For deterministic primality testing, see TODO: wiki link.
85 Probable,
86 /// The number has been confirmed to be a composite number (i.e. not Prime).
87 Composite,
88}
89
90////////////////////////////////////////////////////////////////////////////////
91/// TimeLockPuzzle
92///
93/// A tuple which representes the time-lock puzzle in its raw form.
94// TODO: elaborate: Parameters represent: TimeLockPuzzle(n, a, t, CK, CM).
95struct TimeLockPuzzle {
96 _n: BigUint,
97 _a: BigUint,
98 _t: BigUint,
99 _ck: BigUint,
100 _cm: Vec<u8>,
101}
102
103////////////////////////////////////////////////////////////////////////////////
104/// Capsule
105///
106/// Consists of a Capsule, which can be thought of as a key chain, and a Puzzle.
107/// The Puzzle is an encrypted messagecan stored inside the Capsule.
108/// __Defition:__ a solver is anyone, attacker or intended recipient, trying to
109/// crack the encryption to get the store message inside.
110///
111/// TODO: improve wording of the following interface desriptoin:
112/// The Capsule has the following interface:
113/// + `.create()`
114/// - function will generate private keys `p` and `q`, if provided with:
115/// `M`: a message,
116/// `a`: a constant,
117/// `t` : the desired time lock period, and...
118/// `s`: assumed squaring power of the solver
119///
120/// + `.arm()`
121/// - will TODO(zero out the private keys `p` and `q`) that were
122/// used during the creation of the puzzle, thereby locking the capsule.
123///
124/// + `.log()`
125/// - a method which will print all the steps which where taken during the
126/// creation of the Puzzle.
127///
128// TODO: turn off pub visibility and confirm that p and q are _NOT_ exported.
129pub struct Capsule {
130 // TODO: consider creating a Default for Capsule, with a:BigUint::from(2:32)
131 p: BigUint,
132 q: BigUint,
133 a: BigUint,
134 tlp: Option<TimeLockPuzzle>,
135}
136
137impl Default for Capsule {
138 fn default() -> Self {
139 Capsule {
140 p: Capsule::generate_large_random_prime(256u64),
141 q: Capsule::generate_large_random_prime(256u64),
142 a: BigUint::from(2u32),
143 tlp: None,
144 }
145 }
146}
147
148impl Capsule {
149 /// We create a new Capsule with a fresh pair of private keys, p and q.
150 pub fn new(&self, bits: u64) -> Self {
151 // TODO: verify: do we need to assert bits length > 160 here?
152 Capsule {
153 p: Capsule::generate_large_random_prime(bits),
154 q: Capsule::generate_large_random_prime(bits),
155 a: BigUint::from(2u32),
156 tlp: None,
157 }
158 }
159
160 /// Utility function in case we need to generate a new pair of private keys.
161 fn generate_new_keypair(&mut self, bits: u64) {
162 // TODO: verify: do we need to assert bits length > 160 here?
163 self.p = Capsule::generate_large_random_prime(bits);
164 self.q = Capsule::generate_large_random_prime(bits);
165 }
166
167 /// Function to create a puzzle and store it in the capsule.
168 /// TODO: elaborate : tuple (n,a,t,ck, cm) represents a time-lock-puzzle.
169 fn create_puzzle(
170 &self,
171 s: &BigUint,
172 t: &BigUint,
173 _plaintext: &[u8],
174 //) -> (BigUint, BigUint, BigUint, BigUint, Vec<u8>) {
175 ) -> TimeLockPuzzle {
176 // TODO: improve name of phi_n.
177 let n = self.compute_composite(&self.p, &self.q);
178 let phi_n = self.phi(&self.p, &self.q);
179 let t = self.compute_puzzle_strength(s, t);
180 // TODO: consider renaming crypto_system_key to aes_gcm_key
181 // to makes it clear that the key is part of the AES-GCM key process.
182
183 // TODO:consider changing the type of crypto_system_key to aes_gcm::Key.
184 let (crypto_sys_key, ciphertext) =
185 self.compute_cm(_plaintext);
186 let cipherkey =
187 self.compute_ck(&self.a, &phi_n, &t, &n, &crypto_sys_key);
188
189 // Puzzle created.
190 TimeLockPuzzle {
191 _n: n,
192 _a: self.a.clone(),
193 _t: t,
194 _ck: cipherkey,
195 _cm: ciphertext,
196 }
197 }
198}
199
200impl Capsule {
201 // TODO: work on next...
202 // lock()
203 // open()
204 // arm();
205 // log();
206}
207
208impl Capsule {
209 /// Returns a large random number of length based on the number of bits
210 /// passed as argument. The bit-size is expressed in u64.
211 ///
212 /// Large is a subjective term, but for our purposes we want to make sure
213 /// that the bits length is at least 160 bits.
214 ///
215 /// # Examples : exampel of use.
216 ///
217 /// ```
218 /// use timed_release_crypto::Capsule;
219 /// use num_bigint::{BigUint, RandBigInt};
220 /// // Generate two random 256-bit number.
221 /// let p: BigUint = Capsule::generate_large_random_number(256u64);
222 /// let q: BigUint = Capsule::generate_large_random_number(256u64);
223 /// assert_ne!(p,q);
224 /// println!("256-bit Random Number P: {}", p);
225 /// println!("256-bit Random Number Q: {}", q);
226 /// ```
227 ///
228 /// Panic : This function will panic if passed a bits value below 160.
229 ///
230 /// Failure : TODO: consider changing return value to Result<BigUint, Err>.
231 ///
232 /// TODO: clean up: Require modulo 2^32 - see p3, ref[1]
233 pub fn generate_large_random_number(bits: u64) -> BigUint {
234 // For Time-Lock-Puzzle purposes we want to work with over 159 bits.
235 assert!(bits > 159);
236
237 let mut rng = thread_rng();
238 let prime_candidate: BigUint = rng.gen_biguint(bits);
239 println!("Generated Large Number: {}", prime_candidate);
240
241 // TODO: remove: rand::thread_rng().gen_bigint(bits)
242 prime_candidate
243 }
244
245 /// Rudimentary probabilistic primality test using Fermat's Primality Test.
246 ///
247 /// Fermat's Primality Test checks if:
248 /// is a random base number, raised to n-1 modulo n, equal to 1?
249 /// If true, then the candidate is probably prime. Please note the word
250 /// _probably_! If we want to be 100% sure that the candidate is prime, we
251 /// need to use a deterministic, rather than probabilistic primality test.
252 ///
253 /// A deterministic method may be too slow for our purposes. In that case
254 /// an alternative is to use the Rabin-Miller primality test.
255 ///
256 /// TODO: Look up a deterministic primality-test method.
257 /// TODO: consider primality to be a feature.
258 fn is_probably_prime(candidate: &BigUint) -> bool {
259 // Substep 01: check that the candidate is larger than 1.
260 if *candidate <= BigUint::from(1u32) {
261 return false;
262 }
263
264 // Substep 02: Generate a random base number between [1, candidate).
265 // 1 <= base < candidate
266 let mut rng = thread_rng();
267 let base: BigUint =
268 rng.gen_biguint_range(&BigUint::one(), candidate);
269
270 // Substep 03: Compute the modular: base^(candidate-1) % candidate .
271 let result = base.modpow(
272 &(candidate - BigUint::one()),
273 candidate
274 );
275
276 // Substep 04: If the result is 1 then the candidate is probably prime.
277 // TODO: consider returning an enum:
278 // Primality::Prime,
279 // Primality::Probable,
280 // Primality::Composite,
281 result.is_one()
282 }
283
284 fn generate_large_random_prime(bits: u64) -> BigUint {
285 // TODO: Bench this loop to see how long 50 runs take.
286 loop {
287 let prime_candidate = Self::generate_large_random_number(bits);
288 if Self::is_probably_prime(&prime_candidate) {
289 return prime_candidate;
290 }
291 }
292 }
293
294 ////////////////////////////////////////////////////////////////////////////
295 /// Step 01: Generate (and test for primality) two distinct large secret
296 /// prime numbers `P` and `Q` and multiply them together to form the
297 /// composite `n=pq`.
298 ///
299 fn compute_composite(&self, p: &BigUint, q: &BigUint) -> BigUint {
300 // BigUint will panic if subraction reaches into negative numbers.
301 assert!(p >= &BigUint::from(0u32) && q >= &BigUint::from(0u32));
302
303 // Ref: [1] page 3. equation (1): n = p*q
304 p * q
305 }
306
307 ////////////////////////////////////////////////////////////////////////////
308 /// Step 02: Compute the Euler Totient: phi(n) = (P-1)*(Q-1).
309 /// TODO: lookup: is it possible to write the congruent equal sign?
310 /// The Euler Phi Function is used to ...
311 /// The formula `a^{phi(m)} is congruent with 1 (mod m)`.
312 /// TODO: Explain the mathematics behind using phi funciton.
313 ///
314 /// # Panic
315 /// BigUnit does not support subtraction resulting in a negative number.
316 /// We assert that both arguments are positive, instead of panicking.
317 fn phi(&self, p: &BigUint, q: &BigUint) -> BigUint {
318 // BigUint will panic if subraction reaches into negative numbers.
319 assert!(p >= &BigUint::from(0u32) && q >= &BigUint::from(0u32));
320
321 // Ref: [1] page 3. equation (2): phi(n) = (p-1)*(q-1)
322 (p - &BigUint::from(1u32)) * (q - &BigUint::from(1u32))
323 }
324
325 ////////////////////////////////////////////////////////////////////////////
326 /// Step 03: Compute `t=TS`, where:
327 /// + T is the prefered approximate period in seconds that the puzzle should
328 /// withstand an attempt to decrypt it.
329 /// + S is the number of `squarings modulo n` per second that can be
330 /// performed by the agent (attacker/or intended receiver) trying to decrypt
331 /// the message.
332 ///
333 /// TODO: question: what should we base our S value on?
334 fn compute_puzzle_strength(&self, s: &BigUint, t: &BigUint) -> BigUint {
335 // TODO: consider if S needs to be dealt with here.
336 let t = s * t;
337 t
338 }
339
340 ////////////////////////////////////////////////////////////////////////////
341 // Step 04: Generate a `random key K` for a conventional cryptosystem.
342 // We're using AES-GCM (with a 256-bit key) via the `aes_gcm` crate.
343 // The crate provides AES-256-GCM encryption and decryption capabilities.
344 fn compute_cm(&self, plaintext: &[u8]) -> (Key<Aes256Gcm>, Vec<u8>) {
345 // Substep 01
346 // Generate a random 32-byte (256 bit) key for AES_256.
347 // We use `OsRng` to generate cryptographically secure random numbers.
348 // Note: Key::<Aes256Gcm> is equal to GenericArray<u8, U32>.
349 let key: Key<Aes256Gcm> = Aes256Gcm::generate_key(&mut OsRng);
350 println!("Generated Key: {:?}", key);
351
352 // Substep 02
353 // We generate a random nonce (Number used ONCE), a random 96-bit
354 // (12 bytes, [u8;12]) value, that is used to ensure the uniqueness of our
355 // encryption operations.
356 //
357 // TODO: remove the line below if not needed and tests pass.
358 // let mut nonce: BigUint = Nonce::generate(&mut OsRng);
359 let mut nonce_bytes = [0u8; 12];
360 OsRng.fill_bytes(&mut nonce_bytes);
361 let nonce =
362 Nonce::from_slice(&nonce_bytes);
363 println!("Generated Nonce: {:?}", nonce);
364
365 // Substep 03
366 // Encrypt the message M with key K and encryption algoritym AES-GCM
367 // AES-GCM is an excellent tool for encryption, key initialization,
368 // and random number generation. We use AES-256-GCM to generate keys of
369 // length 256 bits to obtain the cipertext: C_M = AES-GCM(K,M).
370 //
371 // Initialize the AES-GCM cipher with the generated key.
372 // Key: A randomly generated 256-bit key used for encryption/decryption.
373 let cipher = Aes256Gcm::new(&key);
374
375 // Substep 04
376 // The plaintext message `M` is encrypted into ciphertext form, `CM`.
377 // The plaintext needs to be in byte form, for example `b"hello"`.
378 // Encrypt the plaintext: CM = AES-GCM(K,M), using the nonce and key.
379 println!("Plaintext: {:?}", plaintext);
380
381 let cm = cipher
382 .encrypt(&nonce, plaintext.as_ref())
383 .expect("Encryption failed");
384 println!("Ciphertext: {:?}", cm);
385
386 // TODO: consider returning a Result rather than simply doing an assert
387 // to check that CM isn't empty.
388 assert!(cm.len() > 0);
389
390 // TODO: does the nonce need to be returned/persisted or can we simply
391 // discard it for now?
392 (key, cm)
393 }
394
395 ////////////////////////////////////////////////////////////////////////////
396 // Step 05: Pick a random `a module n`, with 1 < a < n, and compute
397 // TODO: see which arguments that are actually needed.
398 fn compute_ck(
399 &self,
400 a: &BigUint,
401 phi_n: &BigUint,
402 t: &BigUint,
403 n: &BigUint,
404 k: &Key<Aes256Gcm>,
405 ) -> BigUint {
406 // TODO: consider moving `a` into a global const or part of Capsule.
407 // Note: According to Ref. [1] page.4: "Indeed, in practice choosing a
408 // fixed value of `a=2` should be safe with high probability".
409 //let a: BigUint = BigUint::from(2u32);
410
411 // Substep 01
412 // Ref: [1] page 4. equation (6): e = 2^t (mod phi(n))
413 // TODO: remove line below once confirmed line underneath is correct.
414 let e: BigUint = a.modpow(t, phi_n);
415
416 // Substep 02
417 // Ref: [1] page 4. equation (7): b = a^e (mod n)
418 // TODO: remove line below, once confirmed line underneath is correct.
419 let b: BigUint = a.modpow(&e, n);
420
421 // Substep 03
422 // Encryption of the public key K is done by computing:
423 // Ref: [1] page 4. equation (5): C_K = K + a^2^t (mod n)
424 //
425 // I faced some challenges here so I'm documenting clarifications.
426 // The passed crypto_system_key `k` is of type Key<Aes256Gcm> and is
427 // essentially an array of bytes, (GenericArray<u8, U32>).
428 //
429 // Before we can perform the operation K + a^2^t (mod n),
430 // where a^2^t is of type BigUint, we need to convert k:Key<Aes256Gcm>
431 // into k:BigUint.
432 //
433 // We can use the `.as_slice()` method to get the raw byte array,
434 // and then use BigUint::from_bytes_be to convert it into a BigUint.
435 let ck = (BigUint::from_bytes_be(k.as_slice()) + b) % n;
436
437 ck
438 }
439} // Capsule
440
441////////////////////////////////////////////////////////////////////////////////
442// Tests
443#[cfg(test)]
444mod test {
445 use super::*;
446
447 #[test]
448 fn test_phi() {
449 let p = BigUint::from(631u32);
450 let q = BigUint::from(2153u32);
451 let c = Capsule::default();
452
453 assert_eq!(c.phi(&p, &q), BigUint::from(1355760u32));
454 }
455
456 #[test]
457 fn test_bit_size_of_generated_number() {
458 let bits = 256;
459 let p = Capsule::generate_large_random_number(bits);
460
461 // Check the number of bits
462 let num_bits = p.bits();
463 assert!(
464 num_bits <= bits && num_bits > bits - 8,
465 "The number of bits ({}) is not in the expected range for {} bits.",
466 num_bits, bits
467 );
468 }
469
470 #[test]
471 fn test_randomness_of_generated_numbers() {
472 let bits = 256;
473 let p = Capsule::generate_large_random_number(bits);
474 let q = Capsule::generate_large_random_number(bits);
475
476 // TODO: instead of pannicking - simply rerun the generation.
477 assert_ne!(
478 p, q,
479 "The two generated numbers are the same, which is very unlikely."
480 );
481 }
482
483 #[test]
484 fn test_compute_composite() {
485
486 let capsule = Capsule {
487 p: BigUint::one(),
488 q: BigUint::one(),
489 a: BigUint::from(2u32),
490 tlp: None,
491 };
492
493 // Test values for p and q
494 let p = BigUint::from(13u32);
495 let q = BigUint::from(17u32);
496 // Compute the composite
497 let result = capsule.compute_composite(&p, &q);
498 // Expected result
499 let expected = p.clone() * q.clone();
500 // Assert the result matches the expected value
501 assert_eq!(result, expected, "The computed composite is incorrect");
502 }
503}
504
505// END
506////////////////////////////////////////////////////////////////////////////////