diff --git a/Cargo.lock b/Cargo.lock index f1d1a5b..ea5ec50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,7 +158,7 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cas-lib" -version = "0.2.79" +version = "0.2.80" dependencies = [ "aes-gcm", "argon2", diff --git a/Cargo.toml b/Cargo.toml index 53fab45..77d36e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cas-lib" -version = "0.2.79" +version = "0.2.80" edition = "2021" description = "A function wrapper layer for RustCrypto and Dalek-Cryptography. Intended to be used in FFI situations with a global heap deallactor at the top level project." license = "Apache-2.0" diff --git a/README.md b/README.md index 6db103d..4938758 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ CAS Rust Core Library (`cas-lib`) is a unified cryptographic abstraction layer f ## Usage Examples -See practical usage and code samples in our [Examples](./docs/EXAMPLES.md). +See practical usage and code samples in our [Examples](https://github.com/Cryptographic-API-Services/cas-lib/blob/main/docs/EXAMPLES.md). ## Supported Platforms diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 6bb1779..eccaa9e 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -1,4 +1,39 @@ -# Examples +# Examples + +This document provides runnable examples for each cryptographic module in `cas-lib`. +Most examples follow the same pattern described in +[issue #7](https://github.com/Cryptographic-API-Services/cas-lib/issues/7): read a file +from disk, get its bytes, and perform the cryptographic operation on them. + +To try any of these: + +```bash +cargo new cas-demo +cd cas-demo +cargo add cas-lib +# paste an example into src/main.rs, then: +cargo run +``` + +> **Note on "Digital Signature" and "ecdsa".** These two items appeared on the +> original checklist in issue #7 but the standalone Digital Signature +> implementation was removed from the library (it did little beyond what the +> hashers already provide), and a dedicated ECDSA module was never shipped. +> Use [Ed25519 signatures](#signatures-ed25519) or +> [RSA sign/verify](#asymmetric-rsa) instead. + +## Table of Contents + +- [Password Hashers](#password-hashers) +- [Symmetric](#symmetric) +- [Hashers](#hashers) +- [Asymmetric (RSA)](#asymmetric-rsa) +- [Signatures (Ed25519)](#signatures-ed25519) +- [Key Exchange (X25519)](#key-exchange-x25519) +- [Message Authentication (HMAC)](#message-authentication-hmac) +- [Hybrid (HPKE)](#hybrid-hpke) +- [Sponges (ASCON-AEAD)](#sponges-ascon-aead) +- [Compression (Zstandard)](#compression-zstandard) ## Password Hashers ### Argon2 @@ -29,26 +64,361 @@ println!("{}", hash); ``` -## Symmetric +## Symmetric + +The symmetric module exposes AES-128-GCM, AES-256-GCM, and ChaCha20-Poly1305. +Nonces and keys are generated for you via `OsRng` — you never supply them +yourself. `encrypt_plaintext` returns the ciphertext with the authentication tag +appended; pass that same output back to `decrypt_ciphertext`. Each operation +returns a `Result`, so handle the error (here with `.unwrap()`). + ### AES-256 GCM Mode ```rust -use std::{fs::{File}, io::Write, path::Path}; +use std::{fs::File, io::Write, path::Path}; -use cas_lib::symmetric::{aes::CASAES256, cas_symmetric_encryption::CASAESEncryption}; +use cas_lib::symmetric::{aes::CASAES256, cas_symmetric_encryption::CASAES256Encryption}; fn main() { + // Read the original file from disk. let path = Path::new("MikeMulchrone_Resume2024.docx"); let file_bytes: Vec = std::fs::read(path).unwrap(); - let aes_nonce = ::generate_nonce(); - let aes_key = ::generate_key(); - let encrypted_bytes = ::encrypt_plaintext(aes_key.clone(), aes_nonce.clone(), file_bytes); - let mut file = File::create("encrypted.docx").unwrap(); + + // Generate a fresh key and nonce, then encrypt and write the ciphertext to disk. + let aes_nonce = ::generate_nonce(); + let aes_key = ::generate_key(); + let encrypted_bytes = ::encrypt_plaintext( + aes_key.clone(), + aes_nonce.clone(), + file_bytes.clone(), + ) + .unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + // Decrypt with the same key and nonce; the result matches the original file. + let decrypted_bytes = + ::decrypt_ciphertext(aes_key, aes_nonce, encrypted_bytes) + .unwrap(); + let mut file = File::create("decrypted.docx").unwrap(); file.write_all(&decrypted_bytes).unwrap(); - let path = Path::new("encrypted.docx"); + assert_eq!(file_bytes, decrypted_bytes); +} +``` + +### AES-128 GCM Mode +```rust +use std::{fs::File, io::Write, path::Path}; + +use cas_lib::symmetric::{aes::CASAES128, cas_symmetric_encryption::CASAES128Encryption}; + +fn main() { + let path = Path::new("MikeMulchrone_Resume2024.docx"); let file_bytes: Vec = std::fs::read(path).unwrap(); - let decrypted_bytes = ::decrypt_ciphertext(aes_key, aes_nonce, file_bytes); - let mut file = File::create("decrypted.docx").unwrap(); + + let aes_nonce = ::generate_nonce(); + let aes_key = ::generate_key(); + let encrypted_bytes = ::encrypt_plaintext( + aes_key.clone(), + aes_nonce.clone(), + file_bytes.clone(), + ) + .unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + let decrypted_bytes = + ::decrypt_ciphertext(aes_key, aes_nonce, encrypted_bytes) + .unwrap(); + let mut file = File::create("decrypted.docx").unwrap(); file.write_all(&decrypted_bytes).unwrap(); + + assert_eq!(file_bytes, decrypted_bytes); +} +``` + +### ChaCha20-Poly1305 +```rust +use std::{fs::File, io::Write, path::Path}; + +use cas_lib::symmetric::{ + cas_symmetric_encryption::Chacha20Poly1305Encryption, chacha20poly1305::CASChacha20Poly1305, +}; + +fn main() { + let path = Path::new("MikeMulchrone_Resume2024.docx"); + let file_bytes: Vec = std::fs::read(path).unwrap(); + + let nonce = ::generate_nonce(); + let key = ::generate_key(); + let encrypted_bytes = ::encrypt_plaintext( + key.clone(), + nonce.clone(), + file_bytes.clone(), + ) + .unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + let decrypted_bytes = + ::decrypt_ciphertext( + key, + nonce, + encrypted_bytes, + ) + .unwrap(); + let mut file = File::create("decrypted.docx").unwrap(); + file.write_all(&decrypted_bytes).unwrap(); + + assert_eq!(file_bytes, decrypted_bytes); } -``` \ No newline at end of file +``` + + +## Hashers + +The hashers module provides SHA-2 / SHA-3 style digests (256- and 512-bit) via +the `CASHasher` trait. A digest is deterministic, so hashing the same file twice +produces equal output, while two different files produce different output. The +example below reads two files from disk and demonstrates both the `true` +(identical content) and `false` (different content) comparison cases described in +issue #7. `verify_256` / `verify_512` re-hash the data and compare it against a +previously produced digest. + +```rust +use std::path::Path; + +use cas_lib::hashers::{cas_hasher::CASHasher, sha::CASSHA}; + +fn main() { + // Hash the first file. + let path = Path::new("file_a.docx"); + let file_a: Vec = std::fs::read(path).unwrap(); + let hash_a = ::hash_256(file_a.clone()); + + // Hashing the SAME file again yields the SAME digest -> true. + let hash_a_again = ::hash_256(file_a.clone()); + println!("same file matches: {}", hash_a == hash_a_again); // true + + // Hashing a DIFFERENT file yields a DIFFERENT digest -> false. + let path_b = Path::new("file_b.docx"); + let file_b: Vec = std::fs::read(path_b).unwrap(); + let hash_b = ::hash_256(file_b); + println!("different file matches: {}", hash_a == hash_b); // false + + // verify_256 re-hashes the data and checks it against an existing digest. + let verified = ::verify_256(hash_a, file_a); + println!("verify_256: {}", verified); // true + + // 512-bit variants are also available: + // ::hash_512(data); + // ::verify_512(digest, data); +} +``` + + +## Asymmetric (RSA) + +The asymmetric module wraps RSA. Keys are PEM-encoded strings. Key sizes below +2048 bits are rejected. RSA here is used for signing and verifying a document's +bytes; every operation returns a `Result`. + +```rust +use cas_lib::asymmetric::{ + cas_rsa::CASRSA, + types::{CASRSAEncryption, RSAKeyPairResult}, +}; + +fn main() { + // Generate a 2048-bit RSA key pair (PEM-encoded strings). + let key_pair: RSAKeyPairResult = CASRSA::generate_rsa_keys(2048).unwrap(); + + // Read the document to sign. + let document: Vec = std::fs::read("contract.pdf").unwrap(); + + // Sign with the private key. + let signature = CASRSA::sign(key_pair.private_key, document.clone()).unwrap(); + + // Verify with the public key -> true when the document is unmodified. + let is_valid = CASRSA::verify(key_pair.public_key, document, signature).unwrap(); + println!("signature valid: {}", is_valid); // true +} +``` + + +## Signatures (Ed25519) + +The signatures module provides Ed25519. `get_ed25519_key_pair` returns a key +pair; signing produces a 64-byte signature plus the 32-byte verification +(public) key. You can verify either with the full key pair or with just the +public key — the latter is what a remote verifier would typically hold. + +```rust +use cas_lib::signatures::ed25519::{ + ed25519_sign_with_key_pair, ed25519_verify_with_public_key, get_ed25519_key_pair, +}; + +fn main() { + // Generate a key pair. + let key_pair = get_ed25519_key_pair(); + + // Read the message/document to sign from disk. + let message: Vec = std::fs::read("message.txt").unwrap(); + + // Sign with the key pair. + let signature = ed25519_sign_with_key_pair(key_pair.key_pair, message.clone()).unwrap(); + + // A verifier holding only the public key can confirm the signature -> true. + let is_valid = + ed25519_verify_with_public_key(signature.public_key, signature.signature, message).unwrap(); + println!("signature valid: {}", is_valid); // true +} +``` + + +## Key Exchange (X25519) + +The key_exchange module implements X25519 Diffie-Hellman. Each party generates a +secret/public key pair; combining your secret key with the other party's public +key yields a shared secret that is identical on both sides. That shared secret +can then be turned into a symmetric key (see +[`key_from_x25519_shared_secret`](#symmetric) on the AES types). + +```rust +use cas_lib::key_exchange::{ + cas_key_exchange::CASKeyExchange, + x25519::{X25519, X25519SecretPublicKeyResult}, +}; + +fn main() { + // Each party generates a secret + public key pair. + let alice: X25519SecretPublicKeyResult = X25519::generate_secret_and_public_key(); + let bob: X25519SecretPublicKeyResult = X25519::generate_secret_and_public_key(); + + // Each side combines their own secret with the other's public key. + let alice_shared = X25519::diffie_hellman(alice.secret_key, bob.public_key).unwrap(); + let bob_shared = X25519::diffie_hellman(bob.secret_key, alice.public_key).unwrap(); + + // Both sides derive the SAME shared secret -> true. + println!("shared secrets match: {}", alice_shared == bob_shared); // true +} +``` + + +## Message Authentication (HMAC) + +The message module provides HMAC. `sign` produces a tag over a message using a +shared key; `verify` recomputes the tag and confirms it matches. A correct +key + message + signature triple verifies as `true`. + +```rust +use cas_lib::message::{cas_hmac::CASHMAC, hmac::HMAC}; + +fn main() { + // Shared secret key and the message bytes (read a file here in practice). + let key: Vec = vec![1, 2, 3, 4, 5]; + let message: Vec = std::fs::read("message.txt").unwrap(); + + // Produce an authentication tag. + let signature = HMAC::sign(key.clone(), message.clone()).unwrap(); + + // Verify the tag with the same key and message -> true. + let is_valid = HMAC::verify(key, message, signature).unwrap(); + println!("hmac valid: {}", is_valid); // true +} +``` + + +## Hybrid (HPKE) + +The hybrid module implements HPKE (Hybrid Public Key Encryption). The recipient +generates a key pair and an `info` string. A sender encrypts to the recipient's +public key, producing an encapsulated key, the ciphertext, and an authentication +tag. The recipient decrypts using their private key plus those three values. + +```rust +use std::path::Path; + +use cas_lib::hybrid::{cas_hybrid::CASHybrid, hpke::CASHPKE}; + +fn main() { + // Read the file to encrypt. + let file_bytes: Vec = std::fs::read(Path::new("secret.docx")).unwrap(); + + // Recipient generates a key pair and info string. + let (private_key, public_key, info_str) = CASHPKE::generate_key_pair(); + + // Sender encrypts to the recipient's public key. + let (encapped_key, ciphertext, tag) = + CASHPKE::encrypt(file_bytes.clone(), public_key, info_str.clone()).unwrap(); + + // Recipient decrypts with their private key + the encapsulated key + tag. + let decrypted_bytes = + CASHPKE::decrypt(ciphertext, private_key, encapped_key, tag, info_str).unwrap(); + + assert_eq!(file_bytes, decrypted_bytes); + println!("hpke round-trip succeeded"); +} +``` + + +## Sponges (ASCON-AEAD) + +The sponges module provides ASCON-AEAD, a lightweight authenticated cipher. As +with the symmetric module, the key and nonce are generated for you, and +`encrypt` / `decrypt` round-trip the file bytes. + +```rust +use std::{fs::File, io::Write, path::Path}; + +use cas_lib::sponges::{ascon_aead::AsconAead, cas_ascon_aead::CASAsconAead}; + +fn main() { + let path = Path::new("secret.docx"); + let file_bytes: Vec = std::fs::read(path).unwrap(); + + let nonce = ::generate_nonce(); + let key = ::generate_key(); + + let encrypted_bytes = + ::encrypt(key.clone(), nonce.clone(), file_bytes.clone()) + .unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + let decrypted_bytes = + ::decrypt(key, nonce, encrypted_bytes).unwrap(); + let mut file = File::create("decrypted.docx").unwrap(); + file.write_all(&decrypted_bytes).unwrap(); + + assert_eq!(file_bytes, decrypted_bytes); +} +``` + + +## Compression (Zstandard) + +The compression module wraps Zstandard. `compress` takes the data and a +compression level (0-22; higher means smaller but slower); `decompress` restores +the original bytes exactly. + +```rust +use cas_lib::compression::zstd::{compress, decompress}; + +fn main() { + // Read a file from disk. + let original: Vec = std::fs::read("large_log.txt").unwrap(); + + // Compress at level 9. + let compressed: Vec = compress(original.clone(), 9).unwrap(); + println!( + "compressed {} bytes down to {} bytes", + original.len(), + compressed.len() + ); + + // Decompress back to the original bytes. + let decompressed: Vec = decompress(compressed).unwrap(); + assert_eq!(original, decompressed); +} +```