96-bit Nonces Too Small? Try XChaCha20 and Rust

With symmetric-key, Bob and Alice have the same key. Rust can use the XChaCha20 method, and which supports stream encryption (and which…

Photo by Maria Ziegler on Unsplash

96-bit Nonces Too Small? Try XChaCha20 and Rust

With symmetric-key, Bob and Alice have the same key. Rust can use the XChaCha20 method, and which supports stream encryption (and which does not require padding as a block cipher does, and is also faster than block cipher modes). ChaCha20 was created by Daniel J. Bernstein and has an eight-byte or 16-byte nonce. XChaCha20 (eXtended-nonce ChaCha) is an update to ChaCha20, and uses a 24 byte (192-bit) nonce. It was created by S. Arciszewski:

XChaCha20 has a lower probability of nonce misuse than ChaCha20. The cipher text is made up of the cipher message (and which is the same length as the plaintext message) is the same number of bytes as the message (five bytes), and that the cipher text has an extra 16 bytes (used for AEAD — Authenticated Encryption with Associated Data). The MAC bytes used Poly1305 and provide an integrity check for the cipher.

An outline of the Rust code is [here]:

extern crate base64;
extern crate hex;
extern crate crypto;
use crypto::{symmetriccipher::{ SynchronousStreamCipher}};
use rustc_serialize::hex::FromHex;
use std::env;
use core::str;
use std::iter::repeat;
fn hex_to_bytes(s: &str) -> Vec<u8> {
s.from_hex().unwrap()
}
fn main() {
let mut mykey="0000000000000000000000000000000000000000000000000000000000000000";
let mut msg="Hello";
let mut myiv="000000000000000000000000000000000000000000000000";
let args: Vec<String> = env::args().collect();

if args.len() >1 { msg = args[1].as_str();}
if args.len() >2 { mykey = args[2].as_str();}
if args.len() >3 { myiv = args[3].as_str();}
println!("== XChaCha20 ==");
println!("Message: {:?}",msg);
println!("Key: {:?}",mykey);
println!("IV: {:?}",myiv);
let key=&hex_to_bytes( mykey)[..];
let iv=&hex_to_bytes( myiv)[..];
let plain=msg.as_bytes();
// Encrypting
let mut c = crypto::chacha20::ChaCha20::new_xchacha20(&key, iv);
let mut output: Vec<u8> = repeat(0).take(plain.len()).collect();
c.process(&plain[..], &mut output[..]);
println!("\nEncrypted: {}",hex::encode(output.clone()));
// Decrypting
let mut c = crypto::chacha20::ChaCha20::new_xchacha20(&key, iv);
let mut newoutput: Vec<u8> = repeat(0).take(output.len()).collect();
c.process(&mut output[..], &mut newoutput[..]);
println!("\nDecrypted: {}",str::from_utf8(&newoutput[..]).unwrap());
}

Finally we simply build with:

cargo build

A sample run is [here]:

== XChaCha20 ==
Message: "This is a secret message!"
Key: "0000000000000000000000000000000000000000000000000000000000000000"
IV: "000000000000000000000000000000000000000000000001"

Encrypted: ef507c4f0e68b27ed67bc6160c1feaa20bd01b7cafe98ab728

Decrypted: This is a secret message!

The standard ChaCha method uses a 128-bit or 256-bit key, and a 64-bit or a 96-bit IV. Overall the IV is stored with the cipher, so an intruder can try a range of encryption keys with the given IV. It may thus be possible to pre-compute some of the IV values for given keys. With XChaCha, we create a 192-bit IV, and which has a massive range of IV values.

Want to see it running in real life? Try here:

https://asecuritysite.com/rust/rust_xchacha20