A single AES block.
pub const Block = struct
pub const Block = struct {
const Repr = [4]u32;
pub const block_length: usize = 16;
/// Internal representation of a block.
repr: Repr align(16),
/// Convert a byte sequence into an internal representation.
pub fn fromBytes(bytes: *const [16]u8) Block {
const s0 = mem.readInt(u32, bytes[0..4], .little);
const s1 = mem.readInt(u32, bytes[4..8], .little);
const s2 = mem.readInt(u32, bytes[8..12], .little);
const s3 = mem.readInt(u32, bytes[12..16], .little);
return Block{ .repr = Repr{ s0, s1, s2, s3 } };
}
/// Convert the internal representation of a block into a byte sequence.
pub fn toBytes(block: Block) [16]u8 {
var bytes: [16]u8 = undefined;
mem.writeInt(u32, bytes[0..4], block.repr[0], .little);
mem.writeInt(u32, bytes[4..8], block.repr[1], .little);
mem.writeInt(u32, bytes[8..12], block.repr[2], .little);
mem.writeInt(u32, bytes[12..16], block.repr[3], .little);
return bytes;
}
/// XOR the block with a byte sequence.
pub fn xorBytes(block: Block, bytes: *const [16]u8) [16]u8 {
const block_bytes = block.toBytes();
var x: [16]u8 = undefined;
comptime var i: usize = 0;
inline while (i < 16) : (i += 1) {
x[i] = block_bytes[i] ^ bytes[i];
}
return x;
}
/// Encrypt a block with a round key.
pub fn encrypt(block: Block, round_key: Block) Block {
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
var x: [4]u32 = undefined;
x = table_lookup(&table_encrypt, @as(u8, @truncate(s0)), @as(u8, @truncate(s1 >> 8)), @as(u8, @truncate(s2 >> 16)), @as(u8, @truncate(s3 >> 24)));
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_encrypt, @as(u8, @truncate(s1)), @as(u8, @truncate(s2 >> 8)), @as(u8, @truncate(s3 >> 16)), @as(u8, @truncate(s0 >> 24)));
var t1 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_encrypt, @as(u8, @truncate(s2)), @as(u8, @truncate(s3 >> 8)), @as(u8, @truncate(s0 >> 16)), @as(u8, @truncate(s1 >> 24)));
var t2 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_encrypt, @as(u8, @truncate(s3)), @as(u8, @truncate(s0 >> 8)), @as(u8, @truncate(s1 >> 16)), @as(u8, @truncate(s2 >> 24)));
var t3 = x[0] ^ x[1] ^ x[2] ^ x[3];
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = Repr{ t0, t1, t2, t3 } };
}
/// Encrypt a block with a round key *WITHOUT ANY PROTECTION AGAINST SIDE CHANNELS*
pub fn encryptUnprotected(block: Block, round_key: Block) Block {
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
var x: [4]u32 = undefined;
x = .{
table_encrypt[0][@as(u8, @truncate(s0))],
table_encrypt[1][@as(u8, @truncate(s1 >> 8))],
table_encrypt[2][@as(u8, @truncate(s2 >> 16))],
table_encrypt[3][@as(u8, @truncate(s3 >> 24))],
};
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_encrypt[0][@as(u8, @truncate(s1))],
table_encrypt[1][@as(u8, @truncate(s2 >> 8))],
table_encrypt[2][@as(u8, @truncate(s3 >> 16))],
table_encrypt[3][@as(u8, @truncate(s0 >> 24))],
};
var t1 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_encrypt[0][@as(u8, @truncate(s2))],
table_encrypt[1][@as(u8, @truncate(s3 >> 8))],
table_encrypt[2][@as(u8, @truncate(s0 >> 16))],
table_encrypt[3][@as(u8, @truncate(s1 >> 24))],
};
var t2 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_encrypt[0][@as(u8, @truncate(s3))],
table_encrypt[1][@as(u8, @truncate(s0 >> 8))],
table_encrypt[2][@as(u8, @truncate(s1 >> 16))],
table_encrypt[3][@as(u8, @truncate(s2 >> 24))],
};
var t3 = x[0] ^ x[1] ^ x[2] ^ x[3];
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = Repr{ t0, t1, t2, t3 } };
}
/// Encrypt a block with the last round key.
pub fn encryptLast(block: Block, round_key: Block) Block {
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
// Last round uses s-box directly and XORs to produce output.
var x: [4]u8 = undefined;
x = sbox_lookup(&sbox_encrypt, @as(u8, @truncate(s0)), @as(u8, @truncate(s1 >> 8)), @as(u8, @truncate(s2 >> 16)), @as(u8, @truncate(s3 >> 24)));
var t0 = mem.readInt(u32, &x, .little);
x = sbox_lookup(&sbox_encrypt, @as(u8, @truncate(s1)), @as(u8, @truncate(s2 >> 8)), @as(u8, @truncate(s3 >> 16)), @as(u8, @truncate(s0 >> 24)));
var t1 = mem.readInt(u32, &x, .little);
x = sbox_lookup(&sbox_encrypt, @as(u8, @truncate(s2)), @as(u8, @truncate(s3 >> 8)), @as(u8, @truncate(s0 >> 16)), @as(u8, @truncate(s1 >> 24)));
var t2 = mem.readInt(u32, &x, .little);
x = sbox_lookup(&sbox_encrypt, @as(u8, @truncate(s3)), @as(u8, @truncate(s0 >> 8)), @as(u8, @truncate(s1 >> 16)), @as(u8, @truncate(s2 >> 24)));
var t3 = mem.readInt(u32, &x, .little);
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = Repr{ t0, t1, t2, t3 } };
}
/// Decrypt a block with a round key.
pub fn decrypt(block: Block, round_key: Block) Block {
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
var x: [4]u32 = undefined;
x = table_lookup(&table_decrypt, @as(u8, @truncate(s0)), @as(u8, @truncate(s3 >> 8)), @as(u8, @truncate(s2 >> 16)), @as(u8, @truncate(s1 >> 24)));
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_decrypt, @as(u8, @truncate(s1)), @as(u8, @truncate(s0 >> 8)), @as(u8, @truncate(s3 >> 16)), @as(u8, @truncate(s2 >> 24)));
var t1 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_decrypt, @as(u8, @truncate(s2)), @as(u8, @truncate(s1 >> 8)), @as(u8, @truncate(s0 >> 16)), @as(u8, @truncate(s3 >> 24)));
var t2 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_decrypt, @as(u8, @truncate(s3)), @as(u8, @truncate(s2 >> 8)), @as(u8, @truncate(s1 >> 16)), @as(u8, @truncate(s0 >> 24)));
var t3 = x[0] ^ x[1] ^ x[2] ^ x[3];
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = Repr{ t0, t1, t2, t3 } };
}
/// Decrypt a block with a round key *WITHOUT ANY PROTECTION AGAINST SIDE CHANNELS*
pub fn decryptUnprotected(block: Block, round_key: Block) Block {
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
var x: [4]u32 = undefined;
x = .{
table_decrypt[0][@as(u8, @truncate(s0))],
table_decrypt[1][@as(u8, @truncate(s3 >> 8))],
table_decrypt[2][@as(u8, @truncate(s2 >> 16))],
table_decrypt[3][@as(u8, @truncate(s1 >> 24))],
};
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_decrypt[0][@as(u8, @truncate(s1))],
table_decrypt[1][@as(u8, @truncate(s0 >> 8))],
table_decrypt[2][@as(u8, @truncate(s3 >> 16))],
table_decrypt[3][@as(u8, @truncate(s2 >> 24))],
};
var t1 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_decrypt[0][@as(u8, @truncate(s2))],
table_decrypt[1][@as(u8, @truncate(s1 >> 8))],
table_decrypt[2][@as(u8, @truncate(s0 >> 16))],
table_decrypt[3][@as(u8, @truncate(s3 >> 24))],
};
var t2 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_decrypt[0][@as(u8, @truncate(s3))],
table_decrypt[1][@as(u8, @truncate(s2 >> 8))],
table_decrypt[2][@as(u8, @truncate(s1 >> 16))],
table_decrypt[3][@as(u8, @truncate(s0 >> 24))],
};
var t3 = x[0] ^ x[1] ^ x[2] ^ x[3];
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = Repr{ t0, t1, t2, t3 } };
}
/// Decrypt a block with the last round key.
pub fn decryptLast(block: Block, round_key: Block) Block {
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
// Last round uses s-box directly and XORs to produce output.
var x: [4]u8 = undefined;
x = sbox_lookup(&sbox_decrypt, @as(u8, @truncate(s0)), @as(u8, @truncate(s3 >> 8)), @as(u8, @truncate(s2 >> 16)), @as(u8, @truncate(s1 >> 24)));
var t0 = mem.readInt(u32, &x, .little);
x = sbox_lookup(&sbox_decrypt, @as(u8, @truncate(s1)), @as(u8, @truncate(s0 >> 8)), @as(u8, @truncate(s3 >> 16)), @as(u8, @truncate(s2 >> 24)));
var t1 = mem.readInt(u32, &x, .little);
x = sbox_lookup(&sbox_decrypt, @as(u8, @truncate(s2)), @as(u8, @truncate(s1 >> 8)), @as(u8, @truncate(s0 >> 16)), @as(u8, @truncate(s3 >> 24)));
var t2 = mem.readInt(u32, &x, .little);
x = sbox_lookup(&sbox_decrypt, @as(u8, @truncate(s3)), @as(u8, @truncate(s2 >> 8)), @as(u8, @truncate(s1 >> 16)), @as(u8, @truncate(s0 >> 24)));
var t3 = mem.readInt(u32, &x, .little);
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = Repr{ t0, t1, t2, t3 } };
}
/// Apply the bitwise XOR operation to the content of two blocks.
pub fn xorBlocks(block1: Block, block2: Block) Block {
var x: Repr = undefined;
comptime var i = 0;
inline while (i < 4) : (i += 1) {
x[i] = block1.repr[i] ^ block2.repr[i];
}
return Block{ .repr = x };
}
/// Apply the bitwise AND operation to the content of two blocks.
pub fn andBlocks(block1: Block, block2: Block) Block {
var x: Repr = undefined;
comptime var i = 0;
inline while (i < 4) : (i += 1) {
x[i] = block1.repr[i] & block2.repr[i];
}
return Block{ .repr = x };
}
/// Apply the bitwise OR operation to the content of two blocks.
pub fn orBlocks(block1: Block, block2: Block) Block {
var x: Repr = undefined;
comptime var i = 0;
inline while (i < 4) : (i += 1) {
x[i] = block1.repr[i] | block2.repr[i];
}
return Block{ .repr = x };
}
/// Apply the inverse MixColumns operation to a block.
pub fn invMixColumns(block: Block) Block {
var out: Repr = undefined;
inline for (0..4) |i| {
const col = block.repr[i];
const b0: u8 = @truncate(col);
const b1: u8 = @truncate(col >> 8);
const b2: u8 = @truncate(col >> 16);
const b3: u8 = @truncate(col >> 24);
const r0 = mul(0x0e, b0) ^ mul(0x0b, b1) ^ mul(0x0d, b2) ^ mul(0x09, b3);
const r1 = mul(0x09, b0) ^ mul(0x0e, b1) ^ mul(0x0b, b2) ^ mul(0x0d, b3);
const r2 = mul(0x0d, b0) ^ mul(0x09, b1) ^ mul(0x0e, b2) ^ mul(0x0b, b3);
const r3 = mul(0x0b, b0) ^ mul(0x0d, b1) ^ mul(0x09, b2) ^ mul(0x0e, b3);
out[i] = @as(u32, r0) | (@as(u32, r1) << 8) | (@as(u32, r2) << 16) | (@as(u32, r3) << 24);
}
return Block{ .repr = out };
}
/// Perform operations on multiple blocks in parallel.
pub const parallel = struct {
/// The recommended number of AES encryption/decryption to perform in parallel for the chosen implementation.
pub const optimal_parallel_blocks = 1;
/// Encrypt multiple blocks in parallel, each their own round key.
pub fn encryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) [count]Block {
var i = 0;
var out: [count]Block = undefined;
while (i < count) : (i += 1) {
out[i] = blocks[i].encrypt(round_keys[i]);
}
return out;
}
/// Decrypt multiple blocks in parallel, each their own round key.
pub fn decryptParallel(comptime count: usize, blocks: [count]Block, round_keys: [count]Block) [count]Block {
var i = 0;
var out: [count]Block = undefined;
while (i < count) : (i += 1) {
out[i] = blocks[i].decrypt(round_keys[i]);
}
return out;
}
/// Encrypt multiple blocks in parallel with the same round key.
pub fn encryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block {
var i = 0;
var out: [count]Block = undefined;
while (i < count) : (i += 1) {
out[i] = blocks[i].encrypt(round_key);
}
return out;
}
/// Decrypt multiple blocks in parallel with the same round key.
pub fn decryptWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block {
var i = 0;
var out: [count]Block = undefined;
while (i < count) : (i += 1) {
out[i] = blocks[i].decrypt(round_key);
}
return out;
}
/// Encrypt multiple blocks in parallel with the same last round key.
pub fn encryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block {
var i = 0;
var out: [count]Block = undefined;
while (i < count) : (i += 1) {
out[i] = blocks[i].encryptLast(round_key);
}
return out;
}
/// Decrypt multiple blocks in parallel with the same last round key.
pub fn decryptLastWide(comptime count: usize, blocks: [count]Block, round_key: Block) [count]Block {
var i = 0;
var out: [count]Block = undefined;
while (i < count) : (i += 1) {
out[i] = blocks[i].decryptLast(round_key);
}
return out;
}
};
}