authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-10-04 02:46:36-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-10-04 02:46:36-04:00
log8170a3d574a7118a6ee616d1b1258b2e839de173
tree4c9102a87c2278792eb1a3c13ede38f9a76faa54
parent5fda4fe4c80011aa1a440aecdeaa281f846c7a0d
parent97fd0974b9372f65c661908fb1ffc01d68255dae
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #6463 from jedisct1/ghash

std/crypto: add GHASH implementation

4 files changed, 315 insertions(+), 8 deletions(-)

lib/std/crypto.zig+1
...@@ -65,6 +65,7 @@ pub const kdf = struct {...@@ -65,6 +65,7 @@ pub const kdf = struct {
65/// MAC functions requiring single-use secret keys.65/// MAC functions requiring single-use secret keys.
66pub const onetimeauth = struct {66pub const onetimeauth = struct {
67 pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305;67 pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305;
68 pub const Ghash = @import("crypto/ghash.zig").Ghash;
68};69};
6970
70/// A password hashing function derives a uniform key from low-entropy input material such as passwords.71/// A password hashing function derives a uniform key from low-entropy input material such as passwords.
lib/std/crypto/benchmark.zig+1
...@@ -57,6 +57,7 @@ pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64...@@ -57,6 +57,7 @@ pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64
57}57}
5858
59const macs = [_]Crypto{59const macs = [_]Crypto{
60 Crypto{ .ty = crypto.onetimeauth.Ghash, .name = "ghash" },
60 Crypto{ .ty = crypto.onetimeauth.Poly1305, .name = "poly1305" },61 Crypto{ .ty = crypto.onetimeauth.Poly1305, .name = "poly1305" },
61 Crypto{ .ty = crypto.auth.hmac.HmacMd5, .name = "hmac-md5" },62 Crypto{ .ty = crypto.auth.hmac.HmacMd5, .name = "hmac-md5" },
62 Crypto{ .ty = crypto.auth.hmac.HmacSha1, .name = "hmac-sha1" },63 Crypto{ .ty = crypto.auth.hmac.HmacSha1, .name = "hmac-sha1" },
lib/std/crypto/ghash.zig created+310
...@@ -0,0 +1,310 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6//
7// Adapted from BearSSL's ctmul64 implementation originally written by Thomas Pornin <pornin@bolet.org>
8
9const std = @import("../std.zig");
10const assert = std.debug.assert;
11const math = std.math;
12const mem = std.mem;
13
14/// GHASH is a universal hash function that features multiplication
15/// by a fixed parameter within a Galois field.
16///
17/// It is not a general purpose hash function - The key must be secret, unpredictable and never reused.
18///
19/// GHASH is typically used to compute the authentication tag in the AES-GCM construction.
20pub const Ghash = struct {
21 pub const block_size: usize = 16;
22 pub const mac_length = 16;
23 pub const minimum_key_length = 16;
24
25 y0: u64 = 0,
26 y1: u64 = 0,
27 h0: u64,
28 h1: u64,
29 h2: u64,
30 h0r: u64,
31 h1r: u64,
32 h2r: u64,
33
34 hh0: u64 = undefined,
35 hh1: u64 = undefined,
36 hh2: u64 = undefined,
37 hh0r: u64 = undefined,
38 hh1r: u64 = undefined,
39 hh2r: u64 = undefined,
40
41 leftover: usize = 0,
42 buf: [block_size]u8 align(16) = undefined,
43
44 pub fn init(key: *const [minimum_key_length]u8) Ghash {
45 const h1 = mem.readIntBig(u64, key[0..8]);
46 const h0 = mem.readIntBig(u64, key[8..16]);
47 const h1r = @bitReverse(u64, h1);
48 const h0r = @bitReverse(u64, h0);
49 const h2 = h0 ^ h1;
50 const h2r = h0r ^ h1r;
51
52 if (std.builtin.mode == .ReleaseSmall) {
53 return Ghash{
54 .h0 = h0,
55 .h1 = h1,
56 .h2 = h2,
57 .h0r = h0r,
58 .h1r = h1r,
59 .h2r = h2r,
60 };
61 } else {
62 // Precompute H^2
63 var hh = Ghash{
64 .h0 = h0,
65 .h1 = h1,
66 .h2 = h2,
67 .h0r = h0r,
68 .h1r = h1r,
69 .h2r = h2r,
70 };
71 hh.update(key);
72 const hh1 = hh.y1;
73 const hh0 = hh.y0;
74 const hh1r = @bitReverse(u64, hh1);
75 const hh0r = @bitReverse(u64, hh0);
76 const hh2 = hh0 ^ hh1;
77 const hh2r = hh0r ^ hh1r;
78
79 return Ghash{
80 .h0 = h0,
81 .h1 = h1,
82 .h2 = h2,
83 .h0r = h0r,
84 .h1r = h1r,
85 .h2r = h2r,
86
87 .hh0 = hh0,
88 .hh1 = hh1,
89 .hh2 = hh2,
90 .hh0r = hh0r,
91 .hh1r = hh1r,
92 .hh2r = hh2r,
93 };
94 }
95 }
96
97 inline fn clmul_pclmul(x: u64, y: u64) u64 {
98 const Vector = std.meta.Vector;
99 const product = asm (
100 \\ vpclmulqdq $0x00, %[x], %[y], %[out]
101 : [out] "=x" (-> Vector(2, u64))
102 : [x] "x" (@bitCast(Vector(2, u64), @as(u128, x))),
103 [y] "x" (@bitCast(Vector(2, u64), @as(u128, y)))
104 );
105 return product[0];
106 }
107
108 fn clmul_soft(x: u64, y: u64) u64 {
109 const x0 = x & 0x1111111111111111;
110 const x1 = x & 0x2222222222222222;
111 const x2 = x & 0x4444444444444444;
112 const x3 = x & 0x8888888888888888;
113 const y0 = y & 0x1111111111111111;
114 const y1 = y & 0x2222222222222222;
115 const y2 = y & 0x4444444444444444;
116 const y3 = y & 0x8888888888888888;
117 var z0 = (x0 *% y0) ^ (x1 *% y3) ^ (x2 *% y2) ^ (x3 *% y1);
118 var z1 = (x0 *% y1) ^ (x1 *% y0) ^ (x2 *% y3) ^ (x3 *% y2);
119 var z2 = (x0 *% y2) ^ (x1 *% y1) ^ (x2 *% y0) ^ (x3 *% y3);
120 var z3 = (x0 *% y3) ^ (x1 *% y2) ^ (x2 *% y1) ^ (x3 *% y0);
121 z0 &= 0x1111111111111111;
122 z1 &= 0x2222222222222222;
123 z2 &= 0x4444444444444444;
124 z3 &= 0x8888888888888888;
125 return z0 | z1 | z2 | z3;
126 }
127
128 const has_pclmul = comptime std.Target.x86.featureSetHas(std.Target.current.cpu.features, .pclmul);
129 const has_avx = comptime std.Target.x86.featureSetHas(std.Target.current.cpu.features, .avx);
130 const clmul = if (std.Target.current.cpu.arch == .x86_64 and has_pclmul and has_avx) clmul_pclmul else clmul_soft;
131
132 fn blocks(st: *Ghash, msg: []const u8) void {
133 assert(msg.len % 16 == 0); // GHASH blocks() expects full blocks
134 var y1 = st.y1;
135 var y0 = st.y0;
136
137 var i: usize = 0;
138
139 // 2-blocks aggregated reduction
140 if (std.builtin.mode != .ReleaseSmall) {
141 while (i + 32 <= msg.len) : (i += 32) {
142 // B0 * H^2 unreduced
143 y1 ^= mem.readIntBig(u64, msg[i..][0..8]);
144 y0 ^= mem.readIntBig(u64, msg[i..][8..16]);
145
146 const y1r = @bitReverse(u64, y1);
147 const y0r = @bitReverse(u64, y0);
148 const y2 = y0 ^ y1;
149 const y2r = y0r ^ y1r;
150
151 var z0 = clmul(y0, st.hh0);
152 var z1 = clmul(y1, st.hh1);
153 var z2 = clmul(y2, st.hh2) ^ z0 ^ z1;
154 var z0h = clmul(y0r, st.hh0r);
155 var z1h = clmul(y1r, st.hh1r);
156 var z2h = clmul(y2r, st.hh2r) ^ z0h ^ z1h;
157
158 // B1 * H unreduced
159 const sy1 = mem.readIntBig(u64, msg[i..][16..24]);
160 const sy0 = mem.readIntBig(u64, msg[i..][24..32]);
161
162 const sy1r = @bitReverse(u64, sy1);
163 const sy0r = @bitReverse(u64, sy0);
164 const sy2 = sy0 ^ sy1;
165 const sy2r = sy0r ^ sy1r;
166
167 const sz0 = clmul(sy0, st.h0);
168 const sz1 = clmul(sy1, st.h1);
169 const sz2 = clmul(sy2, st.h2) ^ sz0 ^ sz1;
170 const sz0h = clmul(sy0r, st.h0r);
171 const sz1h = clmul(sy1r, st.h1r);
172 const sz2h = clmul(sy2r, st.h2r) ^ sz0h ^ sz1h;
173
174 // ((B0 * H^2) + B1 * H) (mod M)
175 z0 ^= sz0;
176 z1 ^= sz1;
177 z2 ^= sz2;
178 z0h ^= sz0h;
179 z1h ^= sz1h;
180 z2h ^= sz2h;
181 z0h = @bitReverse(u64, z0h) >> 1;
182 z1h = @bitReverse(u64, z1h) >> 1;
183 z2h = @bitReverse(u64, z2h) >> 1;
184
185 var v3 = z1h;
186 var v2 = z1 ^ z2h;
187 var v1 = z0h ^ z2;
188 var v0 = z0;
189
190 v3 = (v3 << 1) | (v2 >> 63);
191 v2 = (v2 << 1) | (v1 >> 63);
192 v1 = (v1 << 1) | (v0 >> 63);
193 v0 = (v0 << 1);
194
195 v2 ^= v0 ^ (v0 >> 1) ^ (v0 >> 2) ^ (v0 >> 7);
196 v1 ^= (v0 << 63) ^ (v0 << 62) ^ (v0 << 57);
197 y1 = v3 ^ v1 ^ (v1 >> 1) ^ (v1 >> 2) ^ (v1 >> 7);
198 y0 = v2 ^ (v1 << 63) ^ (v1 << 62) ^ (v1 << 57);
199 }
200 }
201
202 // single block
203 while (i + 16 <= msg.len) : (i += 16) {
204 y1 ^= mem.readIntBig(u64, msg[i..][0..8]);
205 y0 ^= mem.readIntBig(u64, msg[i..][8..16]);
206
207 const y1r = @bitReverse(u64, y1);
208 const y0r = @bitReverse(u64, y0);
209 const y2 = y0 ^ y1;
210 const y2r = y0r ^ y1r;
211
212 const z0 = clmul(y0, st.h0);
213 const z1 = clmul(y1, st.h1);
214 var z2 = clmul(y2, st.h2) ^ z0 ^ z1;
215 var z0h = clmul(y0r, st.h0r);
216 var z1h = clmul(y1r, st.h1r);
217 var z2h = clmul(y2r, st.h2r) ^ z0h ^ z1h;
218 z0h = @bitReverse(u64, z0h) >> 1;
219 z1h = @bitReverse(u64, z1h) >> 1;
220 z2h = @bitReverse(u64, z2h) >> 1;
221
222 // shift & reduce
223 var v3 = z1h;
224 var v2 = z1 ^ z2h;
225 var v1 = z0h ^ z2;
226 var v0 = z0;
227
228 v3 = (v3 << 1) | (v2 >> 63);
229 v2 = (v2 << 1) | (v1 >> 63);
230 v1 = (v1 << 1) | (v0 >> 63);
231 v0 = (v0 << 1);
232
233 v2 ^= v0 ^ (v0 >> 1) ^ (v0 >> 2) ^ (v0 >> 7);
234 v1 ^= (v0 << 63) ^ (v0 << 62) ^ (v0 << 57);
235 y1 = v3 ^ v1 ^ (v1 >> 1) ^ (v1 >> 2) ^ (v1 >> 7);
236 y0 = v2 ^ (v1 << 63) ^ (v1 << 62) ^ (v1 << 57);
237 }
238 st.y1 = y1;
239 st.y0 = y0;
240 }
241
242 pub fn update(st: *Ghash, m: []const u8) void {
243 var mb = m;
244
245 if (st.leftover > 0) {
246 const want = math.min(block_size - st.leftover, mb.len);
247 const mc = mb[0..want];
248 for (mc) |x, i| {
249 st.buf[st.leftover + i] = x;
250 }
251 mb = mb[want..];
252 st.leftover += want;
253 if (st.leftover > block_size) {
254 return;
255 }
256 st.blocks(&st.buf);
257 st.leftover = 0;
258 }
259 if (mb.len >= block_size) {
260 const want = mb.len & ~(block_size - 1);
261 st.blocks(mb[0..want]);
262 mb = mb[want..];
263 }
264 if (mb.len > 0) {
265 for (mb) |x, i| {
266 st.buf[st.leftover + i] = x;
267 }
268 st.leftover += mb.len;
269 }
270 }
271
272 pub fn final(st: *Ghash, out: *[mac_length]u8) void {
273 if (st.leftover > 0) {
274 var i = st.leftover;
275 while (i < block_size) : (i += 1) {
276 st.buf[i] = 0;
277 }
278 st.blocks(&st.buf);
279 }
280 mem.writeIntBig(u64, out[0..8], st.y1);
281 mem.writeIntBig(u64, out[8..16], st.y0);
282
283 mem.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Ghash)]);
284 }
285
286 pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [minimum_key_length]u8) void {
287 var st = Ghash.init(key);
288 st.update(msg);
289 st.final(out);
290 }
291};
292
293const htest = @import("test.zig");
294
295test "ghash" {
296 const key = [_]u8{0x42} ** 16;
297 const m = [_]u8{0x69} ** 256;
298
299 var st = Ghash.init(&key);
300 st.update(&m);
301 var out: [16]u8 = undefined;
302 st.final(&out);
303 htest.assertEqual("889295fa746e8b174bf4ec80a65dea41", &out);
304
305 st = Ghash.init(&key);
306 st.update(m[0..100]);
307 st.update(m[100..]);
308 st.final(&out);
309 htest.assertEqual("889295fa746e8b174bf4ec80a65dea41", &out);
310}
lib/std/crypto/poly1305.zig+3-8
...@@ -22,8 +22,7 @@ pub const Poly1305 = struct {...@@ -22,8 +22,7 @@ pub const Poly1305 = struct {
22 // partial block buffer22 // partial block buffer
23 buf: [block_size]u8 align(16) = undefined,23 buf: [block_size]u8 align(16) = undefined,
2424
25 pub fn init(key: []const u8) Poly1305 {25 pub fn init(key: *const [minimum_key_length]u8) Poly1305 {
26 std.debug.assert(key.len >= minimum_key_length);
27 const t0 = mem.readIntLittle(u64, key[0..8]);26 const t0 = mem.readIntLittle(u64, key[0..8]);
28 const t1 = mem.readIntLittle(u64, key[8..16]);27 const t1 = mem.readIntLittle(u64, key[8..16]);
29 return Poly1305{28 return Poly1305{
...@@ -115,8 +114,7 @@ pub const Poly1305 = struct {...@@ -115,8 +114,7 @@ pub const Poly1305 = struct {
115 }114 }
116 }115 }
117116
118 pub fn final(st: *Poly1305, out: []u8) void {117 pub fn final(st: *Poly1305, out: *[mac_length]u8) void {
119 std.debug.assert(out.len >= mac_length);
120 if (st.leftover > 0) {118 if (st.leftover > 0) {
121 var i = st.leftover;119 var i = st.leftover;
122 st.buf[i] = 1;120 st.buf[i] = 1;
...@@ -187,10 +185,7 @@ pub const Poly1305 = struct {...@@ -187,10 +185,7 @@ pub const Poly1305 = struct {
187 std.mem.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Poly1305)]);185 std.mem.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Poly1305)]);
188 }186 }
189187
190 pub fn create(out: []u8, msg: []const u8, key: []const u8) void {188 pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [minimum_key_length]u8) void {
191 std.debug.assert(out.len >= mac_length);
192 std.debug.assert(key.len >= minimum_key_length);
193
194 var st = Poly1305.init(key);189 var st = Poly1305.init(key);
195 st.update(msg);190 st.update(msg);
196 st.final(out);191 st.final(out);