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