authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2021-04-24 21:54:42+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2021-04-25 17:07:11+02:00
log0747591c3d7469cf3ac5c1a6a80b6058386e337a
tree9efa1127126ffca59aa70fad5fa2529628189580
parent37b05742ff1544bccf7c8ae9b12c6707a5a54df2

Add std.crypto.utils.timingSafeCompare

A little function to complement the existing crypto.utils.timingSafeEql function with a way to compare large numbers serialized as arrays. This is useful to compare nonces and to check that group elements are in canonical form. Absence of side channels remains a best effort, reusing the common pattern we use elsewhere.

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

lib/std/crypto/utils.zig+58
......@@ -2,6 +2,9 @@ const std = @import("../std.zig");
22const mem = std.mem;
33const testing = std.testing;
44
5const Endian = std.builtin.Endian;
6const Order = std.math.Order;
7
58/// Compares two arrays in constant time (for a given length) and returns whether they are equal.
69/// This function was designed to compare short cryptographic secrets (MACs, signatures).
710/// For all other applications, use mem.eql() instead.
......@@ -38,6 +41,48 @@ pub fn timingSafeEql(comptime T: type, a: T, b: T) bool {
3841 }
3942}
4043
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
46pub 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
4186/// Sets a slice to zeroes.
4287/// Prevents the store from being optimized out.
4388pub fn secureZero(comptime T: type, s: []T) void {
......@@ -70,6 +115,19 @@ test "crypto.utils.timingSafeEql (vectors)" {
70115 testing.expect(timingSafeEql(std.meta.Vector(100, u8), v1, v3));
71116}
72117
118test "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
73131test "crypto.utils.secureZero" {
74132 var a = [_]u8{0xfe} ** 8;
75133 var b = [_]u8{0xfe} ** 8;