ECB/CBC Detection Oracle

This commit is contained in:
Armin Friedl 2025-02-15 12:57:38 +01:00
parent 3e03aedb9e
commit f9f27c772e
2 changed files with 92 additions and 0 deletions

View file

@ -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;

View file

@ -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 });
}
}