std.crypto.aes: expose the inverse MixColumns operation

The inverse MixColumns operation is already used internally for
AES decryption, but it wasn’t exposed in the public API because
it didn’t seem necessary at the time.

Since then, several new AES-based block ciphers and permutations
(such as Vistrutah and Areion) have been developed, and they require
this operation to be implementable in Zig.
Since then, new interesting AES-based block ciphers and permutations
(Vistrutah, Areion, etc). have been invented, and require that
operation to be implementable in Zig.
This commit is contained in:
Frank Denis 2025-11-29 19:25:07 +01:00
parent 7d9ad992ab
commit 5e00a0c9b5
4 changed files with 101 additions and 0 deletions

View file

@ -108,6 +108,36 @@ test "expand 128-bit key" {
} }
} }
test "invMixColumns" {
const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
const enc_ctx = Aes128.initEnc(key);
const dec_ctx = Aes128.initDec(key);
for (1..10) |i| {
const enc_rk = enc_ctx.key_schedule.round_keys[10 - i];
const dec_rk = dec_ctx.key_schedule.round_keys[i];
const computed = enc_rk.invMixColumns();
try testing.expectEqualSlices(u8, &dec_rk.toBytes(), &computed.toBytes());
}
}
test "BlockVec invMixColumns" {
const input = [_]u8{
0x5f, 0x57, 0xf7, 0x1d, 0x72, 0xf5, 0xbe, 0xb9, 0x64, 0xbc, 0x3b, 0xf9, 0x15, 0x92, 0x29, 0x1a,
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
};
const vec2 = BlockVec(2).fromBytes(&input);
const result_vec = vec2.invMixColumns();
const result_bytes = result_vec.toBytes();
for (0..2) |i| {
const block = Block.fromBytes(input[i * 16 ..][0..16]);
const expected = block.invMixColumns().toBytes();
try testing.expectEqualSlices(u8, &expected, result_bytes[i * 16 ..][0..16]);
}
}
test "expand 256-bit key" { test "expand 256-bit key" {
const key = [_]u8{ const key = [_]u8{
0x60, 0x3d, 0xeb, 0x10, 0x60, 0x3d, 0xeb, 0x10,

View file

@ -96,6 +96,17 @@ pub const Block = struct {
return Block{ .repr = block1.repr | block2.repr }; return Block{ .repr = block1.repr | block2.repr };
} }
/// Apply the inverse MixColumns operation to a block.
pub fn invMixColumns(block: Block) Block {
return Block{
.repr = asm (
\\ vaesimc %[in], %[out]
: [out] "=x" (-> Repr),
: [in] "x" (block.repr),
),
};
}
/// Perform operations on multiple blocks in parallel. /// Perform operations on multiple blocks in parallel.
pub const parallel = struct { pub const parallel = struct {
const cpu = std.Target.x86.cpu; const cpu = std.Target.x86.cpu;
@ -308,6 +319,17 @@ pub fn BlockVec(comptime blocks_count: comptime_int) type {
} }
return out; return out;
} }
/// Apply the inverse MixColumns operation to each block in the vector.
pub fn invMixColumns(block_vec: Self) Self {
var out_bytes: [blocks_count * 16]u8 = undefined;
const in_bytes = block_vec.toBytes();
inline for (0..blocks_count) |i| {
const block = Block.fromBytes(in_bytes[i * 16 ..][0..16]);
out_bytes[i * 16 ..][0..16].* = block.invMixColumns().toBytes();
}
return fromBytes(&out_bytes);
}
}; };
} }

View file

@ -99,6 +99,17 @@ pub const Block = struct {
return Block{ .repr = block1.repr | block2.repr }; return Block{ .repr = block1.repr | block2.repr };
} }
/// Apply the inverse MixColumns operation to a block.
pub fn invMixColumns(block: Block) Block {
return Block{
.repr = asm (
\\ aesimc %[out].16b, %[in].16b
: [out] "=x" (-> Repr),
: [in] "x" (block.repr),
),
};
}
/// Perform operations on multiple blocks in parallel. /// Perform operations on multiple blocks in parallel.
pub const parallel = struct { pub const parallel = struct {
/// The recommended number of AES encryption/decryption to perform in parallel for the chosen implementation. /// The recommended number of AES encryption/decryption to perform in parallel for the chosen implementation.
@ -275,6 +286,15 @@ pub fn BlockVec(comptime blocks_count: comptime_int) type {
} }
return out; return out;
} }
/// Apply the inverse MixColumns operation to each block in the vector.
pub fn invMixColumns(block_vec: Self) Self {
var out: Self = undefined;
inline for (0..native_words) |i| {
out.repr[i] = block_vec.repr[i].invMixColumns();
}
return out;
}
}; };
} }

View file

@ -265,6 +265,26 @@ pub const Block = struct {
return Block{ .repr = x }; 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. /// Perform operations on multiple blocks in parallel.
pub const parallel = struct { pub const parallel = struct {
/// The recommended number of AES encryption/decryption to perform in parallel for the chosen implementation. /// The recommended number of AES encryption/decryption to perform in parallel for the chosen implementation.
@ -441,6 +461,15 @@ pub fn BlockVec(comptime blocks_count: comptime_int) type {
} }
return out; return out;
} }
/// Apply the inverse MixColumns operation to each block in the vector.
pub fn invMixColumns(block_vec: Self) Self {
var out: Self = undefined;
for (0..native_words) |i| {
out.repr[i] = block_vec.repr[i].invMixColumns();
}
return out;
}
}; };
} }