diff --git a/src/aes_crack.zig b/src/aes_crack.zig index a05ae8f..4592589 100644 --- a/src/aes_crack.zig +++ b/src/aes_crack.zig @@ -38,6 +38,23 @@ pub fn detect(allocator: std.mem.Allocator, input: []const u8) !bool { return false; } +pub const Mode = enum { ECB, CBC }; + +/// Detect whether an oracle encrypts with ECB or CBC +pub fn detect_ecb_cbc(allocator: std.mem.Allocator, oracle: anytype) !Mode { + // all we need to do is creating long enough input to guarantee 2 + // equal input blocks which will encrypt to 2 equal cipher blocks + // in ECB but not in CBC. A length of 3x the block size should be + // sufficient in face of any padding. + const probe = [_]u8{0x00} ** (16 * 3); + const result = try oracle.oracleFn(allocator, &probe); + defer allocator.free(result); + + const ecb = try detect(allocator, result); + + return if (ecb) Mode.ECB else Mode.CBC; +} + test "detect_no" { const allocator = std.testing.allocator; diff --git a/src/main.zig b/src/main.zig index 8946582..b27eb12 100644 --- a/src/main.zig +++ b/src/main.zig @@ -55,6 +55,9 @@ pub fn main() !void { if (std.mem.eql(u8, args[1], "s2c10")) { try s2c10(allocator, stdout); } + + if (std.mem.eql(u8, args[1], "s2c11")) { + try s2c11(allocator, stdout); } } @@ -304,3 +307,75 @@ fn s2c10(allocator: std.mem.Allocator, stdout: anytype) !void { try stdout.print("{s}", .{result}); } + +fn s2c11(allocator: std.mem.Allocator, stdout: anytype) !void { + const Oracle = struct { + mode: aes.Mode, + alloc: std.mem.Allocator, + padded_input: []u8 = undefined, + + fn init(alloc: std.mem.Allocator, mode: aes.Mode) @This() { + return @This(){ .alloc = alloc, .mode = mode }; + } + + pub fn oracleFn(self: *@This(), alloc: std.mem.Allocator, input: []const u8) ![]u8 { + var rng = std.crypto.random; + const padding_start = rng.intRangeAtMost(u32, 0, 16); + const padding_end = 16 - padding_start; + + self.padded_input = try self.alloc.alloc(u8, (padding_start + input.len + padding_end)); + std.crypto.random.bytes(self.padded_input[0..padding_start]); + std.crypto.random.bytes(self.padded_input[(padding_start + input.len)..]); + + const key = try self.alloc.alloc(u8, 16); + defer self.alloc.free(key); + std.crypto.random.bytes(key); + + const iv = try self.alloc.alloc(u8, 16); + defer self.alloc.free(iv); + std.crypto.random.bytes(iv); + + var cipher = switch (self.mode) { + .ECB => aes.AES.init_ecb(self.alloc, key), + .CBC => aes.AES.init_cbc(self.alloc, key, iv), + }; + defer cipher.deinit(); + + return try cipher.encrypt(alloc, input); + } + + fn deinit(self: *@This()) void { + self.alloc.free(self.padded_input); + } + }; + + for (0..1000) |_| { + const mode_rnd = std.crypto.random.intRangeAtMost(u1, 0, 1); + const mode = switch (mode_rnd) { + 0 => aes.Mode.ECB, + 1 => aes.Mode.CBC, + }; + + var oracle = Oracle.init(allocator, mode); + defer oracle.deinit(); + + const result = try aes_crack.detect_ecb_cbc(allocator, &oracle); + + if (mode == aes.Mode.CBC and result == aes_crack.Mode.CBC) { + try stdout.print("SUCCESS: ", .{}); + } + if (mode == aes.Mode.ECB and result == aes_crack.Mode.ECB) { + try stdout.print("SUCCESS: ", .{}); + } + if (mode == aes.Mode.CBC and result == aes_crack.Mode.ECB) { + try stdout.print("FAIL: ", .{}); + @panic("Detection failed"); + } + if (mode == aes.Mode.ECB and result == aes_crack.Mode.CBC) { + try stdout.print("FAIL: ", .{}); + @panic("Detection failed"); + } + + try stdout.print("Mode: {}, Detected: {}\n", .{ mode, result }); + } +}