| ... | ... | @@ -1,7 +1,11 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | const debug = std.debug; |
| 2 | 3 | const mem = std.mem; |
| 3 | 4 | const testing = std.testing; |
| 4 | 5 | |
| 6 | const Endian = std.builtin.Endian; |
| 7 | const Order = std.math.Order; |
| 8 | |
| 5 | 9 | /// Compares two arrays in constant time (for a given length) and returns whether they are equal. |
| 6 | 10 | /// This function was designed to compare short cryptographic secrets (MACs, signatures). |
| 7 | 11 | /// For all other applications, use mem.eql() instead. |
| ... | ... | @@ -38,6 +42,41 @@ pub fn timingSafeEql(comptime T: type, a: T, b: T) bool { |
| 38 | 42 | } |
| 39 | 43 | } |
| 40 | 44 | |
| 45 | /// Compare two integers serialized as arrays of the same size, in constant time. |
| 46 | /// Returns .lt if a<b, .gt if a>b and .eq if a=b |
| 47 | pub fn timingSafeCompare(comptime T: type, a: []const T, b: []const T, endian: Endian) Order { |
| 48 | debug.assert(a.len == b.len); |
| 49 | const bits = switch (@typeInfo(T)) { |
| 50 | .Int => |cinfo| if (cinfo.signedness != .unsigned) @compileError("Elements to be compared must be unsigned") else cinfo.bits, |
| 51 | else => @compileError("Elements to be compared must be integers"), |
| 52 | }; |
| 53 | comptime const Cext = std.meta.Int(.unsigned, bits + 1); |
| 54 | var gt: T = 0; |
| 55 | var eq: T = 1; |
| 56 | if (endian == .Little) { |
| 57 | var i = a.len; |
| 58 | while (i != 0) { |
| 59 | i -= 1; |
| 60 | const x1 = a[i]; |
| 61 | const x2 = b[i]; |
| 62 | gt |= @truncate(T, (@as(Cext, x2) -% @as(Cext, x1)) >> bits) & eq; |
| 63 | eq &= @truncate(T, (@as(Cext, (x2 ^ x1)) -% 1) >> bits); |
| 64 | } |
| 65 | } else { |
| 66 | for (a) |x1, i| { |
| 67 | const x2 = b[i]; |
| 68 | gt |= @truncate(T, (@as(Cext, x2) -% @as(Cext, x1)) >> bits) & eq; |
| 69 | eq &= @truncate(T, (@as(Cext, (x2 ^ x1)) -% 1) >> bits); |
| 70 | } |
| 71 | } |
| 72 | if (gt != 0) { |
| 73 | return Order.gt; |
| 74 | } else if (eq != 0) { |
| 75 | return Order.eq; |
| 76 | } |
| 77 | return Order.lt; |
| 78 | } |
| 79 | |
| 41 | 80 | /// Sets a slice to zeroes. |
| 42 | 81 | /// Prevents the store from being optimized out. |
| 43 | 82 | pub fn secureZero(comptime T: type, s: []T) void { |
| ... | ... | @@ -70,6 +109,19 @@ test "crypto.utils.timingSafeEql (vectors)" { |
| 70 | 109 | testing.expect(timingSafeEql(std.meta.Vector(100, u8), v1, v3)); |
| 71 | 110 | } |
| 72 | 111 | |
| 112 | test "crypto.utils.timingSafeCompare" { |
| 113 | var a = [_]u8{10} ** 32; |
| 114 | var b = [_]u8{10} ** 32; |
| 115 | testing.expectEqual(timingSafeCompare(u8, &a, &b, .Big), .eq); |
| 116 | testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .eq); |
| 117 | a[31] = 1; |
| 118 | testing.expectEqual(timingSafeCompare(u8, &a, &b, .Big), .lt); |
| 119 | testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .lt); |
| 120 | a[0] = 20; |
| 121 | testing.expectEqual(timingSafeCompare(u8, &a, &b, .Big), .gt); |
| 122 | testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .lt); |
| 123 | } |
| 124 | |
| 73 | 125 | test "crypto.utils.secureZero" { |
| 74 | 126 | var a = [_]u8{0xfe} ** 8; |
| 75 | 127 | var b = [_]u8{0xfe} ** 8; |