Skip to content

Commit

Permalink
Merge pull request #64 from drcapybara/feat/ml-kem-encryptions
Browse files Browse the repository at this point in the history
feat: docs and fallible encryptions
  • Loading branch information
Dustin-Ray committed Jun 29, 2024
2 parents 9d95992 + 94c0e64 commit f8c2701
Show file tree
Hide file tree
Showing 14 changed files with 119 additions and 131 deletions.
67 changes: 1 addition & 66 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
[package]
name = "capycrypt"
version = "0.7.0"
version = "0.7.1"
edition = "2021"

license = "MIT"
keywords = ["aes", "sha3", "elliptic-curve", "ed448", "mlkem"]
keywords = ["cryptography", "aes", "sha3", "ed448", "mlkem"]
readme = "README.md"
authors = ["Dustin Ray (Dr. Capybara) <dustinray313@gmail.com>", "Hunter Richardson (HLRichardson-Git) <hunter@hunterrichardson.net>"]
description = "An academic exercise in cryptographic algorithm design, pairing NIST FIPS 202 with a variety of Edwards curves."
description = "An academic exercise in cryptographic algorithm design."
repository = "https://github.com/drcapybara/capyCRYPT"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
num = {version = "0.4.0"}
hex = {version = "0.4.3"}
byteorder = {version = "1.4.3"}
chrono = {version = "0.4.23"}
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ let mut msg = Message::new(get_random_bytes(5242880));
// 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());
msg.kem_encrypt(&kem_pub_key, SecParam::D256);
// Decrypt and verify
assert!(msg.kem_decrypt(&kem_priv_key).is_ok());
```
Expand All @@ -81,7 +81,7 @@ let key_pair = KeyPair::new(
SecParam::D256, // bit-security for key
);
// Encrypt the message
assert!(msg.key_encrypt(&key_pair.pub_key, SecParam::D256).is_ok());
msg.key_encrypt(&key_pair.pub_key, SecParam::D256);
// Decrypt and verify
assert!(msg.key_decrypt(&key_pair.priv_key).is_ok());
```
Expand All @@ -99,11 +99,11 @@ 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());
msg.aes_encrypt_ctr(&pw);
// Decrypt the data
assert!(msg.aes_decrypt_ctr(&pw).is_ok());
// Encrypt the data
assert!(msg.sha3_encrypt(&pw, SecParam::D512).is_ok());
msg.sha3_encrypt(&pw, SecParam::D512);
// Decrypt and verify
assert!(msg.sha3_decrypt(&pw).is_ok());
```
Expand All @@ -124,7 +124,7 @@ let key_pair = KeyPair::new(
SecParam::D256, // bit-security for key
);
// Sign with 128 bits of security
assert!(msg.sign(&key_pair, SecParam::D256).is_ok());
msg.sign(&key_pair, SecParam::D256);
// Verify signature
assert!(msg.verify(&key_pair.pub_key).is_ok());
```
Expand All @@ -151,8 +151,11 @@ 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.
This library pairs ML-KEM-768 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. This design seeds the SHA-3 sponge with the secret shared through the KEM + a session nonce, which then faciliates high-performance symmetric encryption/decryption of arbitrary-length messages.

Our construction is non-standard, has not been subject to peer review, and lacks any formal audit. Our [ML-KEM library](https://github.com/drcapybara/capyKEM) itself is a work in progress and only supports the recommended NIST-II security parameter-set 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 security, reliability, or efficiency.

## Acknowledgements
The authors wish to sincerely thank Dr. Paulo Barreto for the initial design of this library as well as the theoretical backbone of the Edward's 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.

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.
Our [KEM](https://github.com/drcapybara/capyKEM) is inspired by the excellent ML-KEM articles and [go implementation](https://pkg.go.dev/filippo.io/mlkem768) by Filippo Valsorda and the always wonderful rust-crypto implementation by the great Tony Arcieri [here](https://crates.io/crates/ml-kem).
4 changes: 2 additions & 2 deletions benches/benchmark_aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ fn sym_enc_rust_aes(key: &[u8], data: &[u8]) -> Vec<u8> {
/// Symmetric encrypt and decrypt roundtrip
fn sym_cbc_enc(key: &[u8], data: &[u8]) {
let mut msg = Message::new(data.to_owned());
let _ = msg.aes_encrypt_cbc(key);
msg.aes_encrypt_cbc(key);
let _ = msg.aes_decrypt_cbc(key);
}

/// Symmetric encrypt and decrypt roundtrip for AES in CTR mode
fn sym_ctr_enc(key: &[u8], data: &[u8]) {
let mut msg = Message::new(data.to_owned());
let _ = msg.aes_encrypt_ctr(key);
msg.aes_encrypt_ctr(key);
let _ = msg.aes_decrypt_ctr(key);
}

Expand Down
6 changes: 3 additions & 3 deletions benches/benchmark_e448_224.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ 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);
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);
let _ = msg.key_encrypt(&key_pair.pub_key, BIT_SECURITY);
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);
msg.sign(&key_pair, BIT_SECURITY);
let _ = msg.verify(&key_pair.pub_key);
}

