authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-26 23:03:46-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-04-26 23:03:46-04:00
log1c0259f11e84ce9605a5e8e8030d51873ab1fb0c
tree171165b6d8c6e6ac8d59fa03e8c6a4c5f50dea82
parent030fa5e7ebc21339728c79f33bf5d5d22e0a760e
parentd3361c41db638dc606c2e7259900cd281c5e95e4
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8609 from jedisct1/timingSafeCompare

Add std.crypto.utils.timingSafeCompare

1 files changed, 52 insertions(+), 0 deletions(-)

lib/std/crypto/utils.zig+52
......@@ -1,7 +1,11 @@
11const std = @import("../std.zig");
2const debug = std.debug;
23const mem = std.mem;
34const testing = std.testing;
45
6const Endian = std.builtin.Endian;
7const Order = std.math.Order;
8
59/// Compares two arrays in constant time (for a given length) and returns whether they are equal.
610/// This function was designed to compare short cryptographic secrets (MACs, signatures).
711/// For all other applications, use mem.eql() instead.
......@@ -38,6 +42,41 @@ pub fn timingSafeEql(comptime T: type, a: T, b: T) bool {
3842 }
3943}
4044
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
47pub 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
4180/// Sets a slice to zeroes.
4281/// Prevents the store from being optimized out.
4382pub fn secureZero(comptime T: type, s: []T) void {
......@@ -70,6 +109,19 @@ test "crypto.utils.timingSafeEql (vectors)" {
70109 testing.expect(timingSafeEql(std.meta.Vector(100, u8), v1, v3));
71110}
72111
112test "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
73125test "crypto.utils.secureZero" {
74126 var a = [_]u8{0xfe} ** 8;
75127 var b = [_]u8{0xfe} ** 8;