softaes 0.1.4

Fast software implementation of the AES round function in pure Rust
Documentation

softaes

A fast software implementation of the AES round function in pure Rust.

This crate is not a high-level "encrypt my buffer" library. It exposes the AES round function itself as a building block, the same primitive that backs constructions such as AEGIS, AES-based PRFs, and other designs that lean on the AES round for mixing. If that is what you need, you get it here without pulling in hardware intrinsics or unsafe assembly.

The code is no_std, has no dependencies, and runs anywhere Rust runs.

Two implementations

The crate ships two round functions and lets you pick the trade-off explicitly.

The default path, at the crate root, is constant-time. The round is computed with a bitsliced representation (called SRM-1R) that holds the block as eight 32-bit bit planes. ShiftRows is folded into the input packing, SubBytes is a gate-only Boolean S-box circuit, and MixColumns is a fixed sequence of rotations and XORs. No step ever indexes memory with secret data, so the round runs in constant time on every platform. This is the one to reach for when the input is secret.

The unprotected module is a classic table-based implementation. It is faster on machines with good caches, but the table lookups are indexed by secret data, so it leaks through cache-timing side channels. Use it only when the data is not secret, or when you have already accepted that risk.

Installation

[dependencies]
softaes = "0.1"

Using the constant-time round

SoftAes::block_encrypt applies one full forward round: SubBytes, ShiftRows, MixColumns, and then AddRoundKey with the round key you pass in.

use softaes::{Block, SoftAes};

let state = Block::from_bytes(&[0u8; 16]);
let round_key = Block::from_bytes(&[0u8; 16]);

let next = SoftAes::block_encrypt(&state, &round_key);
let bytes = next.to_bytes();

For historical reasons the crate also exports SoftAesFast, SoftAesModerate, and SoftAesSlow. These used to select between table variants with different amounts of cache-timing hardening; they are now all aliases for the single constant-time SoftAes, kept so existing code keeps compiling.

Building the full cipher

A standard AES encryption is the initial AddRoundKey, a number of full rounds, and a final round that omits MixColumns. The constant-time path deliberately exposes only the full round, which is all the AES-round constructions need. To assemble the complete cipher, including the final round and decryption, use the unprotected module, which provides block_encrypt, block_encrypt_last, block_decrypt, and block_decrypt_last.

use softaes::unprotected::{Block, SoftAes};
use softaes::unprotected::key_schedule::key_expansion_128;

let key = [0u8; 16];
let plaintext = [0u8; 16];

let round_keys = key_expansion_128(&key);

let mut state = Block::from_bytes(&plaintext).xor(&round_keys[0]);
for rk in &round_keys[1..10] {
    state = SoftAes::block_encrypt(&state, rk);
}
let ciphertext = SoftAes::block_encrypt_last(&state, &round_keys[10]).to_bytes();

Key schedule

Both paths carry a key_schedule module with the same interface:

  • key_expansion_128, key_expansion_192, key_expansion_256 expand a key into the encryption round keys.
  • inverse_key_schedule_128, inverse_key_schedule_192, inverse_key_schedule_256 turn an encryption schedule into the matching decryption schedule, reversing the round-key order and applying InvMixColumns to every key except the first and last.

The constant-time key_schedule runs its S-box lookups in constant time; the one under unprotected uses a direct table. Both produce identical round keys, and the expansions are checked against the FIPS 197 Appendix C test vectors.

License

MIT. See LICENSE.