master
1const std = @import("std");
2const base64 = std.base64;
3const crypto = std.crypto;
4const debug = std.debug;
5const fmt = std.fmt;
6const math = std.math;
7const mem = std.mem;
8const pwhash = crypto.pwhash;
9const testing = std.testing;
10const HmacSha512 = crypto.auth.hmac.sha2.HmacSha512;
11const Sha512 = crypto.hash.sha2.Sha512;
12
13const phc_format = @import("phc_encoding.zig");
14
15const KdfError = pwhash.KdfError;
16const HasherError = pwhash.HasherError;
17const EncodingError = phc_format.Error;
18const Error = pwhash.Error;
19
20const salt_length: usize = 16;
21const salt_str_length: usize = 22;
22const ct_str_length: usize = 31;
23const ct_length: usize = 24;
24const dk_length: usize = ct_length - 1;
25
26/// Length (in bytes) of a password hash in crypt encoding
27pub const hash_length: usize = 60;
28
29pub const State = struct {
30 sboxes: [4][256]u32 = [4][256]u32{
31 .{
32 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
33 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
34 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
35 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
36 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
37 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
38 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
39 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
40 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
41 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
42 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
43 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
44 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
45 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
46 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
47 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
48 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
49 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
50 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
51 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
52 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
53 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
54 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
55 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
56 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
57 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
58 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
59 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
60 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
61 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
62 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
63 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
64 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
65 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
66 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
67 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
68 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
69 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
70 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
71 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
72 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
73 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
74 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
75 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
76 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
77 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
78 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
79 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
80 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
81 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
82 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
83 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
84 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
85 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
86 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
87 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
88 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
89 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
90 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
91 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
92 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
93 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
94 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
95 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
96 },
97 .{
98 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
99 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
100 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
101 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
102 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
103 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
104 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
105 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
106 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
107 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
108 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
109 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
110 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
111 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
112 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
113 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
114 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
115 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
116 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
117 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
118 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
119 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
120 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
121 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
122 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
123 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
124 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
125 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
126 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
127 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
128 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
129 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
130 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
131 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
132 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
133 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
134 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
135 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
136 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
137 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
138 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
139 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
140 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
141 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
142 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
143 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
144 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
145 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
146 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
147 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
148 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
149 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
150 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
151 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
152 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
153 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
154 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
155 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
156 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
157 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
158 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
159 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
160 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
161 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
162 },
163 .{
164 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
165 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
166 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
167 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
168 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
169 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
170 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
171 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
172 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
173 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
174 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
175 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
176 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
177 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
178 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
179 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
180 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
181 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
182 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
183 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
184 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
185 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
186 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
187 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
188 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
189 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
190 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
191 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
192 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
193 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
194 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
195 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
196 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
197 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
198 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
199 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
200 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
201 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
202 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
203 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
204 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
205 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
206 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
207 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
208 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
209 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
210 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
211 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
212 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
213 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
214 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
215 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
216 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
217 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
218 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
219 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
220 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
221 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
222 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
223 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
224 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
225 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
226 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
227 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
228 },
229 .{
230 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
231 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
232 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
233 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
234 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
235 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
236 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
237 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
238 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
239 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
240 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
241 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
242 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
243 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
244 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
245 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
246 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
247 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
248 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
249 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
250 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
251 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
252 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
253 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
254 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
255 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
256 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
257 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
258 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
259 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
260 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
261 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
262 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
263 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
264 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
265 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
266 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
267 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
268 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
269 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
270 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
271 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
272 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
273 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
274 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
275 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
276 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
277 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
278 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
279 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
280 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
281 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
282 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
283 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
284 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
285 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
286 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
287 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
288 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
289 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
290 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
291 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
292 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
293 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,
294 },
295 },
296 subkeys: [18]u32 = [18]u32{
297 0x243f6a88, 0x85a308d3, 0x13198a2e,
298 0x03707344, 0xa4093822, 0x299f31d0,
299 0x082efa98, 0xec4e6c89, 0x452821e6,
300 0x38d01377, 0xbe5466cf, 0x34e90c6c,
301 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5,
302 0xb5470917, 0x9216d5d9, 0x8979fb1b,
303 },
304
305 fn toWord(data: []const u8, current: *usize) u32 {
306 var t: u32 = 0;
307 var j = current.*;
308 var i: usize = 0;
309 while (i < 4) : (i += 1) {
310 if (j >= data.len) j = 0;
311 t = (t << 8) | data[j];
312 j += 1;
313 }
314 current.* = j;
315 return t;
316 }
317
318 fn expand0(state: *State, key: []const u8) void {
319 var i: usize = 0;
320 var j: usize = 0;
321 while (i < state.subkeys.len) : (i += 1) {
322 state.subkeys[i] ^= toWord(key, &j);
323 }
324
325 var halves = Halves{ .l = 0, .r = 0 };
326 i = 0;
327 while (i < 18) : (i += 2) {
328 state.encipher(&halves);
329 state.subkeys[i] = halves.l;
330 state.subkeys[i + 1] = halves.r;
331 }
332
333 i = 0;
334 while (i < 4) : (i += 1) {
335 var k: usize = 0;
336 while (k < 256) : (k += 2) {
337 state.encipher(&halves);
338 state.sboxes[i][k] = halves.l;
339 state.sboxes[i][k + 1] = halves.r;
340 }
341 }
342 }
343
344 fn expand(state: *State, data: []const u8, key: []const u8) void {
345 var i: usize = 0;
346 var j: usize = 0;
347 while (i < state.subkeys.len) : (i += 1) {
348 state.subkeys[i] ^= toWord(key, &j);
349 }
350
351 var halves = Halves{ .l = 0, .r = 0 };
352 i = 0;
353 j = 0;
354 while (i < 18) : (i += 2) {
355 halves.l ^= toWord(data, &j);
356 halves.r ^= toWord(data, &j);
357 state.encipher(&halves);
358 state.subkeys[i] = halves.l;
359 state.subkeys[i + 1] = halves.r;
360 }
361
362 i = 0;
363 while (i < 4) : (i += 1) {
364 var k: usize = 0;
365 while (k < 256) : (k += 2) {
366 halves.l ^= toWord(data, &j);
367 halves.r ^= toWord(data, &j);
368 state.encipher(&halves);
369 state.sboxes[i][k] = halves.l;
370 state.sboxes[i][k + 1] = halves.r;
371 }
372 }
373 }
374
375 const Halves = struct { l: u32, r: u32 };
376
377 fn halfRound(state: *const State, i: u32, j: u32, n: usize) u32 {
378 var r = state.sboxes[0][@as(u8, @truncate(j >> 24))];
379 r +%= state.sboxes[1][@as(u8, @truncate(j >> 16))];
380 r ^= state.sboxes[2][@as(u8, @truncate(j >> 8))];
381 r +%= state.sboxes[3][@as(u8, @truncate(j))];
382 return i ^ r ^ state.subkeys[n];
383 }
384
385 fn encipher(state: *const State, halves: *Halves) void {
386 halves.l ^= state.subkeys[0];
387 comptime var i = 1;
388 inline while (i < 16) : (i += 2) {
389 halves.r = state.halfRound(halves.r, halves.l, i);
390 halves.l = state.halfRound(halves.l, halves.r, i + 1);
391 }
392 const halves_last = Halves{ .l = halves.r ^ state.subkeys[i], .r = halves.l };
393 halves.* = halves_last;
394 }
395
396 fn encrypt(state: *const State, data: []u32) void {
397 debug.assert(data.len % 2 == 0);
398 var i: usize = 0;
399 while (i < data.len) : (i += 2) {
400 var halves = Halves{ .l = data[i], .r = data[i + 1] };
401 state.encipher(&halves);
402 data[i] = halves.l;
403 data[i + 1] = halves.r;
404 }
405 }
406};
407
408/// bcrypt parameters
409pub const Params = struct {
410 const Self = @This();
411
412 /// log2 of the number of rounds
413 rounds_log: u6,
414
415 /// As originally defined, bcrypt silently truncates passwords to 72 bytes.
416 /// In order to overcome this limitation, if `silently_truncate_password` is set to `false`,
417 /// long passwords will be automatically pre-hashed using HMAC-SHA512 before being passed to bcrypt.
418 /// Only set `silently_truncate_password` to `true` for compatibility with traditional bcrypt implementations,
419 /// or if you want to handle the truncation yourself.
420 silently_truncate_password: bool,
421
422 /// Minimum recommended parameters according to the
423 /// [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
424 pub const owasp = Self{ .rounds_log = 10, .silently_truncate_password = false };
425};
426
427fn bcryptWithTruncation(
428 password: []const u8,
429 salt: [salt_length]u8,
430 params: Params,
431) [dk_length]u8 {
432 var state = State{};
433 var password_buf: [73]u8 = undefined;
434 const trimmed_len = @min(password.len, password_buf.len - 1);
435 @memcpy(password_buf[0..trimmed_len], password[0..trimmed_len]);
436 password_buf[trimmed_len] = 0;
437 const passwordZ = password_buf[0 .. trimmed_len + 1];
438 state.expand(salt[0..], passwordZ);
439
440 const rounds: u64 = @as(u64, 1) << params.rounds_log;
441 var k: u64 = 0;
442 while (k < rounds) : (k += 1) {
443 state.expand0(passwordZ);
444 state.expand0(salt[0..]);
445 }
446 crypto.secureZero(u8, &password_buf);
447
448 var cdata = [6]u32{ 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 }; // "OrpheanBeholderScryDoubt"
449 k = 0;
450 while (k < 64) : (k += 1) {
451 state.encrypt(&cdata);
452 }
453
454 var ct: [ct_length]u8 = undefined;
455 for (cdata, 0..) |c, i| {
456 mem.writeInt(u32, ct[i * 4 ..][0..4], c, .big);
457 }
458 return ct[0..dk_length].*;
459}
460
461/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
462/// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches.
463///
464/// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output.
465///
466/// This function was designed for password storage, not for key derivation.
467/// For key derivation, use `bcrypt.pbkdf()` or `bcrypt.opensshKdf()` instead.
468pub fn bcrypt(
469 password: []const u8,
470 salt: [salt_length]u8,
471 params: Params,
472) [dk_length]u8 {
473 if (password.len <= 72 or params.silently_truncate_password) {
474 return bcryptWithTruncation(password, salt, params);
475 }
476
477 var pre_hash: [HmacSha512.mac_length]u8 = undefined;
478 HmacSha512.create(&pre_hash, password, &salt);
479
480 const Encoder = crypt_format.Codec.Encoder;
481 var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined;
482 _ = Encoder.encode(&pre_hash_b64, &pre_hash);
483
484 return bcryptWithTruncation(&pre_hash_b64, salt, params);
485}
486
487const pbkdf_prf = struct {
488 const Self = @This();
489 pub const mac_length = 32;
490
491 hasher: Sha512,
492 sha2pass: [Sha512.digest_length]u8,
493
494 pub fn create(out: *[mac_length]u8, msg: []const u8, key: []const u8) void {
495 var ctx = Self.init(key);
496 ctx.update(msg);
497 ctx.final(out);
498 }
499
500 pub fn init(key: []const u8) Self {
501 var self: Self = undefined;
502 self.hasher = Sha512.init(.{});
503 Sha512.hash(key, &self.sha2pass, .{});
504 return self;
505 }
506
507 pub fn update(self: *Self, msg: []const u8) void {
508 self.hasher.update(msg);
509 }
510
511 pub fn final(self: *Self, out: *[mac_length]u8) void {
512 var sha2salt: [Sha512.digest_length]u8 = undefined;
513 self.hasher.final(&sha2salt);
514 out.* = hash(self.sha2pass, sha2salt);
515 }
516
517 /// Matches OpenBSD function
518 /// https://github.com/openbsd/src/blob/6df1256b7792691e66c2ed9d86a8c103069f9e34/lib/libutil/bcrypt_pbkdf.c#L98
519 pub fn hash(sha2pass: [Sha512.digest_length]u8, sha2salt: [Sha512.digest_length]u8) [32]u8 {
520 var cdata: [8]u32 = undefined;
521 {
522 const ciphertext = "OxychromaticBlowfishSwatDynamite";
523 var j: usize = 0;
524 for (&cdata) |*v| {
525 v.* = State.toWord(ciphertext, &j);
526 }
527 }
528
529 var state = State{};
530
531 { // key expansion
532 state.expand(&sha2salt, &sha2pass);
533 var i: usize = 0;
534 while (i < 64) : (i += 1) {
535 state.expand0(&sha2salt);
536 state.expand0(&sha2pass);
537 }
538 }
539
540 { // encryption
541 var i: usize = 0;
542 while (i < 64) : (i += 1) {
543 state.encrypt(&cdata);
544 }
545 }
546
547 // copy out
548 var out: [32]u8 = undefined;
549 for (cdata, 0..) |v, i| {
550 std.mem.writeInt(u32, out[4 * i ..][0..4], v, .little);
551 }
552
553 // zap
554 crypto.secureZero(u32, &cdata);
555 crypto.secureZero(u32, &state.subkeys);
556
557 return out;
558 }
559};
560
561/// bcrypt-pbkdf is a key derivation function based on bcrypt.
562///
563/// Unlike the password hashing function `bcrypt`, this function doesn't silently truncate passwords longer than 72 bytes.
564pub fn pbkdf(pass: []const u8, salt: []const u8, key: []u8, rounds: u32) !void {
565 try crypto.pwhash.pbkdf2(key, pass, salt, rounds, pbkdf_prf);
566}
567
568/// The function used in OpenSSH to derive encryption keys from passphrases.
569///
570/// This implementation is compatible with the OpenBSD implementation (https://github.com/openbsd/src/blob/master/lib/libutil/bcrypt_pbkdf.c).
571pub fn opensshKdf(pass: []const u8, salt: []const u8, key: []u8, rounds: u32) !void {
572 var tmp: [32]u8 = undefined;
573 var tmp2: [32]u8 = undefined;
574 if (rounds < 1 or pass.len == 0 or salt.len == 0 or key.len == 0 or key.len > tmp.len * tmp.len) {
575 return error.InvalidInput;
576 }
577 var sha2pass: [Sha512.digest_length]u8 = undefined;
578 Sha512.hash(pass, &sha2pass, .{});
579 const stride = (key.len + tmp.len - 1) / tmp.len;
580 var amt = (key.len + stride - 1) / stride;
581 if (math.shr(usize, key.len, 32) >= amt) {
582 return error.InvalidInput;
583 }
584 var key_remainder = key.len;
585 var count: u32 = 1;
586 while (key_remainder > 0) : (count += 1) {
587 var count_salt: [4]u8 = undefined;
588 std.mem.writeInt(u32, count_salt[0..], count, .big);
589 var sha2salt: [Sha512.digest_length]u8 = undefined;
590 var h = Sha512.init(.{});
591 h.update(salt);
592 h.update(&count_salt);
593 h.final(&sha2salt);
594 tmp2 = pbkdf_prf.hash(sha2pass, sha2salt);
595 tmp = tmp2;
596 for (1..rounds) |_| {
597 Sha512.hash(&tmp2, &sha2salt, .{});
598 tmp2 = pbkdf_prf.hash(sha2pass, sha2salt);
599 for (&tmp, tmp2) |*o, t| o.* ^= t;
600 }
601 amt = @min(amt, key_remainder);
602 key_remainder -= for (0..amt) |i| {
603 const dest = i * stride + (count - 1);
604 if (dest >= key.len) break i;
605 key[dest] = tmp[i];
606 } else amt;
607 }
608 crypto.secureZero(u8, &tmp);
609 crypto.secureZero(u8, &tmp2);
610 crypto.secureZero(u8, &sha2pass);
611}
612
613const crypt_format = struct {
614 /// String prefix for bcrypt
615 pub const prefix = "$2";
616
617 // bcrypt has its own variant of base64, with its own alphabet and no padding
618 const bcrypt_alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".*;
619 const Codec = struct { Encoder: base64.Base64Encoder, Decoder: base64.Base64Decoder }{
620 .Encoder = base64.Base64Encoder.init(bcrypt_alphabet, null),
621 .Decoder = base64.Base64Decoder.init(bcrypt_alphabet, null),
622 };
623
624 fn strHashInternal(
625 password: []const u8,
626 salt: [salt_length]u8,
627 params: Params,
628 ) [hash_length]u8 {
629 var dk = bcrypt(password, salt, params);
630
631 var salt_str: [salt_str_length]u8 = undefined;
632 _ = Codec.Encoder.encode(salt_str[0..], salt[0..]);
633
634 var ct_str: [ct_str_length]u8 = undefined;
635 _ = Codec.Encoder.encode(ct_str[0..], dk[0..]);
636
637 var s_buf: [hash_length]u8 = undefined;
638 const s = fmt.bufPrint(
639 s_buf[0..],
640 "{s}b${d}{d}${s}{s}",
641 .{ prefix, params.rounds_log / 10, params.rounds_log % 10, salt_str, ct_str },
642 ) catch unreachable;
643 debug.assert(s.len == s_buf.len);
644 return s_buf;
645 }
646};
647
648/// Hash and verify passwords using the PHC format.
649const PhcFormatHasher = struct {
650 const alg_id = "bcrypt";
651 const BinValue = phc_format.BinValue;
652
653 const HashResult = struct {
654 alg_id: []const u8,
655 r: u6,
656 salt: BinValue(salt_length),
657 hash: BinValue(dk_length),
658 };
659
660 /// Return a non-deterministic hash of the password encoded as a PHC-format string
661 fn create(
662 password: []const u8,
663 params: Params,
664 buf: []u8,
665 ) HasherError![]const u8 {
666 var salt: [salt_length]u8 = undefined;
667 crypto.random.bytes(&salt);
668
669 const hash = bcrypt(password, salt, params);
670
671 return phc_format.serialize(HashResult{
672 .alg_id = alg_id,
673 .r = params.rounds_log,
674 .salt = try BinValue(salt_length).fromSlice(&salt),
675 .hash = try BinValue(dk_length).fromSlice(&hash),
676 }, buf);
677 }
678
679 /// Verify a password against a PHC-format encoded string
680 fn verify(
681 str: []const u8,
682 password: []const u8,
683 silently_truncate_password: bool,
684 ) HasherError!void {
685 const hash_result = try phc_format.deserialize(HashResult, str);
686
687 if (!mem.eql(u8, hash_result.alg_id, alg_id)) return HasherError.PasswordVerificationFailed;
688 if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length)
689 return HasherError.InvalidEncoding;
690
691 const params = Params{
692 .rounds_log = hash_result.r,
693 .silently_truncate_password = silently_truncate_password,
694 };
695 const hash = bcrypt(password, hash_result.salt.buf, params);
696 const expected_hash = hash_result.hash.constSlice();
697
698 if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed;
699 }
700};
701
702/// Hash and verify passwords using the modular crypt format.
703const CryptFormatHasher = struct {
704 /// Length of a string returned by the create() function
705 const pwhash_str_length: usize = hash_length;
706
707 /// Return a non-deterministic hash of the password encoded into the modular crypt format
708 fn create(
709 password: []const u8,
710 params: Params,
711 buf: []u8,
712 ) HasherError![]const u8 {
713 if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft;
714
715 var salt: [salt_length]u8 = undefined;
716 crypto.random.bytes(&salt);
717
718 const hash = crypt_format.strHashInternal(password, salt, params);
719 @memcpy(buf[0..hash.len], &hash);
720
721 return buf[0..pwhash_str_length];
722 }
723
724 /// Verify a password against a string in modular crypt format
725 fn verify(
726 str: []const u8,
727 password: []const u8,
728 silently_truncate_password: bool,
729 ) HasherError!void {
730 if (str.len != pwhash_str_length or str[3] != '$' or str[6] != '$')
731 return HasherError.InvalidEncoding;
732
733 const rounds_log_str = str[4..][0..2];
734 const rounds_log = fmt.parseInt(u6, rounds_log_str[0..], 10) catch
735 return HasherError.InvalidEncoding;
736
737 const salt_str = str[7..][0..salt_str_length];
738 var salt: [salt_length]u8 = undefined;
739 crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding;
740
741 const wanted_s = crypt_format.strHashInternal(password, salt, .{
742 .rounds_log = rounds_log,
743 .silently_truncate_password = silently_truncate_password,
744 });
745 if (!mem.eql(u8, wanted_s[0..], str[0..])) return HasherError.PasswordVerificationFailed;
746 }
747};
748
749/// Options for hashing a password.
750pub const HashOptions = struct {
751 /// For `bcrypt`, that can be left to `null`.
752 allocator: ?mem.Allocator = null,
753 /// Internal bcrypt parameters.
754 params: Params,
755 /// Encoding to use for the output of the hash function.
756 encoding: pwhash.Encoding,
757};
758
759/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
760/// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches.
761///
762/// The function returns a string that includes all the parameters required for verification.
763///
764/// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes.
765/// If this is an issue for your application, set the `silently_truncate_password` option to `false`.
766pub fn strHash(
767 password: []const u8,
768 options: HashOptions,
769 out: []u8,
770) Error![]const u8 {
771 switch (options.encoding) {
772 .phc => return PhcFormatHasher.create(password, options.params, out),
773 .crypt => return CryptFormatHasher.create(password, options.params, out),
774 }
775}
776
777/// Options for hash verification.
778pub const VerifyOptions = struct {
779 /// For `bcrypt`, that can be left to `null`.
780 allocator: ?mem.Allocator = null,
781 /// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer.
782 silently_truncate_password: bool,
783};
784
785/// Verify that a previously computed hash is valid for a given password.
786pub fn strVerify(
787 str: []const u8,
788 password: []const u8,
789 options: VerifyOptions,
790) Error!void {
791 if (mem.startsWith(u8, str, crypt_format.prefix)) {
792 return CryptFormatHasher.verify(str, password, options.silently_truncate_password);
793 } else {
794 return PhcFormatHasher.verify(str, password, options.silently_truncate_password);
795 }
796}
797
798test "bcrypt codec" {
799 var salt: [salt_length]u8 = undefined;
800 crypto.random.bytes(&salt);
801 var salt_str: [salt_str_length]u8 = undefined;
802 _ = crypt_format.Codec.Encoder.encode(salt_str[0..], salt[0..]);
803 var salt2: [salt_length]u8 = undefined;
804 try crypt_format.Codec.Decoder.decode(salt2[0..], salt_str[0..]);
805 try testing.expectEqualSlices(u8, salt[0..], salt2[0..]);
806}
807
808test "bcrypt crypt format" {
809 var hash_options = HashOptions{
810 .params = .{ .rounds_log = 5, .silently_truncate_password = false },
811 .encoding = .crypt,
812 };
813 var verify_options = VerifyOptions{ .silently_truncate_password = false };
814
815 var buf: [hash_length]u8 = undefined;
816 const s = try strHash("password", hash_options, &buf);
817
818 try testing.expect(mem.startsWith(u8, s, crypt_format.prefix));
819 try strVerify(s, "password", verify_options);
820 try testing.expectError(
821 error.PasswordVerificationFailed,
822 strVerify(s, "invalid password", verify_options),
823 );
824
825 var long_buf: [hash_length]u8 = undefined;
826 var long_s = try strHash("password" ** 100, hash_options, &long_buf);
827
828 try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix));
829 try strVerify(long_s, "password" ** 100, verify_options);
830 try testing.expectError(
831 error.PasswordVerificationFailed,
832 strVerify(long_s, "password" ** 101, verify_options),
833 );
834
835 hash_options.params.silently_truncate_password = true;
836 verify_options.silently_truncate_password = true;
837 long_s = try strHash("password" ** 100, hash_options, &long_buf);
838 try strVerify(long_s, "password" ** 101, verify_options);
839
840 try strVerify(
841 "$2b$08$WUQKyBCaKpziCwUXHiMVvu40dYVjkTxtWJlftl0PpjY2BxWSvFIEe",
842 "The devil himself",
843 verify_options,
844 );
845}
846
847test "bcrypt phc format" {
848 var hash_options = HashOptions{
849 .params = .{ .rounds_log = 5, .silently_truncate_password = false },
850 .encoding = .phc,
851 };
852 var verify_options = VerifyOptions{ .silently_truncate_password = false };
853 const prefix = "$bcrypt$";
854
855 var buf: [hash_length * 2]u8 = undefined;
856 const s = try strHash("password", hash_options, &buf);
857
858 try testing.expect(mem.startsWith(u8, s, prefix));
859 try strVerify(s, "password", verify_options);
860 try testing.expectError(
861 error.PasswordVerificationFailed,
862 strVerify(s, "invalid password", verify_options),
863 );
864
865 var long_buf: [hash_length * 2]u8 = undefined;
866 var long_s = try strHash("password" ** 100, hash_options, &long_buf);
867
868 try testing.expect(mem.startsWith(u8, long_s, prefix));
869 try strVerify(long_s, "password" ** 100, verify_options);
870 try testing.expectError(
871 error.PasswordVerificationFailed,
872 strVerify(long_s, "password" ** 101, verify_options),
873 );
874
875 hash_options.params.silently_truncate_password = true;
876 verify_options.silently_truncate_password = true;
877 long_s = try strHash("password" ** 100, hash_options, &long_buf);
878 try strVerify(long_s, "password" ** 101, verify_options);
879
880 try strVerify(
881 "$bcrypt$r=5$2NopntlgE2lX3cTwr4qz8A$r3T7iKYQNnY4hAhGjk9RmuyvgrYJZwc",
882 "The devil himself",
883 verify_options,
884 );
885}
886
887test "openssh kdf" {
888 var key: [100]u8 = undefined;
889 const pass = "password";
890 const salt = "salt";
891 const rounds = 5;
892 try opensshKdf(pass, salt, &key, rounds);
893 const expected = [_]u8{ 65, 207, 68, 58, 55, 252, 114, 141, 255, 65, 216, 175, 5, 92, 235, 68, 220, 92, 118, 161, 40, 13, 241, 190, 56, 152, 69, 136, 41, 214, 51, 205, 37, 221, 101, 59, 105, 73, 133, 36, 14, 59, 94, 212, 111, 107, 109, 237, 213, 235, 246, 119, 59, 76, 45, 130, 142, 81, 178, 231, 161, 158, 138, 108, 18, 162, 26, 50, 218, 251, 23, 66, 2, 232, 20, 202, 216, 46, 12, 250, 247, 246, 252, 23, 155, 74, 77, 195, 120, 113, 57, 88, 126, 81, 9, 249, 72, 18, 208, 160 };
894 try testing.expectEqualSlices(u8, &key, &expected);
895}