1#![forbid(unsafe_code)]
2
3extern crate alloc;
18
19use alloc::string::String;
20use alloc::vec::Vec;
21use subtle::ConstantTimeEq;
22
23use oxicrypto_core::{CryptoError, PasswordHash as PasswordHashTrait, PasswordHashParams};
24
25#[rustfmt::skip]
30const P_INIT: [u32; 18] = [
31 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
32 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
33 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
34 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
35 0x9216d5d9, 0x8979fb1b,
36];
37
38#[rustfmt::skip]
39const S0_INIT: [u32; 256] = [
40 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
41 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
42 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
43 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
44 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
45 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
46 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
47 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
48 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
49 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
50 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
51 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
52 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
53 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
54 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
55 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
56 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
57 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
58 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
59 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
60 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
61 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
62 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
63 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
64 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
65 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
66 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
67 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
68 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
69 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
70 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
71 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
72 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
73 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
74 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
75 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
76 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
77 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
78 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
79 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
80 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
81 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
82 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
83 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
84 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
85 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
86 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
87 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
88 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
89 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
90 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
91 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
92 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
93 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
94 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
95 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
96 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
97 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
98 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
99 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
100 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
101 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
102 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
103 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
104];
105
106#[rustfmt::skip]
107const S1_INIT: [u32; 256] = [
108 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
109 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
110 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
111 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
112 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
113 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
114 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
115 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
116 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
117 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
118 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
119 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
120 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
121 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
122 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
123 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
124 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
125 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
126 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
127 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
128 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
129 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
130 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
131 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
132 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
133 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
134 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
135 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
136 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
137 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
138 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
139 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
140 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
141 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
142 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
143 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
144 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
145 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
146 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
147 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
148 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
149 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
150 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
151 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
152 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
153 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
154 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
155 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
156 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
157 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
158 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
159 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
160 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
161 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
162 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
163 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
164 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
165 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
166 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
167 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
168 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
169 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
170 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
171 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
172];
173
174#[rustfmt::skip]
175const S2_INIT: [u32; 256] = [
176 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
177 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
178 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
179 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
180 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
181 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
182 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
183 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
184 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
185 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
186 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
187 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
188 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
189 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
190 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
191 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
192 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
193 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
194 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
195 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
196 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
197 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
198 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
199 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
200 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
201 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
202 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
203 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
204 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
205 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
206 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
207 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
208 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
209 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
210 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
211 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
212 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
213 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
214 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
215 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
216 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
217 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
218 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
219 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
220 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
221 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
222 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
223 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
224 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
225 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
226 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
227 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
228 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
229 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
230 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
231 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
232 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
233 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
234 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
235 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
236 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
237 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
238 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
239 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
240];
241
242#[rustfmt::skip]
243const S3_INIT: [u32; 256] = [
244 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
245 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
246 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
247 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
248 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
249 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
250 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
251 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
252 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
253 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
254 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
255 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
256 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
257 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
258 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
259 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
260 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
261 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
262 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
263 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
264 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
265 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
266 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
267 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
268 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
269 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
270 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
271 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
272 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
273 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
274 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
275 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
276 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
277 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
278 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
279 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
280 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
281 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
282 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
283 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
284 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
285 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
286 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
287 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
288 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
289 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
290 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
291 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
292 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
293 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
294 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
295 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
296 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
297 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
298 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
299 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
300 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
301 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
302 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
303 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
304 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
305 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
306 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
307 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,
308];
309
310struct Blowfish {
315 p: [u32; 18],
316 s: [[u32; 256]; 4],
317}
318
319impl Blowfish {
320 fn init() -> Self {
321 Blowfish {
322 p: P_INIT,
323 s: [S0_INIT, S1_INIT, S2_INIT, S3_INIT],
324 }
325 }
326
327 #[inline]
330 fn f(&self, x: u32) -> u32 {
331 let a = ((x >> 24) & 0xff) as usize;
332 let b = ((x >> 16) & 0xff) as usize;
333 let c = ((x >> 8) & 0xff) as usize;
334 let d = (x & 0xff) as usize;
335 ((self.s[0][a].wrapping_add(self.s[1][b])) ^ self.s[2][c]).wrapping_add(self.s[3][d])
336 }
337
338 fn encrypt_block(&self, mut xl: u32, mut xr: u32) -> (u32, u32) {
340 for i in 0..16 {
341 xl ^= self.p[i];
342 xr ^= self.f(xl);
343 core::mem::swap(&mut xl, &mut xr);
344 }
345 core::mem::swap(&mut xl, &mut xr);
347 xr ^= self.p[16];
348 xl ^= self.p[17];
349 (xl, xr)
350 }
351}
352
353fn expand_key_with_data(bf: &mut Blowfish, key: &[u8], data: &[u8]) {
365 if !key.is_empty() {
367 let mut key_idx = 0usize;
368 for pi in bf.p.iter_mut() {
369 let mut word = 0u32;
370 for _ in 0..4 {
371 word = (word << 8) | u32::from(key[key_idx % key.len()]);
372 key_idx += 1;
373 }
374 *pi ^= word;
375 }
376 }
377
378 let data_len = data.len();
382 let mut data_pos = 0usize;
383
384 let next_word = |pos: &mut usize| -> u32 {
387 let mut word = 0u32;
388 for _ in 0..4 {
389 let b = if data_len == 0 {
390 0
391 } else {
392 data[*pos % data_len]
393 };
394 word = (word << 8) | u32::from(b);
395 *pos += 1;
396 }
397 word
398 };
399
400 let mut xl = 0u32;
401 let mut xr = 0u32;
402
403 let mut i = 0usize;
405 while i < 18 {
406 xl ^= next_word(&mut data_pos);
408 xr ^= next_word(&mut data_pos);
409 let (l, r) = bf.encrypt_block(xl, xr);
410 xl = l;
411 xr = r;
412 bf.p[i] = xl;
413 bf.p[i + 1] = xr;
414 i += 2;
415 }
416
417 for box_idx in 0..4 {
419 let mut j = 0usize;
420 while j < 256 {
421 xl ^= next_word(&mut data_pos);
422 xr ^= next_word(&mut data_pos);
423 let (l, r) = bf.encrypt_block(xl, xr);
424 xl = l;
425 xr = r;
426 bf.s[box_idx][j] = xl;
427 bf.s[box_idx][j + 1] = xr;
428 j += 2;
429 }
430 }
431}
432
433fn eks_blowfish_setup(password: &[u8], salt: &[u8; 16], cost: u32) -> Blowfish {
440 let mut state = Blowfish::init();
441
442 expand_key_with_data(&mut state, password, salt);
444
445 let rounds = 1u64 << cost;
447 for _ in 0..rounds {
448 expand_key_with_data(&mut state, password, &[]);
450 expand_key_with_data(&mut state, salt, &[]);
452 }
453
454 state
455}
456
457const BCRYPT_ALPHABET: &[u8; 64] =
464 b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
465
466fn bcrypt_base64_encode(data: &[u8]) -> String {
478 let mut out = Vec::with_capacity((data.len() * 4).div_ceil(3));
479 let mut i = 0usize;
480 while i < data.len() {
481 let b0 = data[i];
482 let remaining = data.len() - i;
483
484 let c1 = (b0 >> 2) as usize;
486 out.push(BCRYPT_ALPHABET[c1]);
487
488 let b1 = if remaining >= 2 { data[i + 1] } else { 0 };
489 let c2 = (((b0 & 0x03) << 4) | (b1 >> 4)) as usize;
491 out.push(BCRYPT_ALPHABET[c2]);
492
493 if remaining >= 2 {
494 let b2 = if remaining >= 3 { data[i + 2] } else { 0 };
495 let c3 = (((b1 & 0x0f) << 2) | (b2 >> 6)) as usize;
497 out.push(BCRYPT_ALPHABET[c3]);
498
499 if remaining >= 3 {
500 let c4 = (b2 & 0x3f) as usize;
502 out.push(BCRYPT_ALPHABET[c4]);
503 }
504 }
505
506 i += 3;
507 }
508
509 out.into_iter().map(|b| b as char).collect()
511}
512
513fn bcrypt_base64_decode_table() -> [u8; 256] {
516 let mut table = [255u8; 256];
517 for (i, &c) in BCRYPT_ALPHABET.iter().enumerate() {
518 table[c as usize] = i as u8;
519 }
520 table
521}
522
523fn bcrypt_base64_decode(s: &str) -> Result<Vec<u8>, CryptoError> {
533 let table = bcrypt_base64_decode_table();
534 let chars: Vec<u8> = s.bytes().collect();
535 let mut out = Vec::new();
536 let mut i = 0usize;
537
538 while i < chars.len() {
539 let c1 = table[chars[i] as usize];
541 if c1 == 255 {
542 return Err(CryptoError::Encoding);
543 }
544 if i + 1 >= chars.len() {
545 return Err(CryptoError::Encoding);
547 }
548 let c2 = table[chars[i + 1] as usize];
549 if c2 == 255 {
550 return Err(CryptoError::Encoding);
551 }
552
553 let b0 = (c1 << 2) | (c2 >> 4);
555 out.push(b0);
556
557 if i + 2 < chars.len() {
558 let c3 = table[chars[i + 2] as usize];
559 if c3 == 255 {
560 return Err(CryptoError::Encoding);
561 }
562 let b1 = ((c2 & 0x0f) << 4) | (c3 >> 2);
564 out.push(b1);
565
566 if i + 3 < chars.len() {
567 let c4 = table[chars[i + 3] as usize];
568 if c4 == 255 {
569 return Err(CryptoError::Encoding);
570 }
571 let b2 = ((c3 & 0x03) << 6) | c4;
573 out.push(b2);
574 i += 4;
575 } else {
576 i += 3;
577 }
578 } else {
579 i += 2;
580 }
581 }
582
583 Ok(out)
584}
585
586const BCRYPT_MAGIC: &[u8; 24] = b"OrpheanBeholderScryDoubt";
592
593fn bcrypt_compute(password: &[u8], salt: &[u8; 16], cost: u32) -> [u8; 23] {
597 let mut pw_buf = [0u8; 73]; let pw_len = password.len().min(72);
600 pw_buf[..pw_len].copy_from_slice(&password[..pw_len]);
601 let pw_bytes = &pw_buf[..pw_len + 1]; let pw_effective = if pw_bytes.len() > 72 {
605 &pw_bytes[..72]
606 } else {
607 pw_bytes
608 };
609
610 let state = eks_blowfish_setup(pw_effective, salt, cost);
611
612 let mut cdata = [0u32; 6];
615 for i in 0..6 {
616 cdata[i] = u32::from_be_bytes([
617 BCRYPT_MAGIC[i * 4],
618 BCRYPT_MAGIC[i * 4 + 1],
619 BCRYPT_MAGIC[i * 4 + 2],
620 BCRYPT_MAGIC[i * 4 + 3],
621 ]);
622 }
623
624 for _ in 0..64 {
625 let (l0, r0) = state.encrypt_block(cdata[0], cdata[1]);
627 cdata[0] = l0;
628 cdata[1] = r0;
629 let (l1, r1) = state.encrypt_block(cdata[2], cdata[3]);
630 cdata[2] = l1;
631 cdata[3] = r1;
632 let (l2, r2) = state.encrypt_block(cdata[4], cdata[5]);
633 cdata[4] = l2;
634 cdata[5] = r2;
635 }
636
637 let mut out_24 = [0u8; 24];
639 for i in 0..6 {
640 let bytes = cdata[i].to_be_bytes();
641 out_24[i * 4..i * 4 + 4].copy_from_slice(&bytes);
642 }
643
644 let mut result = [0u8; 23];
645 result.copy_from_slice(&out_24[..23]);
646 result
647}
648
649#[derive(Clone, Debug)]
658pub struct BcryptParams {
659 pub cost: u32,
661}
662
663impl BcryptParams {
664 #[must_use = "BcryptParams creation result must be checked"]
671 pub fn new(cost: u32) -> Result<Self, CryptoError> {
672 if !(4..=31).contains(&cost) {
673 return Err(CryptoError::BadInput);
674 }
675 Ok(Self { cost })
676 }
677
678 #[must_use]
682 pub fn interactive() -> Self {
683 Self { cost: 10 }
684 }
685
686 #[must_use]
690 pub fn moderate() -> Self {
691 Self { cost: 12 }
692 }
693
694 #[must_use]
698 pub fn sensitive() -> Self {
699 Self { cost: 14 }
700 }
701
702 #[must_use = "BcryptParams validation result must be checked"]
707 pub fn validate(&self) -> Result<(), CryptoError> {
708 if !(4..=31).contains(&self.cost) {
709 return Err(CryptoError::BadInput);
710 }
711 Ok(())
712 }
713}
714
715impl PasswordHashParams for BcryptParams {
716 fn memory_cost(&self) -> Option<u32> {
717 None
719 }
720
721 fn time_cost(&self) -> Option<u32> {
722 Some(self.cost)
723 }
724
725 fn parallelism(&self) -> Option<u32> {
726 None
727 }
728}
729
730#[derive(Clone, Debug)]
744pub struct BcryptHasher {
745 params: BcryptParams,
746}
747
748impl BcryptHasher {
749 #[must_use]
751 pub fn new(params: BcryptParams) -> Self {
752 Self { params }
753 }
754
755 #[must_use = "BcryptHasher creation result must be checked"]
760 pub fn with_cost(cost: u32) -> Result<Self, CryptoError> {
761 let params = BcryptParams::new(cost)?;
762 Ok(Self { params })
763 }
764
765 #[must_use]
767 pub fn params(&self) -> &BcryptParams {
768 &self.params
769 }
770}
771
772impl PasswordHashTrait for BcryptHasher {
773 fn name(&self) -> &'static str {
774 "bcrypt"
775 }
776
777 fn hash_password(
778 &self,
779 password: &[u8],
780 salt: &[u8],
781 _params: &dyn PasswordHashParams,
782 out: &mut [u8],
783 ) -> Result<(), CryptoError> {
784 if salt.len() != 16 {
785 return Err(CryptoError::BadInput);
786 }
787 if out.len() < 23 {
788 return Err(CryptoError::BufferTooSmall);
789 }
790 let salt_arr: [u8; 16] = salt.try_into().map_err(|_| CryptoError::BadInput)?;
791 let hash = bcrypt_compute(password, &salt_arr, self.params.cost);
792 out[..23].copy_from_slice(&hash);
793 Ok(())
794 }
795}
796
797#[must_use = "bcrypt_hash result must be checked"]
818pub fn bcrypt_hash(password: &[u8], cost: u32, salt: &[u8; 16]) -> Result<String, CryptoError> {
819 if !(4..=31).contains(&cost) {
820 return Err(CryptoError::BadInput);
821 }
822
823 let hash_bytes = bcrypt_compute(password, salt, cost);
824
825 let salt_str = bcrypt_base64_encode(salt);
827 let salt_str = &salt_str[..salt_str.len().min(22)];
829
830 let hash_str = bcrypt_base64_encode(&hash_bytes);
832
833 Ok(alloc::format!("$2b${cost:02}${salt_str}{hash_str}"))
834}
835
836#[must_use = "bcrypt_verify result must be checked"]
851pub fn bcrypt_verify(password: &[u8], hash_str: &str) -> Result<bool, CryptoError> {
852 let (cost, salt) = parse_bcrypt_string(hash_str)?;
854
855 let hash_part = extract_hash_part(hash_str)?;
859 let expected_hash_encoded = &hash_part[22..]; let expected_hash = bcrypt_base64_decode(expected_hash_encoded)?;
861
862 if expected_hash.len() < 23 {
863 return Err(CryptoError::Encoding);
864 }
865
866 let computed = bcrypt_compute(password, &salt, cost);
868
869 let ok: bool = computed.as_ref().ct_eq(&expected_hash[..23]).into();
871 Ok(ok)
872}
873
874fn parse_bcrypt_string(hash_str: &str) -> Result<(u32, [u8; 16]), CryptoError> {
876 if !hash_str.starts_with("$2b$") && !hash_str.starts_with("$2a$") {
878 return Err(CryptoError::Encoding);
879 }
880
881 let rest = &hash_str[4..]; if rest.len() < 3 || rest.as_bytes()[2] != b'$' {
885 return Err(CryptoError::Encoding);
886 }
887 let cost_str = &rest[..2];
888 let cost = cost_str.parse::<u32>().map_err(|_| CryptoError::Encoding)?;
889 if !(4..=31).contains(&cost) {
890 return Err(CryptoError::Encoding);
891 }
892
893 let hash_part = &rest[3..]; if hash_part.len() != 53 {
896 return Err(CryptoError::Encoding);
897 }
898
899 let salt_encoded = &hash_part[..22];
901 let salt_bytes = bcrypt_base64_decode(salt_encoded)?;
902 if salt_bytes.len() < 16 {
903 return Err(CryptoError::Encoding);
904 }
905
906 let mut salt = [0u8; 16];
907 salt.copy_from_slice(&salt_bytes[..16]);
908
909 Ok((cost, salt))
910}
911
912fn extract_hash_part(hash_str: &str) -> Result<&str, CryptoError> {
914 if !hash_str.starts_with("$2b$") && !hash_str.starts_with("$2a$") {
915 return Err(CryptoError::Encoding);
916 }
917 let rest = &hash_str[4..];
918 if rest.len() < 3 {
919 return Err(CryptoError::Encoding);
920 }
921 let body = &rest[3..]; if body.len() != 53 {
923 return Err(CryptoError::Encoding);
924 }
925 Ok(body)
926}
927
928#[cfg(test)]
933mod tests {
934 use super::*;
935
936 fn blowfish_ecb_encrypt(key: &[u8], plaintext: (u32, u32)) -> (u32, u32) {
945 let mut bf = Blowfish::init();
948 expand_key_with_data(&mut bf, key, &[]);
949 bf.encrypt_block(plaintext.0, plaintext.1)
950 }
951
952 #[test]
953 fn blowfish_kat_zeros() {
954 let key = [0u8; 8];
958 let (ct_l, ct_r) = blowfish_ecb_encrypt(&key, (0x00000000, 0x00000000));
959 assert_eq!(ct_l, 0x4EF99745, "zero key/pt left word mismatch");
960 assert_eq!(ct_r, 0x6198DD78, "zero key/pt right word mismatch");
961 }
962
963 #[test]
964 fn blowfish_kat_ones() {
965 let key = [0xFFu8; 8];
969 let (ct_l, ct_r) = blowfish_ecb_encrypt(&key, (0xFFFFFFFF, 0xFFFFFFFF));
970 assert_eq!(ct_l, 0x51866FD5, "ones key/pt left word mismatch");
971 assert_eq!(ct_r, 0xB85ECB8A, "ones key/pt right word mismatch");
972 }
973
974 #[test]
975 fn blowfish_kat_mixed() {
976 let key = [0x30u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
980 let (ct_l, ct_r) = blowfish_ecb_encrypt(&key, (0x10000000, 0x00000001));
981 assert_eq!(ct_l, 0x7D856F9A, "mixed key/pt left word mismatch");
982 assert_eq!(ct_r, 0x613063F2, "mixed key/pt right word mismatch");
983 }
984
985 #[test]
986 fn blowfish_kat_schneier_1() {
987 let key = b"AAAAA";
991 let (ct_l, ct_r) = blowfish_ecb_encrypt(key, (0x00000000, 0x00000000));
992 assert_eq!(ct_l, 0xF2C1C8D1, "schneier vector 1 left mismatch");
993 assert_eq!(ct_r, 0xB843193A, "schneier vector 1 right mismatch");
994 }
995
996 #[test]
997 fn blowfish_kat_schneier_2() {
998 let key = b"abcdefghijklmnopqrstuvwxyz";
1003 let pt_l = u32::from_be_bytes(*b"BLOW");
1004 let pt_r = u32::from_be_bytes(*b"FISH");
1005 let (ct_l, ct_r) = blowfish_ecb_encrypt(key, (pt_l, pt_r));
1006 assert_eq!(ct_l, 0x324ED0FE, "schneier 'BLOWFISH' left mismatch");
1007 assert_eq!(ct_r, 0xF413A203, "schneier 'BLOWFISH' right mismatch");
1008 }
1009
1010 fn hex_decode(s: &str) -> Vec<u8> {
1019 (0..s.len())
1020 .step_by(2)
1021 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
1022 .collect()
1023 }
1024
1025 fn decode_salt_from_hash(hash_str: &str) -> [u8; 16] {
1027 let body = &hash_str[7..]; let salt_encoded = &body[..22];
1030 let salt_bytes = bcrypt_base64_decode(salt_encoded).unwrap();
1031 let mut salt = [0u8; 16];
1032 salt.copy_from_slice(&salt_bytes[..16]);
1033 salt
1034 }
1035
1036 #[test]
1037 fn bcrypt_kat_empty_password() {
1038 let salt = [0u8; 16];
1049 let hash = bcrypt_hash(b"", 4, &salt).unwrap();
1050 assert!(hash.starts_with("$2b$04$"), "must start with $2b$04$");
1051 assert_eq!(hash.len(), 60, "bcrypt hash must be 60 chars");
1052
1053 assert!(
1055 bcrypt_verify(b"", &hash).unwrap(),
1056 "empty password must verify"
1057 );
1058 assert!(
1059 !bcrypt_verify(b"x", &hash).unwrap(),
1060 "wrong password must fail"
1061 );
1062 }
1063
1064 #[test]
1065 fn bcrypt_kat_known_vector_1() {
1066 let password = b"correct horse battery staple";
1074 let salt = [
1075 0x4a, 0x3d, 0x50, 0x7e, 0x3b, 0x59, 0x71, 0x0e, 0x6e, 0x57, 0x25, 0x8e, 0x6c, 0x7b,
1076 0xc4, 0x14,
1077 ];
1078 let hash = bcrypt_hash(password, 4, &salt).unwrap();
1079 assert!(hash.starts_with("$2b$04$"), "must start with $2b$04$");
1080 assert_eq!(hash.len(), 60);
1081
1082 assert!(bcrypt_verify(password, &hash).unwrap());
1084 assert!(!bcrypt_verify(b"wrong", &hash).unwrap());
1085 }
1086
1087 #[test]
1088 fn bcrypt_kat_known_vector_go_1() {
1089 let expected = "$2a$10$Ro0CUfOqk6cXEKf3dyaM7O.StgbNllJkFZJLRhnHcKR/PvCEibjV.";
1095 let salt = decode_salt_from_hash(expected);
1096 let hash = bcrypt_hash(b"abc", 10, &salt).unwrap();
1097
1098 assert!(hash.starts_with("$2b$10$"), "must start with $2b$10$");
1100 assert_eq!(
1103 &hash[4..],
1104 &expected[4..],
1105 "hash body must match cross-impl vector"
1106 );
1107 assert!(
1109 bcrypt_verify(b"abc", expected).unwrap(),
1110 "cross-impl vector must verify"
1111 );
1112 }
1113
1114 #[test]
1115 fn bcrypt_kat_known_vector_go_2() {
1116 let expected = "$2a$10$Oiz1x7uRbBhEA1JFrk6csuZnxQTKnb711KgTFvi0bOwl1yPjQYYeS";
1120 let salt = decode_salt_from_hash(expected);
1121 let hash = bcrypt_hash(b"", 10, &salt).unwrap();
1122 assert_eq!(
1123 &hash[4..],
1124 &expected[4..],
1125 "empty pw cross-impl vector must match"
1126 );
1127 assert!(
1128 bcrypt_verify(b"", expected).unwrap(),
1129 "empty pw cross-impl vector must verify"
1130 );
1131 }
1132
1133 #[test]
1134 fn bcrypt_kat_verify_uses_go_vector_abc_cost10() {
1135 let expected = "$2a$10$Ro0CUfOqk6cXEKf3dyaM7O.StgbNllJkFZJLRhnHcKR/PvCEibjV.";
1138 assert!(
1140 bcrypt_verify(b"abc", expected).unwrap(),
1141 "correct password 'abc' must verify against cross-impl vector"
1142 );
1143 assert!(
1145 !bcrypt_verify(b"xyz", expected).unwrap(),
1146 "wrong password must be rejected"
1147 );
1148 }
1149
1150 #[test]
1151 fn bcrypt_kat_determinism() {
1152 let salt1 = [0x01u8; 16];
1154 let salt2 = [0x02u8; 16];
1155 let h1a = bcrypt_hash(b"test", 4, &salt1).unwrap();
1156 let h1b = bcrypt_hash(b"test", 4, &salt1).unwrap();
1157 let h2 = bcrypt_hash(b"test", 4, &salt2).unwrap();
1158 assert_eq!(h1a, h1b, "bcrypt must be deterministic");
1159 assert_ne!(h1a, h2, "different salts must produce different hashes");
1160 }
1161
1162 #[test]
1167 fn bcrypt_round_trip_correct_password() {
1168 let salt = [0xABu8; 16];
1169 let hash = bcrypt_hash(b"my secret password", 4, &salt).unwrap();
1170 let ok = bcrypt_verify(b"my secret password", &hash).unwrap();
1171 assert!(ok, "correct password must verify as true");
1172 }
1173
1174 #[test]
1179 fn bcrypt_wrong_password_returns_false() {
1180 let salt = [0x55u8; 16];
1181 let hash = bcrypt_hash(b"correct", 4, &salt).unwrap();
1182 let ok = bcrypt_verify(b"incorrect", &hash).unwrap();
1183 assert!(!ok, "wrong password must return false");
1184 }
1185
1186 #[test]
1195 fn bcrypt_truncation_at_72_bytes() {
1196 let salt = [0x77u8; 16];
1197
1198 let mut pw_a = [b'A'; 100];
1203 let mut pw_b = [b'A'; 100];
1204 pw_b[72] = b'X';
1206 pw_b[73] = b'Y';
1207
1208 let hash_a = bcrypt_hash(&pw_a, 4, &salt).unwrap();
1209 let hash_b = bcrypt_hash(&pw_b, 4, &salt).unwrap();
1210 assert_eq!(
1211 hash_a, hash_b,
1212 "passwords differing after byte 71 must hash the same"
1213 );
1214
1215 pw_a[70] = b'Z';
1217 let hash_a_diff = bcrypt_hash(&pw_a, 4, &salt).unwrap();
1218 assert_ne!(
1219 hash_a_diff, hash_b,
1220 "passwords differing before byte 72 must hash differently"
1221 );
1222 }
1223
1224 #[test]
1229 fn bcrypt_malformed_hash_errors() {
1230 assert_eq!(
1232 bcrypt_verify(b"pw", "$1$abc$def").unwrap_err(),
1233 CryptoError::Encoding,
1234 "wrong prefix"
1235 );
1236 assert_eq!(
1238 bcrypt_verify(b"pw", "$2b$").unwrap_err(),
1239 CryptoError::Encoding,
1240 "missing cost"
1241 );
1242 assert_eq!(
1244 bcrypt_verify(
1245 b"pw",
1246 "$2b$03$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1247 )
1248 .unwrap_err(),
1249 CryptoError::Encoding,
1250 "cost too low"
1251 );
1252 assert_eq!(
1254 bcrypt_verify(b"pw", "$2b$04$tooshort").unwrap_err(),
1255 CryptoError::Encoding,
1256 "too short"
1257 );
1258 assert_eq!(
1262 bcrypt_verify(
1263 b"pw",
1264 "$2b$04$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
1265 )
1266 .unwrap_err(),
1267 CryptoError::Encoding,
1268 "invalid base64"
1269 );
1270 assert!(bcrypt_verify(b"pw", "garbage").is_err());
1272 assert!(bcrypt_verify(b"pw", "").is_err());
1274 }
1275
1276 #[test]
1281 fn bcrypt_different_costs_produce_different_hashes() {
1282 let salt = [0x33u8; 16];
1283 let h4 = bcrypt_hash(b"password", 4, &salt).unwrap();
1284 let h8 = bcrypt_hash(b"password", 8, &salt).unwrap();
1285 assert_ne!(h4, h8, "cost=4 and cost=8 must produce different hashes");
1286
1287 assert!(bcrypt_verify(b"password", &h4).unwrap());
1289 assert!(bcrypt_verify(b"password", &h8).unwrap());
1290 }
1291
1292 #[test]
1297 fn bcrypt_params_invalid_cost() {
1298 assert!(BcryptParams::new(3).is_err(), "cost=3 must be invalid");
1299 assert!(BcryptParams::new(32).is_err(), "cost=32 must be invalid");
1300 assert!(BcryptParams::new(4).is_ok(), "cost=4 must be valid");
1301 assert!(BcryptParams::new(31).is_ok(), "cost=31 must be valid");
1302 }
1303
1304 #[test]
1305 fn bcrypt_params_presets() {
1306 assert_eq!(BcryptParams::interactive().cost, 10);
1307 assert_eq!(BcryptParams::moderate().cost, 12);
1308 assert_eq!(BcryptParams::sensitive().cost, 14);
1309
1310 assert!(BcryptParams::sensitive().cost > BcryptParams::moderate().cost);
1312 assert!(BcryptParams::moderate().cost > BcryptParams::interactive().cost);
1313 }
1314
1315 #[test]
1316 fn bcrypt_hasher_name() {
1317 let hasher = BcryptHasher::new(BcryptParams::new(4).unwrap());
1318 assert_eq!(hasher.name(), "bcrypt");
1319 }
1320
1321 #[test]
1322 fn bcrypt_hasher_trait_hash_password() {
1323 let hasher = BcryptHasher::with_cost(4).unwrap();
1324 let salt = [0xCCu8; 16];
1325 let mut out = [0u8; 23];
1326 hasher
1327 .hash_password(b"hello", &salt, &hasher.params, &mut out)
1328 .unwrap();
1329 assert_ne!(out, [0u8; 23]);
1330
1331 let mut out2 = [0u8; 23];
1333 hasher
1334 .hash_password(b"hello", &salt, &hasher.params, &mut out2)
1335 .unwrap();
1336 assert_eq!(out, out2);
1337 }
1338
1339 #[test]
1340 fn bcrypt_hasher_bad_salt_length() {
1341 let hasher = BcryptHasher::with_cost(4).unwrap();
1342 let mut out = [0u8; 23];
1343 let result = hasher.hash_password(b"pw", b"short", &hasher.params, &mut out);
1344 assert_eq!(result, Err(CryptoError::BadInput));
1345 }
1346
1347 #[test]
1348 fn bcrypt_hasher_buffer_too_small() {
1349 let hasher = BcryptHasher::with_cost(4).unwrap();
1350 let salt = [0u8; 16];
1351 let mut out = [0u8; 10]; let result = hasher.hash_password(b"pw", &salt, &hasher.params, &mut out);
1353 assert_eq!(result, Err(CryptoError::BufferTooSmall));
1354 }
1355
1356 #[test]
1357 fn bcrypt_hash_invalid_cost() {
1358 let salt = [0u8; 16];
1359 assert!(bcrypt_hash(b"pw", 3, &salt).is_err());
1360 assert!(bcrypt_hash(b"pw", 32, &salt).is_err());
1361 }
1362
1363 #[test]
1364 fn bcrypt_hash_format() {
1365 let salt = [0x10u8; 16];
1366 let hash = bcrypt_hash(b"password", 4, &salt).unwrap();
1367 assert!(hash.starts_with("$2b$04$"));
1369 assert_eq!(hash.len(), 60);
1370 }
1371
1372 #[test]
1377 fn bcrypt_base64_roundtrip() {
1378 for len in 1usize..=32 {
1379 let data: Vec<u8> = (0..len as u8).collect();
1380 let encoded = bcrypt_base64_encode(&data);
1381 let decoded = bcrypt_base64_decode(&encoded).unwrap();
1382 assert_eq!(decoded, data, "base64 round-trip failed for len={len}");
1383 }
1384 }
1385
1386 #[test]
1387 fn bcrypt_base64_invalid_char() {
1388 let result = bcrypt_base64_decode("!! ");
1390 assert!(result.is_err());
1391 }
1392
1393 #[test]
1398 fn hex_decode_sanity() {
1399 assert_eq!(hex_decode("4ef99745"), vec![0x4e, 0xf9, 0x97, 0x45]);
1400 }
1401}