This crate implements the cryptographic primitives (modern encryption and signature schemes) used in many other Cosmian products, such as the Cloudproof libraries and the KMS.
It is primarily a thin layer over the RustCrypto libraries, exposing as far as possible more straightforward and more consistent Traits, for example the lib:
GenericArray
types and uses [u8; N]
arrays only,libsodium
compatibility,This crate may disappear in the future, as the RustCrypto libraries evolve.
To use Cosmian CryptoCore, add the dependency using Cargo:
bash
cargo add cosmian_crypto_core
This document provides examples of the most common use cases. All the examples are available in the examples directory
To install and build Cosmian CryptoCore, clone the repo:
bash
git clone https://github.com/Cosmian/crypto_core.git
Build it with Cargo:
bash
cargo build --release
Running the nightly backend with the following flags (SIMD instructions) will improve performance by about 33% on the Ristretto backend, 25% for AES GCM, and 15% on the ED25519 backend
sh
RUSTFLAGS='--cfg curve25519_dalek_backend="simd" -C target_cpu=native'
Run tests using:
bash
cargo test --release
Run benchmarks using:
bash
cargo bench
This crate offers standardized authenticated encryption schemes: AES-GCM - with 128 and 256 keys -, Chacha20 Poly1305, and XChacha Poly1305. All these primitives are available in three modes: vector-combined, vector-detached, and streaming.
In combined and detached modes, AES 256 GCM, Chacha20-Poly1305, and XChachaPoly1305 are compatible with their IETF equivalent in libsodium
. Check this documentation for details of the libsodium
implementations.
The AES version uses AES-NI instructions when available, which is the case on most modern processors. All these primitives are fast and encrypt a vector of bytes with authentication in an average of 2.5µs on a 2.6GHz Intel Core i7.
In combined mode, the primitive encrypts a plaintext as a single vector of bytes and allocates a new vector for the ciphertext that holds the encrypted data and the MAC.
```Rust use cosmiancryptocore::XChaCha20Poly1305; use cosmiancryptocore::{ reexport::rand_core::SeedableRng, CsRng, Dem, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, SymmetricKey, };
// Choose one of these DEMs depending on your use case // type SC = Aes128Gcm; // type SC = Aes256Gcm; // type SC = ChaCha20Poly1305; type SC = XChaCha20Poly1305;
// A cryptographically secure random generator let mut rng = CsRng::from_entropy();
// the message to encrypt let message = b"my secret message"; // the secret key used to encrypt the message // which is shared between the sender and the recipient let secret_key = SymmetricKey::new(&mut rng);
// the additional data shared between the sender and the recipient to authenticate the message let additionaldata = Some(b"additional data".asslice());
// the sender generate a Nonce and encrypts the message let nonce = Nonce::new(&mut rng); let dem = SC::new(&secretkey); let ciphertext = dem.encrypt(&nonce, message, additionaldata).unwrap();
// to transmit the message, the sender can concatenate the nonce and the ciphertext // and send the concatenated result to the recipient let ciphertext = [nonce.asbytes(), ciphertext.asslice()].concat();
// the ciphertext size is the message size plus the nonce size plus the authentication tag size asserteq!( ciphertext.len(), message.len() + SC::NONCELENGTH + SC::MAC_LENGTH );
// the recipient extracts the nonce and decrypts the message let nonce = Nonce::tryfromslice(&ciphertext[..SC::NONCELENGTH]).unwrap(); let dem = SC::new(&secretkey); let plaintext = dem .decrypt(&nonce, &ciphertext[SC::NONCELENGTH..], additionaldata) .unwrap();
// assert the decrypted message is identical to the original plaintext assert_eq!(plaintext, message, "Decryption failed"); ```
In combined mode, the primitive encrypts a plaintext as a single mutable vector of bytes in place; it returns the MAC tag on the encryption call.
```Rust use cosmiancryptocore::DemInPlace; use cosmiancryptocore::{ reexport::rand_core::SeedableRng, CsRng, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, SymmetricKey, XChaCha20Poly1305, };
// Choose one of these DEMs depending on your use case // type SC = Aes128Gcm; // type SC = Aes256Gcm; // type SC = ChaCha20Poly1305; type SC = XChaCha20Poly1305;
// A cryptographically secure random generator let mut rng = CsRng::from_entropy();
// the message to encrypt let message = b"my secret message"; // the secret key used to encrypt the message // which is shared between the sender and the recipient let secret_key = SymmetricKey::new(&mut rng);
// the additional data shared between the sender and the recipient to authenticate the message let additionaldata = Some(b"additional data".asslice());
// the sender generate a Nonce and encrypts the message in place // the encryption method returns the tag/MAC let mut bytes = message.tovec(); let nonce = Nonce::new(&mut rng); let dem = SC::new(&secretkey); let tag = dem .encryptinplacedetached(&nonce, &mut bytes, additionaldata) .unwrap();
// to transmit the message, the sender can concatenate the nonce, the encrypted data and the MAC // then send the concatenated result to the recipient let ciphertext = [nonce.asbytes(), bytes.asslice(), tag.as_slice()].concat();
// the ciphertext size is the message size plus the nonce size plus the authentication tag size asserteq!( ciphertext.len(), message.len() + SC::NONCELENGTH + SC::MAC_LENGTH );
// the recipient extracts the nonce, message and the tag/MAC then decrypt the message in place let nonce = Nonce::tryfromslice(&ciphertext[..SC::NONCELENGTH]).unwrap(); let mut bytes = ciphertext[SC::NONCELENGTH..ciphertext.len() - SC::MACLENGTH].tovec(); let tag = ciphertext[ciphertext.len() - SC::MACLENGTH..].tovec();
let dem = SC::new(&secretkey); dem.decryptinplacedetached(&nonce, &mut bytes, &tag, additional_data) .unwrap();
// assert the decrypted message is identical to the original plaintext asserteq!(bytes.asslice(), message, "Decryption failed"); ```
The library exposes the 2 streaming DEMs offered in the RustCrypto AEAD crate which are based on the authenticated encryption construction as described in the paper Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance
```Rust use cosmiancryptocore::XChaCha20Poly1305; use cosmiancryptocore::{ reexport::{aead::Payload, rand_core::SeedableRng}, CsRng, DemStream, Instantiable, Nonce, RandomFixedSizeCBytes, SymmetricKey, };
// Choose one of these streaming DEMs depending on your use case // type SC = Aes128Gcm; // type SC = Aes256Gcm; // type SC = ChaCha20Poly1305; type SC = XChaCha20Poly1305;
let message = b"Hello, World!";
// The message will be encrypted in 2 chunks, one of size 8 and one of size 5 // In real life, the block size should be much larger and typically a multiple of 4096 const BLOCK_SIZE: usize = 8;
// use some additional data to authenticate the message let aad = b"the aad";
// generate a random key and nonce let mut rng = CsRng::fromentropy(); let secretkey = SymmetricKey::new(&mut rng); let nonce = Nonce::new(&mut rng);
// Instantiate a streaming encryptor // Two streaming encryptor are available: EncryptorBE32 and EncryptorLE31 // Check the documentation of the DemStream trait for more details let mut encryptor = SC::new(&secretkey).intostreamencryptorbe32(&nonce);
// Encrypt the first chunk
// Encryption of all chunks except the last should use encrypt_next
let mut ciphertext = encryptor
.encryptnext(Payload {
msg: &message[..BLOCKSIZE],
aad,
})
.unwrap();
// Encrypt the second and last chunk using encrypt_last
ciphertext.extendfromslice(
&encryptor
.encryptlast(Payload {
msg: &message[BLOCKSIZE..],
aad,
})
.unwrap(),
);
// decryption
// Instantiate a streaming decryptor let mut decryptor = SC::new(&secretkey).intostreamdecryptorbe32(&nonce);
// Decrypt the first chunk which is BLOCKSIZE + MACLENGTH bytes long
// Decryption of all chunks except the last should use decrypt_next
let mut plaintext = decryptor
.decryptnext(Payload {
msg: &ciphertext[..BLOCKSIZE + SC::MAC_LENGTH],
aad,
})
.unwrap();
// decrypt the second and last chunk plaintext.extendfromslice( &decryptor .decryptlast(Payload { msg: &ciphertext[BLOCKSIZE + SC::MAC_LENGTH..], aad, }) .unwrap(), );
asserteq!( message.asslice(), plaintext.as_slice(), "Decryption failed" ); ```
The library exposes 3 ECIES schemes based on the Dalek implementation of curve 25519
EciesX25519XChaCha20
: which uses the X25519 KEM and the XChaCha20 Poly1305 DEM; it uses HChaCha for the ephemeral symmetric key derivation and Blake2b for the nonce generation. In case of doubt, this is the recommended scheme.EciesSalsaSealBox
: which uses the X25519 KEM and the Salsa20 Poly1305 DEM. This scheme is compatible with libsodium
sealed boxes but does not offer support for additional data in the DEM authentication.EciesR25519Aes128
: which KEM is based on the Ristretto group of curve 25519 and uses Aes 128 GCM as a DEM. Both the derivation of the ephemeral symmetric key and the generation of the nonce is performed using Shake 128.All these implementations have similar performance (about 70µs for encryption/decryption on a 2.6GHz Intel Core i7) and security (128-bit classic security - no post-quantum resistance).
A later version offering a hybrid encryption scheme with post-quantum resistance will be released.
All these implementations use the same format for the Curve 25519 Private key; their public key implementations under the various representations (Edwards, Montgomery, and Ristretto) are available in the asymmetric_crypto
folder.
The following example shows how to encrypt a vector of bytes using ECIES X25519 (KEM) combined with XChaCha20 Poly1305 (DEM). It also demonstrates the use of additional data in the DEM authentication.
Encryption is performed using the public key and decryption using the private key.
```Rust use cosmiancryptocore::{ reexport::rand_core::SeedableRng, CsRng, Ecies, EciesX25519XChaCha20, RandomFixedSizeCBytes, X25519PrivateKey, X25519PublicKey, };
// A cryptographic random number generator let mut rng = CsRng::from_entropy();
// Generate a key pair let privatekey = X25519PrivateKey::new(&mut rng); let publickey = X25519PublicKey::from(&private_key);
// The plain text to encrypt let plaintext = b"Hello, World!";
// Some optional authenticated data for theDEM scheme let authenticated_data = b"Optional authenticated data";
// Encrypt the message with the public key using ECIES X25519 XChaCha20 let ciphertext = EciesX25519XChaCha20::encrypt(&mut rng, &publickey, plaintext, Some(authenticateddata)) .unwrap();
// Decrypt the message using the private key let plaintext_ = EciesX25519XChaCha20::decrypt(&privatekey, &ciphertext, Some(authenticateddata)).unwrap();
// Check if the decrypted message is the same as the original message asserteq!(plaintext, &plaintext[..]); ```
The following example shows how to encrypt a stream of bytes using ECIES X25519 (KEM) combined with XChaCha20 Poly1305 (DEM).
```Rust use aead::Payload; use cosmiancryptocore::{ reexport::rand_core::SeedableRng, CsRng, EciesStream, EciesX25519XChaCha20, FixedSizeCBytes, RandomFixedSizeCBytes, X25519PrivateKey, X25519PublicKey, XChaCha20Poly1305, };
// generate a random key and nonce let mut rng = CsRng::from_entropy();
// generate a key pair let privatekey = X25519PrivateKey::new(&mut rng); let publickey = X25519PublicKey::from(&private_key);
// The plain text to encrypt let message = b"Hello, World!";
// Some optional authenticated data for theDEM scheme let authenticated_data = b"Optional authenticated data";
// there will be 2 chunks for the message, one of size 8 and one of size 5 const BLOCK_SIZE: usize = 8;
let (ephemeralpublickey, mut encryptor) = EciesX25519XChaCha20::getdemencryptorbe32(&mut rng, &publickey).unwrap();
// prepend the ciphertext with the ephemeral public key let mut ciphertext = ephemeralpublickey.tobytes().tovec();
// encrypt the first chunk ciphertext.extend( encryptor .encryptnext(Payload { msg: &message[..BLOCKSIZE], aad: authenticated_data, }) .unwrap(), );
// encrypt the second and last chunk ciphertext.extendfromslice( &encryptor .encryptlast(Payload { msg: &message[BLOCKSIZE..], aad: authenticated_data, }) .unwrap(), );
// decryption
//recover the ephemeral public key from the ciphertext let ephemeralpublickey = X25519PublicKey::tryfromslice(&ciphertext[..X25519PublicKey::LENGTH]).unwrap();
// Instantiate a decryptor let mut decryptor = EciesX25519XChaCha20::getdemdecryptorbe32(&privatekey, &ephemeralpublickey).unwrap();
// decrypt the first chunk which is BLOCKSIZE + MACLENGTH bytes long let mut plaintext = decryptor .decryptnext(Payload { msg: &ciphertext[X25519PublicKey::LENGTH ..X25519PublicKey::LENGTH + BLOCKSIZE + XChaCha20Poly1305::MACLENGTH], aad: authenticateddata, }) .unwrap();
// decrypt the second and last chunk plaintext.extendfromslice( &decryptor .decryptlast(Payload { msg: &ciphertext [X25519PublicKey::LENGTH + BLOCKSIZE + XChaCha20Poly1305::MACLENGTH..], aad: authenticateddata, }) .unwrap(), );
asserteq!( message.asslice(), plaintext.as_slice(), "Decryption failed" ); ```
The crate currently exposes the EdDSA (Ed25519) signature scheme. More signature schemes will be exposed soon.
Using the static signature implementation, signature and verification are performed in about 50µs on a 2.6 GHz Intel Core i7.
```Rust use cosmiancryptocore::{ reexport::{ rand_core::SeedableRng, signature::{Signer, Verifier}, }, CsRng, Ed25519PrivateKey, Ed25519PublicKey, RandomFixedSizeCBytes, };
// instantiate a random number generator let mut rng = CsRng::from_entropy();
// the message to sign let message = b"Hello, world!";
// sign the message with the private key let privatekey = Ed25519PrivateKey::new(&mut rng); let signature = privatekey.try_sign(message).unwrap();
// verify the signature with the public key let publickey = Ed25519PublicKey::from(&privatekey); public_key.verify(message, &signature).unwrap(); ```
Using the cached signature implementation, the signature is performed in about 25µs, verification is still performed in about 50µs on a 2.6 GHz Intel Core i7.
```Rust use cosmiancryptocore::{ reexport::{ rand_core::SeedableRng, signature::{Signer, Verifier}, }, Cached25519Signer, CsRng, Ed25519PrivateKey, Ed25519PublicKey, RandomFixedSizeCBytes, };
// instantiate a random number generator let mut rng = CsRng::from_entropy();
// instantiate the cached signer let privatekey = Ed25519PrivateKey::new(&mut rng); let cachedsigner = Cached25519Signer::tryfrom(&privatekey).unwrap();
// verify the signatures let publickey = Ed25519PublicKey::from(&privatekey);
let message = b"Hello, world!"; let signature = cachedsigner.trysign(message).unwrap(); public_key.verify(message, &signature).unwrap();
let message = b"I'm sorry, Dave. I'm afraid I can't do that."; let signature = cachedsigner.trysign(message).unwrap(); public_key.verify(message, &signature).unwrap(); ```
The signature API also exposes a Keypair
compatible with that of the RustCrypto implementation.
```Rust use cosmiancryptocore::{ reexport::{ randcore::SeedableRng, signature::{Signer, Verifier}, }, CsRng, Ed25519Keypair, FixedSizeCBytes, }; let mut rng = CsRng::fromentropy(); let message = b"Hello, world!";
// generate a keypair let keypair = Ed25519Keypair::new(&mut rng);
// serialize the keypair let serializedkeypair = keypair.tobytes();
// deserialize the keypair let keypair = Ed25519Keypair::tryfrombytes(serialized_keypair).unwrap();
//assert equality asserteq!(keypair.tobytes(), serialized_keypair);
// sign the message using the keypair let signature = keypair.try_sign(message).unwrap();
// verify the signature keypair.verify(message, &signature).unwrap(); ```
This crate exposes a cryptographically secure random number generator (CS-RNG) that uses the implementation of the CHACHA algorithm with 12 rounds from the rand_chacha
.
It is 128 bits secure.
Performance: 1.7µs per instantiation. The next pseudo-random number is generated in nanoseconds.
Rust
use cosmian_crypto_core::CsRng;
let mut rng = CsRng::from_entropy();
This crate uses the pure rust implementation of the SHAKE algorithm from the sha3 crate. Two implementations are available:
kdf128
which is 128-bit secure (in the classic setting) for input sizes of at least 256 bits (32 bytes).kdf256
which is 256-bit secure (in the classic setting) for input sizes of at least 512 bits (64 bytes).Both algorithms run in less than 500ns on a 2.7GHz Intel Core i7.
```Rust
use cosmiancryptocore::kdf256;
const KEY_LENGTH: usize = 32;
const ikm: &str = "asdf34@!dsa@grq5e$2ASGy5";
// derive a 32-bytes key let key = kdf256!(KEYLENGTH, ikm.asbytes());
asserteq!(KEYLENGTH, key.len()); ```
The crate exposes as macros two versions of Blake2 which is specified in RFC 7693
blake2s
: is optimized for 8 to 32-bit platforms and produces digests of any size between 1 and 32 bytes (256 bits)blake2b
: is optimized for 64-bit platforms and produces digests of any size between 1 and 64 bytes (512 bits)Blake2 runs in less than 200ns, which is about 3 times faster than Sha3 and Shake on most hardware but may fail when used to generate hashes of variable length, in particular when the input length is too small.
```Rust
use cosmiancryptocore::{blakebs, CryptoCoreError};
const LENGTH: usize = 12;
let msg1 = b"asdf34@!dsa@grq5e$2ASGy5"; let msg2 = b"oiu54%6uhg1@34"; let res = blake2b!(LENGTH, msg1, msg2).unwrap();
assert_eq!(LENGTH, res.len()); ```
The documentation can be generated using Cargo:
bash
cargo docs
It is also available on doc.rs.