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 }