Skip to content

Commit

Permalink
Merge pull request #63 from drcapybara/feat/ml-kem-encryptions
Browse files Browse the repository at this point in the history
feat: refactor fallible functions
  • Loading branch information
Dustin-Ray committed Jun 29, 2024
2 parents 013808e + cbcbda5 commit aaf00b8
Show file tree
Hide file tree
Showing 19 changed files with 469 additions and 570 deletions.
149 changes: 88 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,34 @@
[![Crates.io](https://img.shields.io/crates/v/capycrypt?style=flat-square)](https://crates.io/crates/capycrypt)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/drcapybara/capyCRYPT/blob/master/LICENSE.txt)

A complete Rust cryptosystem implementing [NIST FIPS 202](https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf) & [NIST FIPS 197](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf) paired to the ed448 Golidlocks curve.
A complete Rust cryptosystem implementing:

## Security
This library is built with love as an academic excercise in cryptographic algorithm design. Despite how awesome and cool it is, it probably shouldn't be used for anything serious. If you find ways to make it even better, open an issue or PR and we'll gladly engage.
- AES: [NIST FIPS 197](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf)
- SHA3: [NIST FIPS 202](https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf)
- ML-KEM: [NIST FIPS 203](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.ipd.pdf)
- E448: [Ed448-Goldilocks Curve](https://eprint.iacr.org/2015/625.pdf)

These primitives form the basis of a platform supporting a wide variety of cryptographic operations, which are detailed below.

## Security
This library is built with love as an academic excercise in cryptographic algorithm design. Despite how awesome and cool it is, it probably shouldn't be used for anything serious right now. If you find ways to make it even better, open an issue or PR and we'll gladly engage.

## Features
- **AES:** NIST-Compliant Advanced Encryption Standard (AES) implementation for encrypting and decrypting data.
- **AES:** NIST-Compliant **Advanced Encryption Standard** (AES) implementation for encrypting and decrypting data.

- **Edwards Elliptic Curve:** High-performance, side-channel resistant instance of the [Ed448-Goldilocks](https://crates.io/crates/tiny_ed448_goldilocks) curve for asymmetric operations.
- **Edwards Elliptic Curve:** High-performance, side-channel resistant instance of the **Ed448-Goldilocks** curve for asymmetric operations.

- **SHA-3:** NIST-Compliant Secure Hash Algorithm 3 (SHA-3) implementation for generating cryptographic hash values, symmetric keystreams, and PRNGs.
- **SHA-3:** NIST-Compliant **Secure Hash Algorithm 3** (SHA-3) implementation for generating cryptographic hash values, symmetric keystreams, and PRNGs.

- **ML-KEM 768:** NIST Initial Public Draft (IPD)-Compliant **Module Ring-Learning With Errors Key Encapsulation Mechanism** (ML-KEM) for quantum-safe asymmetric key and message exchange.

## Supported Operations
- **Message Digest:** Computes hash of a given message, with adjustable digest lengths.
- **MACs:** Computes message authentication code of a given message, with adjustable bit security.
- **Shared Secret Key:** Symmetric message encryption and decryption.
- **Public Key Cryptography:** Asymmetric message encryption under public key, decryption with secret key.
- **Zero-Knowledge:** Prove knowledge of secret information with Schnorr/ECDHIES signatures.
- **Signatures** Prove and verify knowledge of secret information with Schnorr/ECDHIES signatures.
- **Quantum-Safe Message Exchange:** ML-KEM + SHA3 sponge for quantum-safe symmetric messaging and key exchange.

## Installation
Add the following line to your `Cargo.toml` file:
Expand All @@ -36,96 +44,115 @@ cargo add capycrypt
```

## Quick Start
### Compute Digest:
```rust
use capycrypt::{Hashable, Message};
// Hash the empty string
let mut data = Message::new(vec![]);
// Obtained from echo -n "" | openssl dgst -sha3-256
let expected = "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a";
// Compute a SHA3 digest with 128 bits of security
data.compute_sha3_hash(256);
assert!(hex::encode(data.digest.unwrap().to_vec()) == expected);
```

### Symmetric Encrypt/Decrypt:
### Quantum-Secure Encrypt/Decrypt:
```rust
use capycrypt::{
Message,
AESEncryptable,
SpongeEncryptable,
sha3::{aux_functions::byte_utils::get_random_bytes}
kem::{encryptable::KEMEncryptable, keypair::kem_keygen},
sha3::aux_functions::byte_utils::get_random_bytes,
Message, SecParam,
};
// Get a random 128-bit password
let key = get_random_bytes(16);

// Get 5mb random data
let mut msg = Message::new(get_random_bytes(5242880));
// Encrypt the data
msg.aes_encrypt_cbc(&key);
// Decrypt the data
msg.aes_decrypt_cbc(&key);
// Encrypt the data
msg.sha3_encrypt(&pw, 512);
// Decrypt the data
msg.sha3_decrypt(&pw);

// Verify operation success
assert!(msg.op_result.unwrap());
// Create a new ML-KEM public/private keypair
let (kem_pub_key, kem_priv_key) = kem_keygen();
// Encrypt the message
assert!(msg.kem_encrypt(&kem_pub_key, SecParam::D256).is_ok());
// Decrypt and verify
assert!(msg.kem_decrypt(&kem_priv_key).is_ok());
```

### Asymmetric Encrypt/Decrypt:
### Elliptic-Curve Encrypt/Decrypt:
```rust
use capycrypt::{
KeyEncryptable,
KeyPair,
Message,
sha3::aux_functions::byte_utils::get_random_bytes
ecc::{encryptable::KeyEncryptable, keypair::KeyPair},
sha3::aux_functions::byte_utils::get_random_bytes,
Message, SecParam,
};

// Get 5mb random data
let mut msg = Message::new(get_random_bytes(5242880));
// Create a new private/public keypair
let key_pair = KeyPair::new(&get_random_bytes(32), "test key".to_string(), 512);

// Create a new elliptic-curve public/private keypair
let key_pair = KeyPair::new(
&get_random_bytes(64), // random password for key
"test key".to_string(), // label
SecParam::D256, // bit-security for key
);
// Encrypt the message
msg.key_encrypt(&key_pair.pub_key, 512);
// Decrypt the message
msg.key_decrypt(&key_pair.priv_key);
// Verify
assert!(msg.op_result.unwrap());
assert!(msg.key_encrypt(&key_pair.pub_key, SecParam::D256).is_ok());
// Decrypt and verify
assert!(msg.key_decrypt(&key_pair.priv_key).is_ok());
```

### Symmetric Encrypt/Decrypt:
```rust
use capycrypt::{
aes::encryptable::AesEncryptable,
sha3::{aux_functions::byte_utils::get_random_bytes,
encryptable::SpongeEncryptable},
Message, SecParam,
};
// Get a random password
let pw = get_random_bytes(16);
// Get 5mb random data
let mut msg = Message::new(get_random_bytes(5242880));
// Encrypt the data
assert!(msg.aes_encrypt_ctr(&pw).is_ok());
// Decrypt the data
assert!(msg.aes_decrypt_ctr(&pw).is_ok());
// Encrypt the data
assert!(msg.sha3_encrypt(&pw, SecParam::D512).is_ok());
// Decrypt and verify
assert!(msg.sha3_decrypt(&pw).is_ok());
```

### Schnorr Signatures:
```rust
use capycrypt::{
Signable,
KeyPair,
Message,
ecc::{keypair::KeyPair, signable::Signable},
sha3::aux_functions::byte_utils::get_random_bytes,
Message, SecParam,
};
// Get random 5mb
let mut msg = Message::new(get_random_bytes(5242880));
// Get a random password
let pw = get_random_bytes(64);
// Generate a signing keypair
let key_pair = KeyPair::new(&pw, "test key".to_string(), 512);
// Sign with 256 bits of security
msg.sign(&key_pair, 512);
// Create a new elliptic-curve public/private keypair
let key_pair = KeyPair::new(
&get_random_bytes(64), // random password for key
"test key".to_string(), // label
SecParam::D256, // bit-security for key
);
// Sign with 128 bits of security
assert!(msg.sign(&key_pair, SecParam::D256).is_ok());
// Verify signature
msg.verify(&key_pair.pub_key);
// Assert correctness
assert!(msg.op_result.unwrap());
assert!(msg.verify(&key_pair.pub_key).is_ok());
```

### Compute Digest:
```rust
use capycrypt::{sha3::hashable::SpongeHashable, Message, SecParam};
// Hash the empty string
let mut data = Message::new(vec![]);
// Obtained from echo -n "" | openssl dgst -sha3-256
let expected = "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a";
// Compute a SHA3 digest with 128 bits of security
data.compute_sha3_hash(SecParam::D256);
assert!(hex::encode(data.digest) == expected);
```

## Performance
This library uses the criterion crate for benches. Running:
```bash
cargo bench
```
conducts benchmarks in order from lowest security to highest. For example, the lowest security configuration available in this library is the pairing of E222 with cSHAKE256, while the highest security offered is E521 paired with cSHAKE512.
conducts benchmarks over parameter sets in order from lowest security to highest.

Symmetric operations compare well to openSSL. On an Intel® Core™ i7-10710U × 12, our adaption of in-place keccak from the [XKCP](https://github.com/XKCP/XKCP) achieves a runtime of approximately 20 ms to digest 5mb of random data, vs approximately 17 ms in openSSL.

## (Plausible) Post-Quantum Security
This library pairs ML-KEM under the 768-parameter set to a SHA3-sponge construction for a quantum-safe public-key cryptosystem. It offers theoretic quantum-security through the use of the KEM and sponge primitives, which are both based on problems conjectured to be hard to solve for a quantum adversary. Our construction is non-standard, has not been evaluated by experts, and is unaudited. Our MLKEM library itself is a work in progress and only supports the NIST-II security parameter of 768. Furthermore, the current FIPS 203 IPD is, (as the name indicates), a draft, and final details about secure implementation may be subject to change. Our design currently exists in this library purely as an academic curiosity. Use it at your own risk, we provide no guarantee of safety, reliability, or efficiency.

## Acknowledgements

The authors wish to sincerely thank Dr. Paulo Barreto for the general design of this library as well as the curve functionality. We also wish to extend gratitude to the curve-dalek authors [here](https://github.com/crate-crypto/Ed448-Goldilocks) and [here](https://docs.rs/curve25519-dalek/4.1.1/curve25519_dalek/) for the excellent reference implementations and exemplary instances of rock-solid cryptography.
14 changes: 6 additions & 8 deletions benches/benchmark_e448_224.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ const BIT_SECURITY: SecParam = D224;

/// Symmetric encrypt and decrypt roundtrip
fn sym_enc(pw: &[u8], mut msg: Message) {
let _ = msg.sha3_encrypt(pw, &BIT_SECURITY);
let _ = msg.sha3_encrypt(pw, BIT_SECURITY);
let _ = msg.sha3_decrypt(pw);
}

/// Asymmetric encrypt and decrypt roundtrip + keygen
fn key_gen_enc_dec(pw: &[u8], mut msg: Message) {
let key_pair = KeyPair::new(pw, "test key".to_string(), &BIT_SECURITY).unwrap();
let _ = msg.key_encrypt(&key_pair.pub_key, &BIT_SECURITY);
let key_pair = KeyPair::new(pw, "test key".to_string(), BIT_SECURITY);
let _ = msg.key_encrypt(&key_pair.pub_key, BIT_SECURITY);
let _ = msg.key_decrypt(&key_pair.priv_key);
}

/// Signature generation + verification roundtrip
pub fn sign_verify(key_pair: KeyPair, mut msg: Message) {
let _ = msg.sign(&key_pair, &BIT_SECURITY);
let _ = msg.sign(&key_pair, BIT_SECURITY);
let _ = msg.verify(&key_pair.pub_key);
}

fn bench_sign_verify(c: &mut Criterion) {
c.bench_function("e448 + SHA3-224 Sign + Verify Roundtrip", |b| {
b.iter(|| {
sign_verify(
KeyPair::new(&get_random_bytes(16), "test key".to_string(), &BIT_SECURITY).unwrap(),
KeyPair::new(&get_random_bytes(16), "test key".to_string(), BIT_SECURITY),
Message::new(get_random_bytes(5242880)),
)
});
Expand All @@ -54,9 +54,7 @@ fn bench_key_gen_enc_dec(c: &mut Criterion) {
c.bench_function("e448 + SHA3-224 Asymmetric enc + dec", |b| {
b.iter(|| {
key_gen_enc_dec(
&KeyPair::new(&get_random_bytes(32), "test key".to_string(), &BIT_SECURITY)
.unwrap()
.priv_key,
&KeyPair::new(&get_random_bytes(32), "test key".to_string(), BIT_SECURITY).priv_key,
Message::new(get_random_bytes(5242880)),
)
});
Expand Down
14 changes: 6 additions & 8 deletions benches/benchmark_e448_512.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ const BIT_SECURITY: SecParam = D512;

/// Symmetric encrypt and decrypt roundtrip
fn sym_enc(pw: &[u8], mut msg: Message) {
let _ = msg.sha3_encrypt(pw, &BIT_SECURITY);
let _ = msg.sha3_encrypt(pw, BIT_SECURITY);
let _ = msg.sha3_decrypt(pw);
}

/// Asymmetric encrypt and decrypt roundtrip + keygen
fn key_gen_enc_dec(pw: &[u8], mut msg: Message) {
let key_pair = KeyPair::new(pw, "test key".to_string(), &BIT_SECURITY).unwrap();
let _ = msg.key_encrypt(&key_pair.pub_key, &BIT_SECURITY);
let key_pair = KeyPair::new(pw, "test key".to_string(), BIT_SECURITY);
let _ = msg.key_encrypt(&key_pair.pub_key, BIT_SECURITY);
let _ = msg.key_decrypt(&key_pair.priv_key);
}

/// Signature generation + verification roundtrip
pub fn sign_verify(key_pair: KeyPair, mut msg: Message) {
let _ = msg.sign(&key_pair, &BIT_SECURITY);
let _ = msg.sign(&key_pair, BIT_SECURITY);
let _ = msg.verify(&key_pair.pub_key);
}

fn bench_sign_verify(c: &mut Criterion) {
c.bench_function("e448 + SHA3-512 Sign + Verify Roundtrip 5mb", |b| {
b.iter(|| {
sign_verify(
KeyPair::new(&get_random_bytes(16), "test key".to_string(), &BIT_SECURITY).unwrap(),
KeyPair::new(&get_random_bytes(16), "test key".to_string(), BIT_SECURITY),
Message::new(get_random_bytes(5242880)),
)
});
Expand All @@ -54,9 +54,7 @@ fn bench_key_gen_enc_dec(c: &mut Criterion) {
c.bench_function("e448 + SHA3-512 Asymmetric enc + dec 5mb", |b| {
b.iter(|| {
key_gen_enc_dec(
&KeyPair::new(&get_random_bytes(32), "test key".to_string(), &BIT_SECURITY)
.unwrap()
.priv_key,
&KeyPair::new(&get_random_bytes(32), "test key".to_string(), BIT_SECURITY).priv_key,
Message::new(get_random_bytes(5242880)),
)
});
Expand Down
4 changes: 2 additions & 2 deletions benches/benchmark_sha3.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use capycrypt::sha3::hashable::Hashable;
use capycrypt::sha3::hashable::SpongeHashable;
use capycrypt::{Message, SecParam};

use capycrypt::sha3::aux_functions::byte_utils::get_random_bytes;
Expand All @@ -9,7 +9,7 @@ const BIT_SECURITY: SecParam = D256;

/// hash 5mb of random data with 128 bits of security
fn sha3_digest(mut msg: Message) {
let _ = msg.compute_sha3_hash(&BIT_SECURITY);
msg.compute_sha3_hash(BIT_SECURITY);
}

fn bench_sha3_digest(c: &mut Criterion) {
Expand Down
30 changes: 8 additions & 22 deletions src/aes/encryptable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ impl AesEncryptable for Message {
let iv = get_random_bytes(16);
let mut ke_ka = iv.clone();
ke_ka.append(&mut key.to_owned());
let ke_ka = kmac_xof(&ke_ka, &[], 512, "AES", &SecParam::D256)?;
let ke_ka = kmac_xof(&ke_ka, &[], 512, "AES", SecParam::D256);
let ke = &ke_ka[..key.len()].to_vec(); // Encryption Key
let ka = &ke_ka[key.len()..].to_vec(); // Authentication Key

self.digest = kmac_xof(ka, &self.msg, 512, "AES", &SecParam::D256);
self.digest = kmac_xof(ka, &self.msg, 512, "AES", SecParam::D256);
self.sym_nonce = Some(iv.clone());

let key_schedule = AES::new(ke);
Expand Down Expand Up @@ -81,7 +81,7 @@ impl AesEncryptable for Message {
let iv = self.sym_nonce.clone().unwrap();
let mut ke_ka = iv.clone();
ke_ka.append(&mut key.to_owned());
let ke_ka = kmac_xof(&ke_ka, &[], 512, "AES", &SecParam::D256)?;
let ke_ka = kmac_xof(&ke_ka, &[], 512, "AES", SecParam::D256);
let ke = &ke_ka[..key.len()].to_vec(); // Encryption Key
let ka = &ke_ka[key.len()..].to_vec(); // Authentication Key

Expand All @@ -107,12 +107,7 @@ impl AesEncryptable for Message {

remove_pcks7_padding(&mut self.msg);

let ver = kmac_xof(ka, &self.msg, 512, "AES", &SecParam::D256)?;
self.op_result = match self.digest.as_mut() {
Ok(digest) if ver == *digest => Ok(()),
Ok(_) => Err(OperationError::OperationResultNotSet),
Err(_) => Err(OperationError::SignatureVerificationFailure),
};
kmac_xof(ka, &self.msg, 512, "AES", SecParam::D256);
Ok(())
}

Expand Down Expand Up @@ -143,12 +138,12 @@ impl AesEncryptable for Message {
let mut ke_ka = iv.clone();
ke_ka.extend_from_slice(&counter_bytes);
ke_ka.extend_from_slice(key);
let ke_ka = kmac_xof(&ke_ka, &[], 512, "AES", &SecParam::D256)?;
let ke_ka = kmac_xof(&ke_ka, &[], 512, "AES", SecParam::D256);

let (ke, ka) = ke_ka.split_at(key.len());

self.sym_nonce = Some(iv.clone());
self.digest = Ok(kmac_xof(ka, &self.msg, 512, "AES", &SecParam::D256)?);
self.digest = kmac_xof(ka, &self.msg, 512, "AES", SecParam::D256);

let key_schedule = AES::new(ke);

Expand Down Expand Up @@ -197,7 +192,7 @@ impl AesEncryptable for Message {
let mut ke_ka = iv.clone();
ke_ka.extend_from_slice(&counter_bytes);
ke_ka.extend_from_slice(key);
let ke_ka = kmac_xof(&ke_ka, &[], 512, "AES", &SecParam::D256)?;
let ke_ka = kmac_xof(&ke_ka, &[], 512, "AES", SecParam::D256);

let (ke, ka) = ke_ka.split_at(key.len());

Expand All @@ -217,16 +212,7 @@ impl AesEncryptable for Message {
xor_blocks(block, &temp);
});

let ver = kmac_xof(ka, &self.msg, 512, "AES", &SecParam::D256)?;
self.op_result = if let Ok(digest) = self.digest.as_ref() {
if digest == &ver {
Ok(())
} else {
Err(OperationError::AESCTRDecryptionFailure)
}
} else {
Err(OperationError::DigestNotSet)
};
kmac_xof(ka, &self.msg, 512, "AES", SecParam::D256);
Ok(())
}
}
Loading

0 comments on commit aaf00b8

Please sign in to comment.