authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-09 14:04:02-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-09 19:47:06-07:00
log54151428e5236a0612fe36837ed5a6c1e28aec7a
tree89e3fda0a275c3e6aa94f9f03a7050fcba30bd72
parentae5bf2faabfe949e2cb94a230e79614c7eafd84a

std.crypto: better names for everything in utils

std.crypto has quite a few instances of breaking naming conventions. This is the beginning of an effort to address that. Deprecates `std.crypto.utils`.

19 files changed, 263 insertions(+), 242 deletions(-)

doc/langref.html.in+1-1
......@@ -5053,7 +5053,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
50535053 It may have any alignment, and it may have any element type.</p>
50545054 <p>{#syntax#}elem{#endsyntax#} is coerced to the element type of {#syntax#}dest{#endsyntax#}.</p>
50555055 <p>For securely zeroing out sensitive contents from memory, you should use
5056 {#syntax#}std.crypto.utils.secureZero{#endsyntax#}</p>
5056 {#syntax#}std.crypto.secureZero{#endsyntax#}</p>
50575057 {#header_close#}
50585058
50595059 {#header_open|@min#}
lib/std/crypto.zig+37-3
......@@ -2,6 +2,8 @@
22
33const root = @import("root");
44
5pub const timing_safe = @import("crypto/timing_safe.zig");
6
57/// Authenticated Encryption with Associated Data
68pub const aead = struct {
79 pub const aegis = struct {
......@@ -180,8 +182,6 @@ pub const nacl = struct {
180182 pub const SealedBox = salsa20.SealedBox;
181183};
182184
183pub const utils = @import("crypto/utils.zig");
184
185185/// Finite-field arithmetic.
186186pub const ff = @import("crypto/ff.zig");
187187
......@@ -301,7 +301,8 @@ test {
301301 _ = nacl.SecretBox;
302302 _ = nacl.SealedBox;
303303
304 _ = utils;
304 _ = secureZero;
305 _ = timing_safe;
305306 _ = ff;
306307 _ = random;
307308 _ = errors;
......@@ -353,3 +354,36 @@ test "issue #4532: no index out of bounds" {
353354 try std.testing.expectEqual(out1, out2);
354355 }
355356}
357
358/// Sets a slice to zeroes.
359/// Prevents the store from being optimized out.
360pub inline fn secureZero(comptime T: type, s: []volatile T) void {
361 @memset(s, 0);
362}
363
364test secureZero {
365 var a = [_]u8{0xfe} ** 8;
366 var b = [_]u8{0xfe} ** 8;
367
368 @memset(&a, 0);
369 secureZero(u8, &b);
370
371 try std.testing.expectEqualSlices(u8, &a, &b);
372}
373
374/// Deprecated in favor of `std.crypto`. To be removed after Zig 0.14.0 is released.
375///
376/// As a reminder, never use "utils" in a namespace (in any programming language).
377/// https://ziglang.org/documentation/0.13.0/#Avoid-Redundancy-in-Names
378pub const utils = struct {
379 /// Deprecated in favor of `std.crypto.secureZero`.
380 pub const secureZero = std.crypto.secureZero;
381 /// Deprecated in favor of `std.crypto.timing_safe.eql`.
382 pub const timingSafeEql = timing_safe.eql;
383 /// Deprecated in favor of `std.crypto.timing_safe.compare`.
384 pub const timingSafeCompare = timing_safe.compare;
385 /// Deprecated in favor of `std.crypto.timing_safe.add`.
386 pub const timingSafeAdd = timing_safe.add;
387 /// Deprecated in favor of `std.crypto.timing_safe.sub`.
388 pub const timingSafeSub = timing_safe.sub;
389};
lib/std/crypto/aegis.zig+4-4
......@@ -208,9 +208,9 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type {
208208 blocks[4] = blocks[4].xorBlocks(AesBlock.fromBytes(dst[16..32]));
209209 }
210210 var computed_tag = state.mac(tag_bits, ad.len, m.len);
211 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
211 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
212212 if (!verify) {
213 crypto.utils.secureZero(u8, &computed_tag);
213 crypto.secureZero(u8, &computed_tag);
214214 @memset(m, undefined);
215215 return error.AuthenticationFailed;
216216 }
......@@ -390,9 +390,9 @@ fn Aegis256Generic(comptime tag_bits: u9) type {
390390 blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(&dst));
391391 }
392392 var computed_tag = state.mac(tag_bits, ad.len, m.len);
393 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
393 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
394394 if (!verify) {
395 crypto.utils.secureZero(u8, &computed_tag);
395 crypto.secureZero(u8, &computed_tag);
396396 @memset(m, undefined);
397397 return error.AuthenticationFailed;
398398 }
lib/std/crypto/aes_gcm.zig+2-2
......@@ -95,9 +95,9 @@ fn AesGcm(comptime Aes: anytype) type {
9595 computed_tag[i] ^= x;
9696 }
9797
98 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
98 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
9999 if (!verify) {
100 crypto.utils.secureZero(u8, &computed_tag);
100 crypto.secureZero(u8, &computed_tag);
101101 @memset(m, undefined);
102102 return error.AuthenticationFailed;
103103 }
lib/std/crypto/aes_ocb.zig+2-2
......@@ -234,9 +234,9 @@ fn AesOcb(comptime Aes: anytype) type {
234234 var e = xorBlocks(xorBlocks(sum, offset), lx.dol);
235235 aes_enc_ctx.encrypt(&e, &e);
236236 var computed_tag = xorBlocks(e, hash(aes_enc_ctx, &lx, ad));
237 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
237 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
238238 if (!verify) {
239 crypto.utils.secureZero(u8, &computed_tag);
239 crypto.secureZero(u8, &computed_tag);
240240 @memset(m, undefined);
241241 return error.AuthenticationFailed;
242242 }
lib/std/crypto/ascon.zig+1-1
......@@ -152,7 +152,7 @@ pub fn State(comptime endian: std.builtin.Endian) type {
152152
153153 /// Clear the entire state, disabling compiler optimizations.
154154 pub fn secureZero(self: *Self) void {
155 std.crypto.utils.secureZero(u64, &self.st);
155 std.crypto.secureZero(u64, &self.st);
156156 }
157157
158158 /// Apply a reduced-round permutation to the state.
lib/std/crypto/bcrypt.zig+3-4
......@@ -9,7 +9,6 @@ const pwhash = crypto.pwhash;
99const testing = std.testing;
1010const HmacSha512 = crypto.auth.hmac.sha2.HmacSha512;
1111const Sha512 = crypto.hash.sha2.Sha512;
12const utils = crypto.utils;
1312
1413const phc_format = @import("phc_encoding.zig");
1514
......@@ -446,7 +445,7 @@ pub fn bcrypt(
446445 state.expand0(passwordZ);
447446 state.expand0(salt[0..]);
448447 }
449 utils.secureZero(u8, &password_buf);
448 crypto.secureZero(u8, &password_buf);
450449
451450 var cdata = [6]u32{ 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 }; // "OrpheanBeholderScryDoubt"
452451 k = 0;
......@@ -556,8 +555,8 @@ const pbkdf_prf = struct {
556555 }
557556
558557 // zap
559 crypto.utils.secureZero(u32, &cdata);
560 crypto.utils.secureZero(u32, &state.subkeys);
558 crypto.secureZero(u32, &cdata);
559 crypto.secureZero(u32, &state.subkeys);
561560
562561 return out;
563562 }
lib/std/crypto/chacha20.zig+2-2
......@@ -714,9 +714,9 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type {
714714 var computed_tag: [16]u8 = undefined;
715715 mac.final(computed_tag[0..]);
716716
717 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
717 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
718718 if (!verify) {
719 crypto.utils.secureZero(u8, &computed_tag);
719 crypto.secureZero(u8, &computed_tag);
720720 @memset(m, undefined);
721721 return error.AuthenticationFailed;
722722 }
lib/std/crypto/ff.zig+2-2
......@@ -225,12 +225,12 @@ pub fn Uint(comptime max_bits: comptime_int) type {
225225
226226 /// Returns `true` if both integers are equal.
227227 pub fn eql(x: Self, y: Self) bool {
228 return crypto.utils.timingSafeEql([max_limbs_count]Limb, x.limbs_buffer, y.limbs_buffer);
228 return crypto.timing_safe.eql([max_limbs_count]Limb, x.limbs_buffer, y.limbs_buffer);
229229 }
230230
231231 /// Compares two integers.
232232 pub fn compare(x: Self, y: Self) math.Order {
233 return crypto.utils.timingSafeCompare(
233 return crypto.timing_safe.compare(
234234 Limb,
235235 x.limbsConst(),
236236 y.limbsConst(),
lib/std/crypto/ghash_polyval.zig+1-2
......@@ -3,7 +3,6 @@ const builtin = @import("builtin");
33const assert = std.debug.assert;
44const math = std.math;
55const mem = std.mem;
6const utils = std.crypto.utils;
76
87const Precomp = u128;
98
......@@ -403,7 +402,7 @@ fn Hash(comptime endian: std.builtin.Endian, comptime shift_key: bool) type {
403402 st.pad();
404403 mem.writeInt(u128, out[0..16], st.acc, endian);
405404
406 utils.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Self)]);
405 std.crypto.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Self)]);
407406 }
408407
409408 /// Compute the GHASH of a message.
lib/std/crypto/isap.zig+2-2
......@@ -158,9 +158,9 @@ pub const IsapA128A = struct {
158158 /// Contents of `m` are undefined if an error is returned.
159159 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
160160 var computed_tag = mac(c, ad, npub, key);
161 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
161 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
162162 if (!verify) {
163 crypto.utils.secureZero(u8, &computed_tag);
163 crypto.secureZero(u8, &computed_tag);
164164 @memset(m, undefined);
165165 return error.AuthenticationFailed;
166166 }
lib/std/crypto/keccak_p.zig+1-1
......@@ -132,7 +132,7 @@ pub fn KeccakF(comptime f: u11) type {
132132
133133 /// Clear the entire state, disabling compiler optimizations.
134134 pub fn secureZero(self: *Self) void {
135 std.crypto.utils.secureZero(T, &self.st);
135 std.crypto.secureZero(T, &self.st);
136136 }
137137
138138 inline fn round(self: *Self, rc: T) void {
lib/std/crypto/ml_kem.zig+1-1
......@@ -1508,7 +1508,7 @@ fn Mat(comptime K: u8) type {
15081508
15091509// Returns `true` if a ≠ b.
15101510fn ctneq(comptime len: usize, a: [len]u8, b: [len]u8) u1 {
1511 return 1 - @intFromBool(crypto.utils.timingSafeEql([len]u8, a, b));
1511 return 1 - @intFromBool(crypto.timing_safe.eql([len]u8, a, b));
15121512}
15131513
15141514// Copy src into dst given b = 1.
lib/std/crypto/pcurves/common.zig+1-1
......@@ -57,7 +57,7 @@ pub fn Field(comptime params: FieldParams) type {
5757 mem.writeInt(std.meta.Int(.unsigned, encoded_length * 8), &fos, field_order, .little);
5858 break :fos fos;
5959 };
60 if (crypto.utils.timingSafeCompare(u8, &s, &field_order_s, .little) != .lt) {
60 if (crypto.timing_safe.compare(u8, &s, &field_order_s, .little) != .lt) {
6161 return error.NonCanonical;
6262 }
6363 }
lib/std/crypto/poly1305.zig+1-2
......@@ -1,5 +1,4 @@
11const std = @import("../std.zig");
2const utils = std.crypto.utils;
32const mem = std.mem;
43const mulWide = std.math.mulWide;
54
......@@ -185,7 +184,7 @@ pub const Poly1305 = struct {
185184 mem.writeInt(u64, out[0..8], st.h[0], .little);
186185 mem.writeInt(u64, out[8..16], st.h[1], .little);
187186
188 utils.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Poly1305)]);
187 std.crypto.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Poly1305)]);
189188 }
190189
191190 pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void {
lib/std/crypto/salsa20.zig+3-4
......@@ -4,7 +4,6 @@ const crypto = std.crypto;
44const debug = std.debug;
55const math = std.math;
66const mem = std.mem;
7const utils = std.crypto.utils;
87
98const Poly1305 = crypto.onetimeauth.Poly1305;
109const Blake2b = crypto.hash.blake2.Blake2b;
......@@ -419,9 +418,9 @@ pub const XSalsa20Poly1305 = struct {
419418 var computed_tag: [tag_length]u8 = undefined;
420419 mac.final(&computed_tag);
421420
422 const verify = utils.timingSafeEql([tag_length]u8, computed_tag, tag);
421 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
423422 if (!verify) {
424 utils.secureZero(u8, &computed_tag);
423 crypto.secureZero(u8, &computed_tag);
425424 @memset(m, undefined);
426425 return error.AuthenticationFailed;
427426 }
......@@ -540,7 +539,7 @@ pub const SealedBox = struct {
540539 const nonce = createNonce(ekp.public_key, public_key);
541540 c[0..public_length].* = ekp.public_key;
542541 try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key);
543 utils.secureZero(u8, ekp.secret_key[0..]);
542 crypto.secureZero(u8, ekp.secret_key[0..]);
544543 }
545544
546545 /// Decrypt a message using a key pair.
lib/std/crypto/timing_safe.zig created+197
......@@ -0,0 +1,197 @@
1//! Please see this accepted proposal for the long-term plans regarding
2//! constant-time operations in Zig: https://github.com/ziglang/zig/issues/1776
3
4const std = @import("../std.zig");
5const assert = std.debug.assert;
6const Endian = std.builtin.Endian;
7const Order = std.math.Order;
8
9/// Compares two arrays in constant time (for a given length) and returns whether they are equal.
10/// This function was designed to compare short cryptographic secrets (MACs, signatures).
11/// For all other applications, use mem.eql() instead.
12pub fn eql(comptime T: type, a: T, b: T) bool {
13 switch (@typeInfo(T)) {
14 .Array => |info| {
15 const C = info.child;
16 if (@typeInfo(C) != .Int) {
17 @compileError("Elements to be compared must be integers");
18 }
19 var acc = @as(C, 0);
20 for (a, 0..) |x, i| {
21 acc |= x ^ b[i];
22 }
23 const s = @typeInfo(C).Int.bits;
24 const Cu = std.meta.Int(.unsigned, s);
25 const Cext = std.meta.Int(.unsigned, s + 1);
26 return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s))));
27 },
28 .Vector => |info| {
29 const C = info.child;
30 if (@typeInfo(C) != .Int) {
31 @compileError("Elements to be compared must be integers");
32 }
33 const acc = @reduce(.Or, a ^ b);
34 const s = @typeInfo(C).Int.bits;
35 const Cu = std.meta.Int(.unsigned, s);
36 const Cext = std.meta.Int(.unsigned, s + 1);
37 return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s))));
38 },
39 else => {
40 @compileError("Only arrays and vectors can be compared");
41 },
42 }
43}
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
47pub fn compare(comptime T: type, a: []const T, b: []const T, endian: Endian) Order {
48 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 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 |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq;
63 eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits));
64 }
65 } else {
66 for (a, 0..) |x1, i| {
67 const x2 = b[i];
68 gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq;
69 eq &= @as(T, @truncate((@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
80/// Add two integers serialized as arrays of the same size, in constant time.
81/// The result is stored into `result`, and `true` is returned if an overflow occurred.
82pub fn add(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool {
83 const len = a.len;
84 assert(len == b.len and len == result.len);
85 var carry: u1 = 0;
86 if (endian == .little) {
87 var i: usize = 0;
88 while (i < len) : (i += 1) {
89 const ov1 = @addWithOverflow(a[i], b[i]);
90 const ov2 = @addWithOverflow(ov1[0], carry);
91 result[i] = ov2[0];
92 carry = ov1[1] | ov2[1];
93 }
94 } else {
95 var i: usize = len;
96 while (i != 0) {
97 i -= 1;
98 const ov1 = @addWithOverflow(a[i], b[i]);
99 const ov2 = @addWithOverflow(ov1[0], carry);
100 result[i] = ov2[0];
101 carry = ov1[1] | ov2[1];
102 }
103 }
104 return @as(bool, @bitCast(carry));
105}
106
107/// Subtract two integers serialized as arrays of the same size, in constant time.
108/// The result is stored into `result`, and `true` is returned if an underflow occurred.
109pub fn sub(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool {
110 const len = a.len;
111 assert(len == b.len and len == result.len);
112 var borrow: u1 = 0;
113 if (endian == .little) {
114 var i: usize = 0;
115 while (i < len) : (i += 1) {
116 const ov1 = @subWithOverflow(a[i], b[i]);
117 const ov2 = @subWithOverflow(ov1[0], borrow);
118 result[i] = ov2[0];
119 borrow = ov1[1] | ov2[1];
120 }
121 } else {
122 var i: usize = len;
123 while (i != 0) {
124 i -= 1;
125 const ov1 = @subWithOverflow(a[i], b[i]);
126 const ov2 = @subWithOverflow(ov1[0], borrow);
127 result[i] = ov2[0];
128 borrow = ov1[1] | ov2[1];
129 }
130 }
131 return @as(bool, @bitCast(borrow));
132}
133
134test eql {
135 const random = std.crypto.random;
136 const expect = std.testing.expect;
137 var a: [100]u8 = undefined;
138 var b: [100]u8 = undefined;
139 random.bytes(a[0..]);
140 random.bytes(b[0..]);
141 try expect(!eql([100]u8, a, b));
142 a = b;
143 try expect(eql([100]u8, a, b));
144}
145
146test "eql (vectors)" {
147 if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest;
148
149 const random = std.crypto.random;
150 const expect = std.testing.expect;
151 var a: [100]u8 = undefined;
152 var b: [100]u8 = undefined;
153 random.bytes(a[0..]);
154 random.bytes(b[0..]);
155 const v1: @Vector(100, u8) = a;
156 const v2: @Vector(100, u8) = b;
157 try expect(!eql(@Vector(100, u8), v1, v2));
158 const v3: @Vector(100, u8) = a;
159 try expect(eql(@Vector(100, u8), v1, v3));
160}
161
162test compare {
163 const expectEqual = std.testing.expectEqual;
164 var a = [_]u8{10} ** 32;
165 var b = [_]u8{10} ** 32;
166 try expectEqual(compare(u8, &a, &b, .big), .eq);
167 try expectEqual(compare(u8, &a, &b, .little), .eq);
168 a[31] = 1;
169 try expectEqual(compare(u8, &a, &b, .big), .lt);
170 try expectEqual(compare(u8, &a, &b, .little), .lt);
171 a[0] = 20;
172 try expectEqual(compare(u8, &a, &b, .big), .gt);
173 try expectEqual(compare(u8, &a, &b, .little), .lt);
174}
175
176test "add and sub" {
177 const expectEqual = std.testing.expectEqual;
178 const expectEqualSlices = std.testing.expectEqualSlices;
179 const random = std.crypto.random;
180 const len = 32;
181 var a: [len]u8 = undefined;
182 var b: [len]u8 = undefined;
183 var c: [len]u8 = undefined;
184 const zero = [_]u8{0} ** len;
185 var iterations: usize = 100;
186 while (iterations != 0) : (iterations -= 1) {
187 random.bytes(&a);
188 random.bytes(&b);
189 const endian = if (iterations % 2 == 0) Endian.big else Endian.little;
190 _ = sub(u8, &a, &b, &c, endian); // a-b
191 _ = add(u8, &c, &b, &c, endian); // (a-b)+b
192 try expectEqualSlices(u8, &c, &a);
193 const borrow = sub(u8, &c, &a, &c, endian); // ((a-b)+b)-a
194 try expectEqualSlices(u8, &c, &zero);
195 try expectEqual(borrow, false);
196 }
197}
lib/std/crypto/tlcsprng.zig+2-2
......@@ -137,7 +137,7 @@ fn childAtForkHandler() callconv(.C) void {
137137 // The atfork handler is global, this function may be called after
138138 // fork()-ing threads that never initialized the CSPRNG context.
139139 if (wipe_mem.len == 0) return;
140 std.crypto.utils.secureZero(u8, wipe_mem);
140 std.crypto.secureZero(u8, wipe_mem);
141141}
142142
143143fn fillWithCsprng(buffer: []u8) void {
......@@ -159,7 +159,7 @@ fn initAndFill(buffer: []u8) void {
159159
160160 const ctx = @as(*Context, @ptrCast(wipe_mem.ptr));
161161 ctx.rng = Rng.init(seed);
162 std.crypto.utils.secureZero(u8, &seed);
162 std.crypto.secureZero(u8, &seed);
163163
164164 // This is at the end so that accidental recursive dependencies result
165165 // in stack overflows instead of invalid random data.
lib/std/crypto/utils.zig deleted-206
......@@ -1,206 +0,0 @@
1const std = @import("../std.zig");
2const debug = std.debug;
3const mem = std.mem;
4const random = std.crypto.random;
5const testing = std.testing;
6
7const Endian = std.builtin.Endian;
8const Order = std.math.Order;
9
10/// Compares two arrays in constant time (for a given length) and returns whether they are equal.
11/// This function was designed to compare short cryptographic secrets (MACs, signatures).
12/// For all other applications, use mem.eql() instead.
13pub fn timingSafeEql(comptime T: type, a: T, b: T) bool {
14 switch (@typeInfo(T)) {
15 .Array => |info| {
16 const C = info.child;
17 if (@typeInfo(C) != .Int) {
18 @compileError("Elements to be compared must be integers");
19 }
20 var acc = @as(C, 0);
21 for (a, 0..) |x, i| {
22 acc |= x ^ b[i];
23 }
24 const s = @typeInfo(C).Int.bits;
25 const Cu = std.meta.Int(.unsigned, s);
26 const Cext = std.meta.Int(.unsigned, s + 1);
27 return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s))));
28 },
29 .Vector => |info| {
30 const C = info.child;
31 if (@typeInfo(C) != .Int) {
32 @compileError("Elements to be compared must be integers");
33 }
34 const acc = @reduce(.Or, a ^ b);
35 const s = @typeInfo(C).Int.bits;
36 const Cu = std.meta.Int(.unsigned, s);
37 const Cext = std.meta.Int(.unsigned, s + 1);
38 return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s))));
39 },
40 else => {
41 @compileError("Only arrays and vectors can be compared");
42 },
43 }
44}
45
46/// Compare two integers serialized as arrays of the same size, in constant time.
47/// Returns .lt if a<b, .gt if a>b and .eq if a=b
48pub fn timingSafeCompare(comptime T: type, a: []const T, b: []const T, endian: Endian) Order {
49 debug.assert(a.len == b.len);
50 const bits = switch (@typeInfo(T)) {
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 const Cext = std.meta.Int(.unsigned, bits + 1);
55 var gt: T = 0;
56 var eq: T = 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 |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq;
64 eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits));
65 }
66 } else {
67 for (a, 0..) |x1, i| {
68 const x2 = b[i];
69 gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq;
70 eq &= @as(T, @truncate((@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
81/// Add two integers serialized as arrays of the same size, in constant time.
82/// The result is stored into `result`, and `true` is returned if an overflow occurred.
83pub fn timingSafeAdd(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool {
84 const len = a.len;
85 debug.assert(len == b.len and len == result.len);
86 var carry: u1 = 0;
87 if (endian == .little) {
88 var i: usize = 0;
89 while (i < len) : (i += 1) {
90 const ov1 = @addWithOverflow(a[i], b[i]);
91 const ov2 = @addWithOverflow(ov1[0], carry);
92 result[i] = ov2[0];
93 carry = ov1[1] | ov2[1];
94 }
95 } else {
96 var i: usize = len;
97 while (i != 0) {
98 i -= 1;
99 const ov1 = @addWithOverflow(a[i], b[i]);
100 const ov2 = @addWithOverflow(ov1[0], carry);
101 result[i] = ov2[0];
102 carry = ov1[1] | ov2[1];
103 }
104 }
105 return @as(bool, @bitCast(carry));
106}
107
108/// Subtract two integers serialized as arrays of the same size, in constant time.
109/// The result is stored into `result`, and `true` is returned if an underflow occurred.
110pub fn timingSafeSub(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool {
111 const len = a.len;
112 debug.assert(len == b.len and len == result.len);
113 var borrow: u1 = 0;
114 if (endian == .little) {
115 var i: usize = 0;
116 while (i < len) : (i += 1) {
117 const ov1 = @subWithOverflow(a[i], b[i]);
118 const ov2 = @subWithOverflow(ov1[0], borrow);
119 result[i] = ov2[0];
120 borrow = ov1[1] | ov2[1];
121 }
122 } else {
123 var i: usize = len;
124 while (i != 0) {
125 i -= 1;
126 const ov1 = @subWithOverflow(a[i], b[i]);
127 const ov2 = @subWithOverflow(ov1[0], borrow);
128 result[i] = ov2[0];
129 borrow = ov1[1] | ov2[1];
130 }
131 }
132 return @as(bool, @bitCast(borrow));
133}
134
135/// Sets a slice to zeroes.
136/// Prevents the store from being optimized out.
137pub inline fn secureZero(comptime T: type, s: []T) void {
138 @memset(@as([]volatile T, s), 0);
139}
140
141test timingSafeEql {
142 var a: [100]u8 = undefined;
143 var b: [100]u8 = undefined;
144 random.bytes(a[0..]);
145 random.bytes(b[0..]);
146 try testing.expect(!timingSafeEql([100]u8, a, b));
147 a = b;
148 try testing.expect(timingSafeEql([100]u8, a, b));
149}
150
151test "timingSafeEql (vectors)" {
152 if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest;
153
154 var a: [100]u8 = undefined;
155 var b: [100]u8 = undefined;
156 random.bytes(a[0..]);
157 random.bytes(b[0..]);
158 const v1: @Vector(100, u8) = a;
159 const v2: @Vector(100, u8) = b;
160 try testing.expect(!timingSafeEql(@Vector(100, u8), v1, v2));
161 const v3: @Vector(100, u8) = a;
162 try testing.expect(timingSafeEql(@Vector(100, u8), v1, v3));
163}
164
165test timingSafeCompare {
166 var a = [_]u8{10} ** 32;
167 var b = [_]u8{10} ** 32;
168 try testing.expectEqual(timingSafeCompare(u8, &a, &b, .big), .eq);
169 try testing.expectEqual(timingSafeCompare(u8, &a, &b, .little), .eq);
170 a[31] = 1;
171 try testing.expectEqual(timingSafeCompare(u8, &a, &b, .big), .lt);
172 try testing.expectEqual(timingSafeCompare(u8, &a, &b, .little), .lt);
173 a[0] = 20;
174 try testing.expectEqual(timingSafeCompare(u8, &a, &b, .big), .gt);
175 try testing.expectEqual(timingSafeCompare(u8, &a, &b, .little), .lt);
176}
177
178test "timingSafe{Add,Sub}" {
179 const len = 32;
180 var a: [len]u8 = undefined;
181 var b: [len]u8 = undefined;
182 var c: [len]u8 = undefined;
183 const zero = [_]u8{0} ** len;
184 var iterations: usize = 100;
185 while (iterations != 0) : (iterations -= 1) {
186 random.bytes(&a);
187 random.bytes(&b);
188 const endian = if (iterations % 2 == 0) Endian.big else Endian.little;
189 _ = timingSafeSub(u8, &a, &b, &c, endian); // a-b
190 _ = timingSafeAdd(u8, &c, &b, &c, endian); // (a-b)+b
191 try testing.expectEqualSlices(u8, &c, &a);
192 const borrow = timingSafeSub(u8, &c, &a, &c, endian); // ((a-b)+b)-a
193 try testing.expectEqualSlices(u8, &c, &zero);
194 try testing.expectEqual(borrow, false);
195 }
196}
197
198test secureZero {
199 var a = [_]u8{0xfe} ** 8;
200 var b = [_]u8{0xfe} ** 8;
201
202 @memset(a[0..], 0);
203 secureZero(u8, b[0..]);
204
205 try testing.expectEqualSlices(u8, a[0..], b[0..]);
206}