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
20pub const 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: *const [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, 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);
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: *const [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: *const [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, salt);
633
634 var ct_str: [ct_str_length]u8 = undefined;
635 _ = Codec.Encoder.encode(&ct_str, dk[0..]);
636
637 var s_buf: [hash_length]u8 = undefined;
638 const s = mem.print(
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 io: std.Io,
666 ) HasherError![]const u8 {
667 var salt: [salt_length]u8 = undefined;
668 io.random(&salt);
669 return createWithSalt(password, params, buf, salt);
670 }
671
672 /// Return a deterministic hash of the password encoded as a PHC-format string.
673 /// Uses the provided salt instead of generating one randomly.
674 fn createWithSalt(
675 password: []const u8,
676 params: Params,
677 buf: []u8,
678 salt: [salt_length]u8,
679 ) HasherError![]const u8 {
680 const hash = bcrypt(password, &salt, params);
681
682 return phc_format.serialize(HashResult{
683 .alg_id = alg_id,
684 .r = params.rounds_log,
685 .salt = try BinValue(salt_length).fromSlice(&salt),
686 .hash = try BinValue(dk_length).fromSlice(&hash),
687 }, buf);
688 }
689
690 /// Verify a password against a PHC-format encoded string
691 fn verify(
692 str: []const u8,
693 password: []const u8,
694 silently_truncate_password: bool,
695 ) HasherError!void {
696 const hash_result = try phc_format.deserialize(HashResult, str);
697
698 if (!mem.eql(u8, hash_result.alg_id, alg_id)) return HasherError.PasswordVerificationFailed;
699 if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length)
700 return HasherError.InvalidEncoding;
701
702 const params: Params = .{
703 .rounds_log = hash_result.r,
704 .silently_truncate_password = silently_truncate_password,
705 };
706 const hash = bcrypt(password, &hash_result.salt.buf, params);
707 const expected_hash = hash_result.hash.constSlice();
708
709 if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed;
710 }
711};
712
713/// Hash and verify passwords using the modular crypt format.
714const CryptFormatHasher = struct {
715 /// Length of a string returned by the create() function
716 const pwhash_str_length: usize = hash_length;
717
718 /// Return a non-deterministic hash of the password encoded into the modular crypt format
719 fn create(
720 password: []const u8,
721 params: Params,
722 buf: []u8,
723 io: std.Io,
724 ) HasherError![]const u8 {
725 var salt: [salt_length]u8 = undefined;
726 io.random(&salt);
727 return createWithSalt(password, params, buf, &salt);
728 }
729
730 /// Return a deterministic hash of the password encoded into the modular crypt format.
731 /// Uses the provided salt instead of generating one randomly.
732 fn createWithSalt(
733 password: []const u8,
734 params: Params,
735 buf: []u8,
736 salt: *const [salt_length]u8,
737 ) HasherError![]const u8 {
738 if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft;
739
740 const hash = crypt_format.strHashInternal(password, salt, params);
741 @memcpy(buf[0..hash.len], &hash);
742
743 return buf[0..pwhash_str_length];
744 }
745
746 /// Verify a password against a string in modular crypt format
747 fn verify(
748 str: []const u8,
749 password: []const u8,
750 silently_truncate_password: bool,
751 ) HasherError!void {
752 if (str.len != pwhash_str_length or str[3] != '$' or str[6] != '$')
753 return HasherError.InvalidEncoding;
754
755 const rounds_log_str = str[4..][0..2];
756 const rounds_log = fmt.parseInt(u6, rounds_log_str[0..], 10) catch
757 return HasherError.InvalidEncoding;
758
759 const salt_str = str[7..][0..salt_str_length];
760 var salt: [salt_length]u8 = undefined;
761 crypt_format.Codec.Decoder.decode(&salt, salt_str) catch return HasherError.InvalidEncoding;
762
763 const wanted_s = crypt_format.strHashInternal(password, &salt, .{
764 .rounds_log = rounds_log,
765 .silently_truncate_password = silently_truncate_password,
766 });
767 if (!mem.eql(u8, wanted_s[0..], str[0..])) return HasherError.PasswordVerificationFailed;
768 }
769};
770
771/// Options for hashing a password.
772pub const HashOptions = struct {
773 /// For `bcrypt`, that can be left to `null`.
774 allocator: ?mem.Allocator = null,
775 /// Internal bcrypt parameters.
776 params: Params,
777 /// Encoding to use for the output of the hash function.
778 encoding: pwhash.Encoding,
779};
780
781/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key
782/// stretching function.
783///
784/// bcrypt is a computationally expensive and cache-hard function, explicitly
785/// designed to slow down exhaustive searches.
786///
787/// The function returns a string that includes all the parameters required for
788/// verification.
789///
790/// By design, bcrypt silently truncates passwords to 72 bytes. If this is an
791/// issue for your application, set the `silently_truncate_password` option to
792/// `false`.
793pub fn strHash(
794 password: []const u8,
795 options: HashOptions,
796 out: []u8,
797 io: std.Io,
798) Error![]const u8 {
799 switch (options.encoding) {
800 .phc => return PhcFormatHasher.create(password, options.params, out, io),
801 .crypt => return CryptFormatHasher.create(password, options.params, out, io),
802 }
803}
804
805/// Compute a deterministic hash of a password using the bcrypt key derivation function.
806/// The function returns a string that includes all the parameters required for verification.
807/// Uses the provided salt instead of generating one randomly.
808pub fn strHashWithSalt(
809 password: []const u8,
810 options: HashOptions,
811 out: []u8,
812 salt: [salt_length]u8,
813) Error![]const u8 {
814 switch (options.encoding) {
815 .phc => return PhcFormatHasher.createWithSalt(password, options.params, out, salt),
816 .crypt => return CryptFormatHasher.createWithSalt(password, options.params, out, &salt),
817 }
818}
819
820/// Options for hash verification.
821pub const VerifyOptions = struct {
822 /// For `bcrypt`, that can be left to `null`.
823 allocator: ?mem.Allocator = null,
824 /// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer.
825 silently_truncate_password: bool,
826};
827
828/// Verify that a previously computed hash is valid for a given password.
829pub fn strVerify(
830 str: []const u8,
831 password: []const u8,
832 options: VerifyOptions,
833) Error!void {
834 if (mem.startsWith(u8, str, crypt_format.prefix)) {
835 return CryptFormatHasher.verify(str, password, options.silently_truncate_password);
836 } else {
837 return PhcFormatHasher.verify(str, password, options.silently_truncate_password);
838 }
839}
840
841test "bcrypt codec" {
842 const io = testing.io;
843 var salt: [salt_length]u8 = undefined;
844 io.random(&salt);
845 var salt_str: [salt_str_length]u8 = undefined;
846 _ = crypt_format.Codec.Encoder.encode(salt_str[0..], salt[0..]);
847 var salt2: [salt_length]u8 = undefined;
848 try crypt_format.Codec.Decoder.decode(salt2[0..], salt_str[0..]);
849 try testing.expectEqualSlices(u8, salt[0..], salt2[0..]);
850}
851
852test "bcrypt crypt format" {
853 const io = testing.io;
854
855 var hash_options: HashOptions = .{
856 .params = .{ .rounds_log = 5, .silently_truncate_password = false },
857 .encoding = .crypt,
858 };
859 var verify_options: VerifyOptions = .{ .silently_truncate_password = false };
860
861 var buf: [hash_length]u8 = undefined;
862 const s = try strHash("password", hash_options, &buf, io);
863
864 try testing.expect(mem.startsWith(u8, s, crypt_format.prefix));
865 try strVerify(s, "password", verify_options);
866 try testing.expectError(
867 error.PasswordVerificationFailed,
868 strVerify(s, "invalid password", verify_options),
869 );
870
871 const password_100: []const u8 = password: {
872 const arr: [100][8]u8 = @splat("password".*);
873 break :password @ptrCast(&arr);
874 };
875
876 var long_buf: [hash_length]u8 = undefined;
877 var long_s = try strHash(password_100, hash_options, &long_buf, io);
878
879 try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix));
880 try strVerify(long_s, password_100, verify_options);
881 try testing.expectError(
882 error.PasswordVerificationFailed,
883 strVerify(long_s, password_100 ++ "password", verify_options),
884 );
885
886 hash_options.params.silently_truncate_password = true;
887 verify_options.silently_truncate_password = true;
888 long_s = try strHash(password_100, hash_options, &long_buf, io);
889 try strVerify(long_s, password_100 ++ "password", verify_options);
890
891 try strVerify(
892 "$2b$08$WUQKyBCaKpziCwUXHiMVvu40dYVjkTxtWJlftl0PpjY2BxWSvFIEe",
893 "The devil himself",
894 verify_options,
895 );
896}
897
898test "bcrypt phc format" {
899 const io = testing.io;
900 var hash_options: HashOptions = .{
901 .params = .{ .rounds_log = 5, .silently_truncate_password = false },
902 .encoding = .phc,
903 };
904 var verify_options: VerifyOptions = .{ .silently_truncate_password = false };
905 const prefix = "$bcrypt$";
906
907 var buf: [hash_length * 2]u8 = undefined;
908 const s = try strHash("password", hash_options, &buf, io);
909
910 try testing.expect(mem.startsWith(u8, s, prefix));
911 try strVerify(s, "password", verify_options);
912 try testing.expectError(
913 error.PasswordVerificationFailed,
914 strVerify(s, "invalid password", verify_options),
915 );
916
917 const password_100: []const u8 = password: {
918 const arr: [100][8]u8 = @splat("password".*);
919 break :password @ptrCast(&arr);
920 };
921
922 var long_buf: [hash_length * 2]u8 = undefined;
923 var long_s = try strHash(password_100, hash_options, &long_buf, io);
924
925 try testing.expect(mem.startsWith(u8, long_s, prefix));
926 try strVerify(long_s, password_100, verify_options);
927 try testing.expectError(
928 error.PasswordVerificationFailed,
929 strVerify(long_s, password_100 ++ "password", verify_options),
930 );
931
932 hash_options.params.silently_truncate_password = true;
933 verify_options.silently_truncate_password = true;
934 long_s = try strHash(password_100, hash_options, &long_buf, io);
935 try strVerify(long_s, password_100 ++ "password", verify_options);
936
937 try strVerify(
938 "$bcrypt$r=5$2NopntlgE2lX3cTwr4qz8A$r3T7iKYQNnY4hAhGjk9RmuyvgrYJZwc",
939 "The devil himself",
940 verify_options,
941 );
942}
943
944test "strHashWithSalt deterministic" {
945 const password = "testpass";
946 const salt: [salt_length]u8 = "0123456789abcdef".*;
947 const params: Params = .{ .rounds_log = 5, .silently_truncate_password = false };
948
949 var buf1: [hash_length * 2]u8 = undefined;
950 var buf2: [hash_length * 2]u8 = undefined;
951
952 const str1 = try strHashWithSalt(password, .{ .params = params, .encoding = .phc }, &buf1, salt);
953 const str2 = try strHashWithSalt(password, .{ .params = params, .encoding = .phc }, &buf2, salt);
954 try testing.expectEqualStrings(str1, str2);
955 try strVerify(str1, password, .{ .silently_truncate_password = false });
956
957 const str3 = try strHashWithSalt(password, .{ .params = params, .encoding = .crypt }, &buf1, salt);
958 const str4 = try strHashWithSalt(password, .{ .params = params, .encoding = .crypt }, &buf2, salt);
959 try testing.expectEqualStrings(str3, str4);
960 try strVerify(str3, password, .{ .silently_truncate_password = false });
961}
962
963test "openssh kdf" {
964 var key: [100]u8 = undefined;
965 const pass = "password";
966 const salt = "salt";
967 const rounds = 5;
968 try opensshKdf(pass, salt, &key, rounds);
969 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 };
970 try testing.expectEqualSlices(u8, &key, &expected);
971}