ntrust-native 1.0

✍️ Written on 2022-05-04 in 1003 words.
Part of cs IT-security pqcrypto software-development programming-languages rustlang

Motivation

This was actually the first implementation, I reviewed and finished. But I had to wait for a consent and is published last. Today I published the 1.0 release of ntrust-native on crates.io, a pure-rust implementation of the NTRU post-quantum cryptographic scheme. NTRUst is the name of the Patrick’s WebAssembly implementation and thus ntrust-native is the implementation for the native platform.

ntrust-native

NTRU

NTRU is a third-NIST-round post-quantum key encapsulation mechanism to negotiate a secret key between two parties. For a lattice-based scheme, NTRU has a pretty long history. One might say lattice-based cryptography before 2005 only consisted of NTRU (it was named NTRUEncrypt and NTRUSign back then).

API

The API consists of some constants that define the size of buffers as well as the three KEM steps (key generation, encryption, decryption) and an RNG implementation AesState.


use ntrust_native::AesState;
use ntrust_native::{crypto_kem_dec, crypto_kem_enc, crypto_kem_keypair};
use ntrust_native::{CRYPTO_BYTES, CRYPTO_CIPHERTEXTBYTES, CRYPTO_PUBLICKEYBYTES, CRYPTO_SECRETKEYBYTES};

use hex;
use std::error;

To run the negotiation, you need to allocate all buffers and then use a proper seed (like /dev/urandom). We left out fetching a proper seed in the example code:

let mut rng = AesState::new();
let mut pk = [0u8; CRYPTO_PUBLICKEYBYTES];
let mut sk = [0u8; CRYPTO_SECRETKEYBYTES];
let mut ct = [0u8; CRYPTO_CIPHERTEXTBYTES];
let mut ss_alice = [0u8; CRYPTO_BYTES];
let mut ss_bob = [0u8; CRYPTO_BYTES];

//rng.randombytes_init([0u8; 48]);  // TODO use a proper seed (like bytes from /dev/urandom) here

Then, we run the KEM:

// Party a: generate public key `pk` and secret key `sk`
crypto_kem_keypair(&mut pk, &mut sk, &mut rng)?;
println!("[Alice]\tRunning key generation …");
println!("[Alice]\tI generated public key {}", hex::encode_upper(pk));
println!("[Alice]\tI generated secret key {}", hex::encode_upper(sk));

// Party b: generate a shared secret `ss_a` and ciphertext `ct` from the public key `pk`
crypto_kem_enc(&mut ct, &mut ss_bob, &pk, &mut rng)?;
println!("[Bob]\tRunning encapsulation …");
println!("[Bob]\tI generated shared key {}", hex::encode_upper(ss_bob));
println!("[Bob]\tI generated ciphertext {}", hex::encode_upper(ct));

// Party a: derive the same shared secret `ss_b` from the ciphertext `ct` and the secret key `sk`
crypto_kem_dec(&mut ss_alice, &ct, &sk)?;
println!("[Alice]\tRunning decapsulation …");
println!("[Alice]\tI decapsulated shared key {}", hex::encode_upper(ss_alice));

assert_eq!(ss_alice, ss_bob);

The assert shows the property of a KEM: the shared secret of both parties is the same.

Characteristics

The README file lists some properties:

  • The implementation does not utilize any concurrency techniques (SIMD/threading/…, except maybe auto-vectorization on your CPU)

  • It depends on tiny-keccak as SHA-3 implementation and aes as AES block cipher (used as RNG) implementation

  • It passes the 100 testcases of the C reference implementation

  • It implements the NTRU-HPS (Hoffstein-Pipher-Silverman) scheme in three variants

  • It implements the NTRU-HRSS (Hülsing-Rijneveld-Schanck-Schwabe) scheme in one variant

  • The implementation takes between 20 milliseconds (ntruhps2048509) and 45 milliseconds (ntruhps4096821) to run on a modern computer

  • The implementation is constant-time on software instruction level

  • The random number generator is based on AES256 in counter mode

Compared to the C reference implementation, it takes roughly 108% of the runtime.

Conclusion

I am glad to now see all three implementations useable for the public. This shows some progress in the intersection of rust and PQC. Thank you Patrick for this implementation!