Expand Down
6 changes: 3 additions & 3 deletions benches/benchmark_e448_512.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ 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);
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);
let _ = msg.key_encrypt(&key_pair.pub_key, BIT_SECURITY);
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);
msg.sign(&key_pair, BIT_SECURITY);
let _ = msg.verify(&key_pair.pub_key);
}

Expand Down
11 changes: 4 additions & 7 deletions src/aes/encryptable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use crate::{
use rayon::prelude::*;

pub trait AesEncryptable {
fn aes_encrypt_cbc(&mut self, key: &[u8]) -> Result<(), OperationError>;
fn aes_encrypt_cbc(&mut self, key: &[u8]);
fn aes_decrypt_cbc(&mut self, key: &[u8]) -> Result<(), OperationError>;
fn aes_encrypt_ctr(&mut self, key: &[u8]) -> Result<(), OperationError>;
fn aes_encrypt_ctr(&mut self, key: &[u8]);
fn aes_decrypt_ctr(&mut self, key: &[u8]) -> Result<(), OperationError>;
}

Expand All @@ -31,7 +31,7 @@ impl AesEncryptable for Message {
/// - C: Represents ciphertext blocks.
/// ## Arguments:
/// * `key: &Vec<u8>`: symmetric encryption key.
fn aes_encrypt_cbc(&mut self, key: &[u8]) -> Result<(), OperationError> {
fn aes_encrypt_cbc(&mut self, key: &[u8]) {
let iv = get_random_bytes(16);
let mut ke_ka = iv.clone();
ke_ka.append(&mut key.to_owned());
Expand All @@ -56,7 +56,6 @@ impl AesEncryptable for Message {
}

self.sym_nonce = Some(iv);
Ok(())
}

/// # Symmetric Decryption using AES in CBC Mode
Expand Down Expand Up @@ -130,7 +129,7 @@ impl AesEncryptable for Message {
/// - C: Represents ciphertext blocks.
/// ## Arguments:
/// * `key: &[u8]`: symmetric encryption key.
fn aes_encrypt_ctr(&mut self, key: &[u8]) -> Result<(), OperationError> {
fn aes_encrypt_ctr(&mut self, key: &[u8]) {
let iv = get_random_bytes(12);
let counter = 0u32;
let counter_bytes = counter.to_be_bytes();
Expand Down Expand Up @@ -160,8 +159,6 @@ impl AesEncryptable for Message {

xor_blocks(block, &temp);
});

Ok(())
}
/// # Symmetric Decryption using AES in CTR Mode
/// Decrypts a [`Message`] using the AES algorithm in CTR (Counter) mode.
Expand Down
5 changes: 2 additions & 3 deletions src/ecc/encryptable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
use tiny_ed448_goldilocks::curve::{extended_edwards::ExtendedPoint, field::scalar::Scalar};

pub trait KeyEncryptable {
fn key_encrypt(&mut self, pub_key: &ExtendedPoint, d: SecParam) -> Result<(), OperationError>;
fn key_encrypt(&mut self, pub_key: &ExtendedPoint, d: SecParam);
fn key_decrypt(&mut self, pw: &[u8]) -> Result<(), OperationError>;
}

Expand All @@ -31,7 +31,7 @@ impl KeyEncryptable for Message {
/// * pub_key: [`ExtendedPoint`] : X coordinate of public key 𝑉
/// * d: u64: Requested security strength in bits. Can only be 224, 256, 384, or 512.
#[allow(non_snake_case)]
fn key_encrypt(&mut self, pub_key: &ExtendedPoint, d: SecParam) -> Result<(), OperationError> {
fn key_encrypt(&mut self, pub_key: &ExtendedPoint, d: SecParam) {
self.d = Some(d);
let k = bytes_to_scalar(&get_random_bytes(56)).mul_mod(&Scalar::from(4_u64));
let w = (*pub_key * k).to_affine();
Expand All @@ -47,7 +47,6 @@ impl KeyEncryptable for Message {

self.digest = t;
self.asym_nonce = Some(Z.to_extended());
Ok(())
}

/// # Asymmetric Decryption
Expand Down
5 changes: 2 additions & 3 deletions src/ecc/signable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tiny_ed448_goldilocks::curve::{extended_edwards::ExtendedPoint, field::scala
use super::keypair::KeyPair;

pub trait Signable {
fn sign(&mut self, key: &KeyPair, d: SecParam) -> Result<(), OperationError>;
fn sign(&mut self, key: &KeyPair, d: SecParam);
fn verify(&mut self, pub_key: &ExtendedPoint) -> Result<(), OperationError>;
}

Expand All @@ -38,7 +38,7 @@ impl Signable for Message {
/// * key: &[`KeyPair`], : reference to KeyPair.
/// * d: u64: encryption security strength in bits. Can only be 224, 256, 384, or 512.
#[allow(non_snake_case)]
fn sign(&mut self, key: &KeyPair, d: SecParam) -> Result<(), OperationError> {
fn sign(&mut self, key: &KeyPair, d: SecParam) {
let s_bytes = kmac_xof(&key.priv_key, &[], 448, "SK", d);
let s = bytes_to_scalar(&s_bytes).mul_mod(&Scalar::from(4_u64));
let s_bytes = scalar_to_bytes(&s);
Expand All @@ -55,7 +55,6 @@ impl Signable for Message {
let z = k - h_big.mul_mod(&s);
self.sig = Some(Signature { h, z });
self.d = Some(d);
Ok(())
}

/// # Signature Verification
Expand Down
37 changes: 33 additions & 4 deletions src/kem/encryptable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,31 @@ use rand::{thread_rng, RngCore};
use super::keypair::{KEMPrivateKey, KEMPublicKey};

pub trait KEMEncryptable {
fn kem_encrypt(&mut self, key: &KEMPublicKey, d: SecParam) -> Result<(), OperationError>;
fn kem_encrypt(&mut self, key: &KEMPublicKey, d: SecParam);
fn kem_decrypt(&mut self, key: &KEMPrivateKey) -> Result<(), OperationError>;
}

impl KEMEncryptable for Message {
fn kem_encrypt(&mut self, key: &KEMPublicKey, d: SecParam) -> Result<(), OperationError> {
/// # Key Encapsulation Mechanism (KEM) Encryption
/// Encrypts a [`Message`] symmetrically under a KEM public key 𝑉. The KEM keys
/// are used to derive a shared secret which seeds the sponge, and is then
/// subsequently used for symmetric encryptions.
/// ## Replaces:
/// * `Message.kem_ciphertext` with the result of encryption using KEM public key 𝑉.
/// * `Message.digest` with the keyed hash of the message using components derived from the encryption process.
/// * `Message.sym_nonce` with random bytes 𝑧.
/// ## Algorithm:
/// * Generate a random secret.
/// * Encrypt the secret using the KEM public key 𝑉 to generate
/// shared secret.
/// * Generate a random nonce 𝑧
/// * (ke || ka) ← kmac_xof(𝑧 || secret, "", 1024, "S")
/// * 𝑐 ← kmac_xof(ke, "", |m|, "SKE") ⊕ m
/// * t ← kmac_xof(ka, m, 512, "SKA")
/// ## Arguments:
/// * `key: &KEMPublicKey`: The public key 𝑉 used for encryption.
/// * `d: SecParam`: Security parameters defining the strength of cryptographic operations.
fn kem_encrypt(&mut self, key: &KEMPublicKey, d: SecParam) {
self.d = Some(d);

let mut rng = thread_rng();
Expand All @@ -44,15 +63,25 @@ impl KEMEncryptable for Message {
xor_bytes(&mut self.msg, &m);

self.sym_nonce = Some(z);
Ok(())
}

/// # Key Encapsulation Mechanism (KEM) Decryption
/// Decrypts a [`Message`] using a KEM private key.
/// ## Replaces:
/// * `Message.msg` with the result of decryption.
/// * `Message.op_result` with the result of the comparison of the stored and computed message digests.
/// ## Algorithm:
/// * Retrieve the KEM ciphertext and decrypt it using the KEM private key to obtain the decrypted secret.
/// * Use the stored nonce 𝑧 and decrypted secret to derive two keys (ke and ka) using `kmac_xof`.
/// * m ← kmac_xof(ke, "", |c|, "SKE") ⊕ c
/// * t′ ← kmac_xof(ka, m, 512, "SKA")
/// ## Arguments:
/// * `key: &KEMPrivateKey`: The private key used for decryption.
fn kem_decrypt(&mut self, key: &KEMPrivateKey) -> Result<(), OperationError> {
let ciphertext = self
.kem_ciphertext
.as_ref()
.ok_or(OperationError::EmptyDecryptionError)?;

let dec = k_pke_decrypt::<KEM_768>(&key.dk, ciphertext);

let d = self.d.ok_or(OperationError::SecurityParameterNotSet)?;
Expand Down
Loading

0 comments on commit f8c2701

Please sign in to comment.