CryptoCore

Build status Build status latest version

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:

This crate may disappear in the future, as the RustCrypto libraries evolve.

Using

To use Cosmian CryptoCore, add the dependency using Cargo:

bash cargo add cosmian_crypto_core

Example usages

This document provides examples of the most common use cases. All the examples are available in the examples directory

Building

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

Further improving performance

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'

Running tests and benchmarks

Run tests using:

bash cargo test --release

Run benchmarks using:

bash cargo bench

Symmetric key encryption

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.

Symmetric key encryption of a vector of bytes in combined mode

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"); ```

Symmetric key encryption of a vector of bytes in detached mode

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"); ```

Symmetric key encryption of a stream of bytes

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" ); ```

ECIES - Elliptic Curve Integrated Encryption Scheme

The library exposes 3 ECIES schemes based on the Dalek implementation of curve 25519

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_cypto folder.

ECIES encryption of a vector of bytes

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[..]); ```

ECIES encryption of a stream of bytes

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" ); ```

Signature

The crate currently exposes the EdDSA (Ed25519) signature scheme. More signature schemes will be exposed soon.

Static implementation

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(); ```

Cached implementation

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(); ```

Using a Keypair

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(); ```

Cryptographically Secure Random Number Generator (CS-RNG)

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();

Key Derivation Function (KDF)

This crate uses the pure rust implementation of the SHAKE algorithm from the sha3 crate. Two implementations are available:

Both algorithms run in less than 500ns on a 2.7GHz Intel Core i7.

```Rust

[macro_use]

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()); ```

Blake2 hashing

The crate exposes as macros two versions of Blake2 which is specified in RFC 7693

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

[macro_use]

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()); ```

Code documentation

The documentation can be generated using Cargo:

bash cargo docs

It is also available on doc.rs.