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 }