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 }