1 /**
2 * Bcrypt Password Hashing
3 * 
4 * Copyright:
5 * (C) 2011 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.passhash.bcrypt;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_BCRYPT && BOTAN_HAS_BLOWFISH):
15 import botan.rng.rng;
16 import botan.utils.loadstor;
17 import botan.block.blowfish;
18 import botan.codec.base64;
19 import botan.utils.types;
20 import botan.utils.mem_ops;
21 import std..string : toStringz;
22 import std.conv : to;
23 
24 /**
25 * Create a password hash using Bcrypt
26 * Params:
27 *  password = the password
28 *  rng = a random number generator
29 *  work_factor = how much work to do to slow down guessing attacks
30 *
31 * @see http://www.usenix.org/events/usenix99/provos/provos_html/
32 */
33 string generateBcrypt(in string password,
34                       RandomNumberGenerator rng,
35                       ushort work_factor = 10)
36 {
37     return makeBcrypt(password, unlock(rng.randomVec(16)), work_factor);
38 }
39 
40 /**
41 * Check a previously created password hash
42 * Params:
43 *  password = the password to check against
44 *  hash = the stored hash to check against
45 */
46 bool checkBcrypt(in string password, in string hash)
47 {
48     if (hash.length != 60 ||
49         hash[0] != '$' || hash[1] != '2' || hash[2] != 'a' ||
50         hash[3] != '$' || hash[6] != '$')
51     {
52         return false;
53     }
54     const ushort workfactor = cast(ushort) to!uint(hash[4 .. 6]);
55 
56     Vector!ubyte salt = bcryptBase64Decode(hash[7 .. 29].dup);
57     const string compare = makeBcrypt(password, salt, workfactor);
58 	// constant time memory comparisons on hashes, inefficient but necessary for timing attacks
59 	bool valid = true;
60 	if (compare.length != hash.length)
61 		valid = false;
62 	else {
63 		foreach (i, immutable(char) c; hash) {
64 			foreach (j, immutable(char) c2; compare) {
65 				if (i != j) continue;
66 				if (c != c2) {
67 					valid = false;
68 				}
69 				break;
70 			}
71 		}
72 	}
73     return valid;
74 }
75 
76 
77 private:
78 
79 string bcryptBase64Encode(const(ubyte)* input, size_t length)
80 {
81     // Bcrypt uses a non-standard base64 alphabet
82     __gshared immutable ubyte[256] OPENBSD_BASE64_SUB = [
83         0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
84         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
85         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
86         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x38, 0x80, 0x80, 0x80, 0x39,
87         0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x80, 0x80,
88         0x80, 0x80, 0x80, 0x80, 0x80, 0x2E, 0x2F, 0x41, 0x42, 0x43, 0x44, 0x45,
89         0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51,
90         0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x80, 0x80, 0x80, 0x80, 0x80,
91         0x80, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
92         0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
93         0x76, 0x77, 0x78, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
94         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
95         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
96         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
97         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
98         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
99         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
100         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
101         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
102         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
103         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
104         0x80, 0x80, 0x80, 0x80
105     ];
106     
107     char[] b64 = base64Encode(input, length).dup;
108 
109     while (b64.length && b64[b64.length-1] == '=') {
110         b64 = b64[0 .. $-1];
111 
112     }
113     
114     foreach (size_t i; 0 .. b64.length)
115         b64[i] = OPENBSD_BASE64_SUB[cast(ubyte) b64[i]];
116     
117     return cast(string) b64;
118 }
119 
120 Vector!ubyte bcryptBase64Decode(char[] input)
121 {
122     __gshared immutable ubyte[256] OPENBSD_BASE64_SUB = [
123         0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
124         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
125         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
126         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x41, 0x42,
127         0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F, 0x80, 0x80,
128         0x80, 0x80, 0x80, 0x80, 0x80, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
129         0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
130         0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x80, 0x80, 0x80, 0x80, 0x80,
131         0x80, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D,
132         0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
133         0x7A, 0x30, 0x31, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
134         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
135         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
136         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
137         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
138         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
139         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
140         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
141         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
142         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
143         0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
144         0x80, 0x80, 0x80, 0x80
145     ];
146     
147     foreach (size_t i; 0 .. input.length)
148         input[i] = OPENBSD_BASE64_SUB[cast(ubyte)(input[i])];
149     return unlock(base64Decode(cast(string)input));
150 }
151 
152 string makeBcrypt()(in string pass,
153                     auto const ref Vector!ubyte salt,
154                     ushort work_factor)
155 {
156     __gshared immutable ubyte[24] magic = [
157         0x4F, 0x72, 0x70, 0x68, 0x65, 0x61, 0x6E, 0x42,
158         0x65, 0x68, 0x6F, 0x6C, 0x64, 0x65, 0x72, 0x53,
159         0x63, 0x72, 0x79, 0x44, 0x6F, 0x75, 0x62, 0x74
160     ];
161     
162     Vector!ubyte ctext = Vector!ubyte(magic.ptr[0 .. magic.length]);
163     
164     Unique!Blowfish blowfish = new Blowfish;
165     
166     // Include the trailing NULL ubyte
167     blowfish.eksKeySchedule(cast(const(ubyte)*) pass.toStringz, pass.length + 1, salt.ptr[0 .. 16], work_factor);
168     
169     foreach (size_t i; 0 .. 64)  {
170         blowfish.encryptN(ctext.ptr, ctext.ptr, 3);
171     }
172     
173     string salt_b64 = bcryptBase64Encode(salt.ptr, salt.length);
174     
175     string work_factor_str = to!string(work_factor);
176     if (work_factor_str.length == 1)
177         work_factor_str = "0" ~ work_factor_str;
178     
179     return "$2a$" ~ work_factor_str ~ "$" ~ salt_b64[0 .. 22] ~ bcryptBase64Encode(ctext.ptr, ctext.length - 1);
180 }
181 
182 
183 static if (BOTAN_TEST):
184 import botan.test;
185 import botan.rng.auto_rng;
186 
187 static if (BOTAN_HAS_TESTS && !SKIP_BCRYPT_TEST) unittest
188 {
189     import botan.libstate.libstate;
190     globalState();
191     logDebug("Testing bcrypt.d ...");
192     size_t fails = 0;
193     
194     // Generated by jBCrypt 0.3
195     if (!checkBcrypt("abc", "$2a$05$DfPyLs.G6.To9fXEFgUL1O6HpYw3jIXgPcl/L3Qt3jESuWmhxtmpS"))
196     {
197         logTrace("Bcrypt test 1 failed");
198         fails++;
199     }
200     
201     // http://www.openwall.com/lists/john-dev/2011/06/19/2
202     if (!checkBcrypt("\xA3", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"))
203     {
204         logTrace("Bcrypt test 2 failed");
205         fails++;
206     }
207     
208     Unique!AutoSeededRNG rng = new AutoSeededRNG;
209     
210     for(ushort level = 1; level != 5; ++level)
211     {
212         const string input = "some test passphrase 123";
213         const string gen_hash = generateBcrypt(input, *rng, level);
214         
215         if (!checkBcrypt(input, gen_hash))
216         {
217             logTrace("Gen and check for bcrypt failed: " ~ gen_hash ~ " not valid");
218             ++fails;
219         }
220     }
221     
222     testReport("Bcrypt", 6, fails);
223 }