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     char[22] salt_decode = hash[7 .. 29];
56     Vector!ubyte salt = bcryptBase64Decode(salt_decode[0 .. 22]);
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     foreach (size_t i; 0 .. b64.length)
114         b64[i] = OPENBSD_BASE64_SUB[cast(ubyte) b64[i]];
115     
116     return cast(string) b64;
117 }
118 
119 Vector!ubyte bcryptBase64Decode(char[] input)
120 {
121     __gshared immutable ubyte[256] OPENBSD_BASE64_SUB = [
122         0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
123         0x80, 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, 0x41, 0x42,
126         0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F, 0x80, 0x80,
127         0x80, 0x80, 0x80, 0x80, 0x80, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
128         0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
129         0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x80, 0x80, 0x80, 0x80, 0x80,
130         0x80, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D,
131         0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
132         0x7A, 0x30, 0x31, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
133         0x80, 0x80, 0x80, 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
144     ];
145     
146     foreach (size_t i; 0 .. input.length)
147         input[i] = OPENBSD_BASE64_SUB[cast(ubyte)(input[i])];
148     return unlock(base64Decode(cast(string)input));
149 }
150 
151 string makeBcrypt()(in string pass,
152                     auto const ref Vector!ubyte salt,
153                     ushort work_factor)
154 {
155     __gshared immutable ubyte[24] magic = [
156         0x4F, 0x72, 0x70, 0x68, 0x65, 0x61, 0x6E, 0x42,
157         0x65, 0x68, 0x6F, 0x6C, 0x64, 0x65, 0x72, 0x53,
158         0x63, 0x72, 0x79, 0x44, 0x6F, 0x75, 0x62, 0x74
159     ];
160     
161     Vector!ubyte ctext = Vector!ubyte(magic.ptr[0 .. magic.length]);
162     
163     Unique!Blowfish blowfish = new Blowfish;
164     
165     // Include the trailing NULL ubyte
166     blowfish.eksKeySchedule(cast(const(ubyte)*) pass.toStringz, pass.length + 1, salt.ptr[0 .. 16], work_factor);
167     
168     foreach (size_t i; 0 .. 64)  {
169         blowfish.encryptN(ctext.ptr, ctext.ptr, 3);
170     }
171     
172     string salt_b64 = bcryptBase64Encode(salt.ptr, salt.length);
173     
174     string work_factor_str = to!string(work_factor);
175     if (work_factor_str.length == 1)
176         work_factor_str = "0" ~ work_factor_str;
177     
178     return "$2a$" ~ work_factor_str ~ "$" ~ salt_b64[0 .. 22] ~ bcryptBase64Encode(ctext.ptr, ctext.length - 1);
179 }
180 
181 
182 static if (BOTAN_TEST):
183 import botan.test;
184 import botan.rng.auto_rng;
185 
186 static if (BOTAN_HAS_TESTS && !SKIP_BCRYPT_TEST) unittest
187 {
188     import botan.libstate.libstate;
189     globalState();
190     logDebug("Testing bcrypt.d ...");
191     size_t fails = 0;
192     
193     // Generated by jBCrypt 0.3
194     if (!checkBcrypt("abc", "$2a$05$DfPyLs.G6.To9fXEFgUL1O6HpYw3jIXgPcl/L3Qt3jESuWmhxtmpS"))
195     {
196         logTrace("Bcrypt test 1 failed");
197         fails++;
198     }
199     
200     // http://www.openwall.com/lists/john-dev/2011/06/19/2
201     if (!checkBcrypt("\xA3", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"))
202     {
203         logTrace("Bcrypt test 2 failed");
204         fails++;
205     }
206     
207     Unique!AutoSeededRNG rng = new AutoSeededRNG;
208     
209     for(ushort level = 1; level != 5; ++level)
210     {
211         const string input = "some test passphrase 123";
212         const string gen_hash = generateBcrypt(input, *rng, level);
213         
214         if (!checkBcrypt(input, gen_hash))
215         {
216             logTrace("Gen and check for bcrypt failed: " ~ gen_hash ~ " not valid");
217             ++fails;
218         }
219     }
220     
221     testReport("Bcrypt", 6, fails);
222 }