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