Update 2022-02-24: In randombytes
, I limited the copy_from_slice
to size chunk.len()
since chunk
need not have 16 bytes uniformly.
Motivation
The NIST PQC candidates use the same RNG (written by Bassham & Lawrence E in 2017) in their reference implementations to fetch random number based on a seed. The interface is trivial:
int
randombytes(unsigned char *x, unsigned long long xlen);
void
randombytes_init(unsigned char *entropy_input,
unsigned char *personalization_string,
int security_strength)
What does an equivalent implementation in rust look like?
The C implementation
The following implementation is provided in Classic McEliece and is more compact than others.
/*
rng.c
Created by Bassham, Lawrence E (Fed) on 8/29/17.
Copyright © 2017 Bassham, Lawrence E (Fed). All rights reserved.
*/
#include <string.h>
#include "rng.h"
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
AES256_CTR_DRBG_struct DRBG_ctx;
void AES256_ECB(unsigned char *key, unsigned char *ctr, unsigned char *buffer);
/*
seedexpander_init()
ctx - stores the current state of an instance of the seed expander
seed - a 32 byte random value
diversifier - an 8 byte diversifier
maxlen - maximum number of bytes (less than 2**32) generated under this seed and diversifier
*/
int
seedexpander_init(AES_XOF_struct *ctx,
unsigned char *seed,
unsigned char *diversifier,
unsigned long maxlen)
{
if ( maxlen >= 0x100000000 )
return RNG_BAD_MAXLEN;
ctx->length_remaining = maxlen;
memcpy(ctx->key, seed, 32);
memcpy(ctx->ctr, diversifier, 8);
ctx->ctr[11] = maxlen % 256;
maxlen >>= 8;
ctx->ctr[10] = maxlen % 256;
maxlen >>= 8;
ctx->ctr[9] = maxlen % 256;
maxlen >>= 8;
ctx->ctr[8] = maxlen % 256;
memset(ctx->ctr+12, 0x00, 4);
ctx->buffer_pos = 16;
memset(ctx->buffer, 0x00, 16);
return RNG_SUCCESS;
}
/*
seedexpander()
ctx - stores the current state of an instance of the seed expander
x - returns the XOF data
xlen - number of bytes to return
*/
int
seedexpander(AES_XOF_struct *ctx, unsigned char *x, unsigned long xlen)
{
unsigned long offset;
int i;
if ( x == NULL )
return RNG_BAD_OUTBUF;
if ( xlen >= ctx->length_remaining )
return RNG_BAD_REQ_LEN;
ctx->length_remaining -= xlen;
offset = 0;
while ( xlen > 0 ) {
if ( xlen <= (16-ctx->buffer_pos) ) { /* buffer has what we need */
memcpy(x+offset, ctx->buffer+ctx->buffer_pos, xlen);
ctx->buffer_pos += xlen;
return RNG_SUCCESS;
}
/* take what's in the buffer */
memcpy(x+offset, ctx->buffer+ctx->buffer_pos, 16-ctx->buffer_pos);
xlen -= 16-ctx->buffer_pos;
offset += 16-ctx->buffer_pos;
AES256_ECB(ctx->key, ctx->ctr, ctx->buffer);
ctx->buffer_pos = 0;
/* increment the counter */
for (i=15; i>=12; i--) {
if ( ctx->ctr[i] == 0xff )
ctx->ctr[i] = 0x00;
else {
ctx->ctr[i]++;
break;
}
}
}
return RNG_SUCCESS;
}
void handleErrors(void)
{
ERR_print_errors_fp(stderr);
abort();
}
/*
Use whatever AES implementation you have. This uses AES from openSSL library
key - 256-bit AES key
ctr - a 128-bit plaintext value
buffer - a 128-bit ciphertext value
*/
void
AES256_ECB(unsigned char *key, unsigned char *ctr, unsigned char *buffer)
{
EVP_CIPHER_CTX *ctx;
int len;
/* Create and initialise the context */
if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_ecb(), NULL, key, NULL))
handleErrors();
if(1 != EVP_EncryptUpdate(ctx, buffer, &len, ctr, 16))
handleErrors();
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
}
void
randombytes_init(unsigned char *entropy_input,
unsigned char *personalization_string,
int security_strength)
{
unsigned char seed_material[48];
int i;
memcpy(seed_material, entropy_input, 48);
if (personalization_string)
for (i=0; i<48; i++)
seed_material[i] ^= personalization_string[i];
memset(DRBG_ctx.Key, 0x00, 32);
memset(DRBG_ctx.V, 0x00, 16);
AES256_CTR_DRBG_Update(seed_material, DRBG_ctx.Key, DRBG_ctx.V);
DRBG_ctx.reseed_counter = 1;
}
int
randombytes(unsigned char *x, unsigned long long xlen)
{
unsigned char block[16];
int i = 0;
int j;
while ( xlen > 0 ) {
/* increment V */
for (j=15; j>=0; j--) {
if ( DRBG_ctx.V[j] == 0xff )
DRBG_ctx.V[j] = 0x00;
else {
DRBG_ctx.V[j]++;
break;
}
}
AES256_ECB(DRBG_ctx.Key, DRBG_ctx.V, block);
if ( xlen > 15 ) {
memcpy(x+i, block, 16);
i += 16;
xlen -= 16;
}
else {
memcpy(x+i, block, xlen);
xlen = 0;
}
}
AES256_CTR_DRBG_Update(NULL, DRBG_ctx.Key, DRBG_ctx.V);
DRBG_ctx.reseed_counter++;
return RNG_SUCCESS;
}
void
AES256_CTR_DRBG_Update(unsigned char *provided_data,
unsigned char *Key,
unsigned char *V)
{
unsigned char temp[48];
int i;
int j;
for (i=0; i<3; i++) {
/* increment V */
for (j=15; j>=0; j--) {
if ( V[j] == 0xff )
V[j] = 0x00;
else {
V[j]++;
break;
}
}
AES256_ECB(Key, V, temp+16*i);
}
if ( provided_data != NULL )
for (i=0; i<48; i++)
temp[i] ^= provided_data[i];
memcpy(Key, temp, 32);
memcpy(V, temp+32, 16);
}
My main routine calls the init-function once and randombytes
two times:
#include <stdio.h>
void
fprintBstr(FILE *fp, char *S, unsigned char *A, unsigned long long L)
{
unsigned long long i;
fprintf(fp, "%s", S);
for ( i=0; i<L; i++ )
fprintf(fp, "%02X", A[i]);
if ( L == 0 )
fprintf(fp, "00");
fprintf(fp, "\n");
}
int main() {
unsigned char entropy_input[48];
unsigned char personalization_string[48];
unsigned char data[256];
for (int i=0; i<48; i++) {
entropy_input[i] = i;
personalization_string[i] = 0;
}
randombytes_init(entropy_input, personalization_string, 256);
printf("randombytes returned %u\n", randombytes(data, sizeof(data)));
fprintBstr(stdout, "data = ", data, sizeof(data));
printf("randombytes returned %u\n", randombytes(data, sizeof(data)));
fprintBstr(stdout, "data = ", data, sizeof(data));
return 0;
}
With this main routine, we get the following output:
randombytes returned 0
data = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA19810F5392D076276EF41277C3AB6E94A4E3B7DCC104A05BB089D338BF55C72CAB375389A94BB920BD5D6DC9E7F2EC6FDE028B6F5724BB039F3652AD98DF8CE6C97013210B84BBE81388C3D141D61957C73BCDC5E5CD92525F46A2B757B03CAB5C337004A2DA35324A325713564DAE28F57ACC6DBE32A0726190BAA6B8A0A255AA1AD01E8DD569AA36D096256C420718A69D46D8DB1C6DD40606A0BE3C235BEFE623A90593F82D6A8F9F924E44E36BE87F7D26B8445966F9EE329C426C12521E85F6FD4ECD5D566BA0A3487125D79CC64
randombytes returned 0
data = C17E034061ED5EA817C41D61636281E816F817DCF753A91D97C018FF82FBC9B1728FC66AF114B57978FB6082B70D285140B26725AA5F7BB4409820F67E2D656EDACA30B5BB12EB5249CC3809B188CF0CC95B5AE0EFE8FC5887152CB6601B4CCF9FC411894FA0C0264EB51A481D4D7074FDF065053030C8A92BFCDD06BF18C8489C38D03784FD63001830E5A385A4A37866693F5BDAB8A8A25B519DDBF2D28268601D95BEED647E430484A227C023B0297A282F06C91376433BDE5EC3ABBA8C06B830C26452EA2FA7EDEA8DCFE20EAFCF8980B3D5AECEF89DD861ACEC1F5F7CD2AE6B3CDE3C1D80A2830DD0B9E8468AFAD161981074BEB33DF1CDFF9A5214F9F0
A compatible rust implementation
Now, we would like to write an equivalent rust implementation. Our implementation should focus on compatibility with the C implementation. First, we need to define the dependencies. The hex
crate is only used for representation in the main routine below.
[dependencies]
lazy_static = "1.4.0"
hex = "0.4.3"
aes = "0.7.5"
Like the C implementation, we use 3 arguments for the init-function and 2 arguments for randombytes
. The RNG state is stored in a global variable utilized through the lazy_static
crate (in general, a bad idea due to concurrent usecases):
use hex;
use std::error;
use aes::NewBlockCipher;
use aes::BlockEncrypt;
use std::sync;
use lazy_static::lazy_static;
pub struct Aes256CtrDrbgStruct {
pub key: [u8; 32],
pub v: [u8; 16],
pub reseed_counter: i32,
}
lazy_static! {
static ref DRBG_CTX: sync::Mutex<Aes256CtrDrbgStruct> = sync::Mutex::new({
Aes256CtrDrbgStruct {
key: [0u8; 32],
v: [0u8; 16],
reseed_counter: 0,
}
});
}
fn aes256_ecb(
key: &[u8; 32],
ctr: &[u8; 16],
buffer: &mut [u8; 16]
) {
let cipher = aes::Aes256::new(key.into());
buffer.copy_from_slice(ctr);
cipher.encrypt_block(buffer.into());
}
fn aes256_ctr_drbg_update(
provided_data: &mut Option<[u8; 48]>,
key: &mut [u8; 32],
v: &mut [u8; 16]
) {
let mut temp = [[0u8; 16]; 3];
for i in 0..3 {
let count = u128::from_be_bytes(*v);
v.copy_from_slice(&(count + 1).to_be_bytes());
aes256_ecb(key, v, &mut temp[i]);
}
if let Some(d) = provided_data {
for j in 0..3 {
for i in 0..16 {
temp[j][i] ^= d[16 * j + i];
}
}
}
key[0..16].copy_from_slice(&temp[0]);
key[16..32].copy_from_slice(&temp[1]);
v.copy_from_slice(&temp[2]);
}
fn randombytes_init(
entropy_input: &[u8; 48],
personalization_string: &[u8; 48],
_security_strength: u32,
) -> Result<(), Box<dyn error::Error>> {
let mut drbg_ctx = DRBG_CTX.lock()?;
// reset ctx
drbg_ctx.key = [0u8; 32];
drbg_ctx.v = [0u8; 16];
drbg_ctx.reseed_counter = 1i32;
// get seed ready
let mut seed = [0u8; 48];
for i in 0..48 {
seed[i] = entropy_input[i] ^ personalization_string[i];
}
let mut key = drbg_ctx.key;
aes256_ctr_drbg_update(&mut Some(seed), &mut key, &mut drbg_ctx.v);
drbg_ctx.key.copy_from_slice(&key);
Ok(())
}
fn randombytes(
x: &mut [u8],
xlen: usize,
) -> Result<(), Box<dyn error::Error>> {
assert_eq!(x.len(), xlen);
let mut drbg_ctx = DRBG_CTX.lock()?;
for chunk in x.chunks_mut(16) {
let count = u128::from_be_bytes(drbg_ctx.v);
drbg_ctx.v.copy_from_slice(&(count + 1).to_be_bytes());
let mut block = [0u8; 16];
aes256_ecb(&drbg_ctx.key, &drbg_ctx.v, &mut block);
(*chunk).copy_from_slice(&mut block[..chunk.len()]);
}
let mut key = drbg_ctx.key;
aes256_ctr_drbg_update(&mut None, &mut key, &mut drbg_ctx.v);
drbg_ctx.key.copy_from_slice(&key);
drbg_ctx.reseed_counter += 1;
Ok(())
}
The main routine similarly calls the init-function and randombytes
two times. It also checks against the output of the C implementation:
fn main() -> Result<(), Box<dyn error::Error>> {
let mut data = [0u8; 256];
let mut entropy_input = [0u8; 48];
let mut personalization_string = [0u8; 48];
for i in 0..48 {
entropy_input[i] = i as u8;
personalization_string[i] = 0 as u8;
}
{
randombytes_init(&entropy_input, &personalization_string, 256)?;
}
{
randombytes(&mut data, 256)?;
println!("randombytes returned 0");
println!("data = {}", hex::encode_upper(&data));
}
{
let mut ref_bytes = [0u8; 256];
hex::decode_to_slice("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA19810F5392D076276EF41277C3AB6E94A4E3B7DCC104A05BB089D338BF55C72CAB375389A94BB920BD5D6DC9E7F2EC6FDE028B6F5724BB039F3652AD98DF8CE6C97013210B84BBE81388C3D141D61957C73BCDC5E5CD92525F46A2B757B03CAB5C337004A2DA35324A325713564DAE28F57ACC6DBE32A0726190BAA6B8A0A255AA1AD01E8DD569AA36D096256C420718A69D46D8DB1C6DD40606A0BE3C235BEFE623A90593F82D6A8F9F924E44E36BE87F7D26B8445966F9EE329C426C12521E85F6FD4ECD5D566BA0A3487125D79CC64", &mut ref_bytes as &mut [u8])?;
assert_eq!(data, ref_bytes);
}
{
randombytes(&mut data, 256)?;
println!("randombytes returned 0");
println!("data = {}", hex::encode_upper(&data));
}
{
let mut ref_bytes = [0u8; 256];
hex::decode_to_slice("C17E034061ED5EA817C41D61636281E816F817DCF753A91D97C018FF82FBC9B1728FC66AF114B57978FB6082B70D285140B26725AA5F7BB4409820F67E2D656EDACA30B5BB12EB5249CC3809B188CF0CC95B5AE0EFE8FC5887152CB6601B4CCF9FC411894FA0C0264EB51A481D4D7074FDF065053030C8A92BFCDD06BF18C8489C38D03784FD63001830E5A385A4A37866693F5BDAB8A8A25B519DDBF2D28268601D95BEED647E430484A227C023B0297A282F06C91376433BDE5EC3ABBA8C06B830C26452EA2FA7EDEA8DCFE20EAFCF8980B3D5AECEF89DD861ACEC1F5F7CD2AE6B3CDE3C1D80A2830DD0B9E8468AFAD161981074BEB33DF1CDFF9A5214F9F0", &mut ref_bytes as &mut [u8])?;
assert_eq!(data, ref_bytes);
}
Ok(())
}
An idiomatic rust implementation
In this implementation, we want to skip implementation aspects resulting from C conventions and focus on idiomatic rust implementations:
-
I removed the unused
_security_strength
parameter. -
the testcases are provided as actual unittests.
-
RNG state is now an object passed as argument not stored as global variable.
First, we reduce dependencies to the aes
crate:
[dependencies]
aes = "0.7.5"
Then the implementation has the same set of functions (and a new
method for Aes256CtrDrbgStruct
) but sometimes different arguments:
use std::error;
use aes::NewBlockCipher;
use aes::BlockEncrypt;
pub struct Aes256CtrDrbgStruct {
pub key: [u8; 32],
pub v: [u8; 16],
pub reseed_counter: i32,
}
impl Aes256CtrDrbgStruct {
pub fn new() -> Aes256CtrDrbgStruct {
Aes256CtrDrbgStruct {
key: [0; 32],
v: [0; 16],
reseed_counter: 0
}
}
}
fn aes256_ecb(
key: &[u8; 32],
ctr: &[u8; 16],
buffer: &mut [u8; 16]
) {
let cipher = aes::Aes256::new(key.into());
buffer.copy_from_slice(ctr);
cipher.encrypt_block(buffer.into());
}
fn aes256_ctr_drbg_update(
provided_data: &mut Option<[u8; 48]>,
key: &mut [u8; 32],
v: &mut [u8; 16]
) {
let mut temp = [[0u8; 16]; 3];
for i in 0..3 {
let count = u128::from_be_bytes(*v);
v.copy_from_slice(&(count + 1).to_be_bytes());
aes256_ecb(key, v, &mut temp[i]);
}
if let Some(d) = provided_data {
for j in 0..3 {
for i in 0..16 {
temp[j][i] ^= d[16 * j + i];
}
}
}
key[0..16].copy_from_slice(&temp[0]);
key[16..32].copy_from_slice(&temp[1]);
v.copy_from_slice(&temp[2]);
}
fn randombytes_init(
entropy_input: &[u8; 48],
personalization_string: &[u8; 48],
drbg_ctx: &mut Aes256CtrDrbgStruct
) {
// reset ctx
drbg_ctx.key = [0u8; 32];
drbg_ctx.v = [0u8; 16];
drbg_ctx.reseed_counter = 1i32;
// get seed ready
let mut seed = [0u8; 48];
for i in 0..48 {
seed[i] = entropy_input[i] ^ personalization_string[i];
}
aes256_ctr_drbg_update(&mut Some(seed), &mut drbg_ctx.key, &mut drbg_ctx.v);
}
fn randombytes(
x: &mut [u8],
drbg_ctx: &mut Aes256CtrDrbgStruct
) -> Result<(), Box<dyn error::Error>> {
for chunk in x.chunks_mut(16) {
let count = u128::from_be_bytes(drbg_ctx.v);
drbg_ctx.v.copy_from_slice(&(count + 1).to_be_bytes());
let mut block = [0u8; 16];
aes256_ecb(&drbg_ctx.key, &drbg_ctx.v, &mut block);
(*chunk).copy_from_slice(&mut block[..chunk.len()]);
}
aes256_ctr_drbg_update(&mut None, &mut drbg_ctx.key, &mut drbg_ctx.v);
drbg_ctx.reseed_counter += 1;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rng() -> Result<(), Box<dyn error::Error>> {
let mut data = [0u8; 256];
let mut entropy_input = [0u8; 48];
let mut personalization_string = [0u8; 48];
let mut rng_state = Aes256CtrDrbgStruct::new();
for i in 0..48 {
entropy_input[i] = i as u8;
personalization_string[i] = 0 as u8;
}
randombytes_init(&entropy_input, &personalization_string, &mut rng_state);
randombytes(&mut data, &mut rng_state)?;
let ref1 = [0x06u8, 0x15, 0x50, 0x23, 0x4D, 0x15, 0x8C, 0x5E, 0xC9, 0x55, 0x95, 0xFE, 0x04, 0xEF, 0x7A, 0x25, 0x76, 0x7F, 0x2E, 0x24, 0xCC, 0x2B, 0xC4, 0x79, 0xD0, 0x9D, 0x86, 0xDC, 0x9A, 0xBC, 0xFD, 0xE7, 0x05, 0x6A, 0x8C, 0x26, 0x6F, 0x9E, 0xF9, 0x7E, 0xD0, 0x85, 0x41, 0xDB, 0xD2, 0xE1, 0xFF, 0xA1, 0x98, 0x10, 0xF5, 0x39, 0x2D, 0x07, 0x62, 0x76, 0xEF, 0x41, 0x27, 0x7C, 0x3A, 0xB6, 0xE9, 0x4A, 0x4E, 0x3B, 0x7D, 0xCC, 0x10, 0x4A, 0x05, 0xBB, 0x08, 0x9D, 0x33, 0x8B, 0xF5, 0x5C, 0x72, 0xCA, 0xB3, 0x75, 0x38, 0x9A, 0x94, 0xBB, 0x92, 0x0B, 0xD5, 0xD6, 0xDC, 0x9E, 0x7F, 0x2E, 0xC6, 0xFD, 0xE0, 0x28, 0xB6, 0xF5, 0x72, 0x4B, 0xB0, 0x39, 0xF3, 0x65, 0x2A, 0xD9, 0x8D, 0xF8, 0xCE, 0x6C, 0x97, 0x01, 0x32, 0x10, 0xB8, 0x4B, 0xBE, 0x81, 0x38, 0x8C, 0x3D, 0x14, 0x1D, 0x61, 0x95, 0x7C, 0x73, 0xBC, 0xDC, 0x5E, 0x5C, 0xD9, 0x25, 0x25, 0xF4, 0x6A, 0x2B, 0x75, 0x7B, 0x03, 0xCA, 0xB5, 0xC3, 0x37, 0x00, 0x4A, 0x2D, 0xA3, 0x53, 0x24, 0xA3, 0x25, 0x71, 0x35, 0x64, 0xDA, 0xE2, 0x8F, 0x57, 0xAC, 0xC6, 0xDB, 0xE3, 0x2A, 0x07, 0x26, 0x19, 0x0B, 0xAA, 0x6B, 0x8A, 0x0A, 0x25, 0x5A, 0xA1, 0xAD, 0x01, 0xE8, 0xDD, 0x56, 0x9A, 0xA3, 0x6D, 0x09, 0x62, 0x56, 0xC4, 0x20, 0x71, 0x8A, 0x69, 0xD4, 0x6D, 0x8D, 0xB1, 0xC6, 0xDD, 0x40, 0x60, 0x6A, 0x0B, 0xE3, 0xC2, 0x35, 0xBE, 0xFE, 0x62, 0x3A, 0x90, 0x59, 0x3F, 0x82, 0xD6, 0xA8, 0xF9, 0xF9, 0x24, 0xE4, 0x4E, 0x36, 0xBE, 0x87, 0xF7, 0xD2, 0x6B, 0x84, 0x45, 0x96, 0x6F, 0x9E, 0xE3, 0x29, 0xC4, 0x26, 0xC1, 0x25, 0x21, 0xE8, 0x5F, 0x6F, 0xD4, 0xEC, 0xD5, 0xD5, 0x66, 0xBA, 0x0A, 0x34, 0x87, 0x12, 0x5D, 0x79, 0xCC, 0x64];
assert_eq!(data, ref1);
randombytes(&mut data, &mut rng_state)?;
let ref2 = [0xC1u8, 0x7E, 0x03, 0x40, 0x61, 0xED, 0x5E, 0xA8, 0x17, 0xC4, 0x1D, 0x61, 0x63, 0x62, 0x81, 0xE8, 0x16, 0xF8, 0x17, 0xDC, 0xF7, 0x53, 0xA9, 0x1D, 0x97, 0xC0, 0x18, 0xFF, 0x82, 0xFB, 0xC9, 0xB1, 0x72, 0x8F, 0xC6, 0x6A, 0xF1, 0x14, 0xB5, 0x79, 0x78, 0xFB, 0x60, 0x82, 0xB7, 0x0D, 0x28, 0x51, 0x40, 0xB2, 0x67, 0x25, 0xAA, 0x5F, 0x7B, 0xB4, 0x40, 0x98, 0x20, 0xF6, 0x7E, 0x2D, 0x65, 0x6E, 0xDA, 0xCA, 0x30, 0xB5, 0xBB, 0x12, 0xEB, 0x52, 0x49, 0xCC, 0x38, 0x09, 0xB1, 0x88, 0xCF, 0x0C, 0xC9, 0x5B, 0x5A, 0xE0, 0xEF, 0xE8, 0xFC, 0x58, 0x87, 0x15, 0x2C, 0xB6, 0x60, 0x1B, 0x4C, 0xCF, 0x9F, 0xC4, 0x11, 0x89, 0x4F, 0xA0, 0xC0, 0x26, 0x4E, 0xB5, 0x1A, 0x48, 0x1D, 0x4D, 0x70, 0x74, 0xFD, 0xF0, 0x65, 0x05, 0x30, 0x30, 0xC8, 0xA9, 0x2B, 0xFC, 0xDD, 0x06, 0xBF, 0x18, 0xC8, 0x48, 0x9C, 0x38, 0xD0, 0x37, 0x84, 0xFD, 0x63, 0x00, 0x18, 0x30, 0xE5, 0xA3, 0x85, 0xA4, 0xA3, 0x78, 0x66, 0x69, 0x3F, 0x5B, 0xDA, 0xB8, 0xA8, 0xA2, 0x5B, 0x51, 0x9D, 0xDB, 0xF2, 0xD2, 0x82, 0x68, 0x60, 0x1D, 0x95, 0xBE, 0xED, 0x64, 0x7E, 0x43, 0x04, 0x84, 0xA2, 0x27, 0xC0, 0x23, 0xB0, 0x29, 0x7A, 0x28, 0x2F, 0x06, 0xC9, 0x13, 0x76, 0x43, 0x3B, 0xDE, 0x5E, 0xC3, 0xAB, 0xBA, 0x8C, 0x06, 0xB8, 0x30, 0xC2, 0x64, 0x52, 0xEA, 0x2F, 0xA7, 0xED, 0xEA, 0x8D, 0xCF, 0xE2, 0x0E, 0xAF, 0xCF, 0x89, 0x80, 0xB3, 0xD5, 0xAE, 0xCE, 0xF8, 0x9D, 0xD8, 0x61, 0xAC, 0xEC, 0x1F, 0x5F, 0x7C, 0xD2, 0xAE, 0x6B, 0x3C, 0xDE, 0x3C, 0x1D, 0x80, 0xA2, 0x83, 0x0D, 0xD0, 0xB9, 0xE8, 0x46, 0x8A, 0xFA, 0xD1, 0x61, 0x98, 0x10, 0x74, 0xBE, 0xB3, 0x3D, 0xF1, 0xCD, 0xFF, 0x9A, 0x52, 0x14, 0xF9, 0xF0];
assert_eq!(data, ref2);
Ok(())
}
}
Conclusion
We provided two implementations. One focuses on compatibility with C, one focuses on idiomatic rust. Both yield the same results like the C implementation and the latter implementation uses only the aes
crate as dependency.