1 /** 2 Ulrich Drepper's SHA-based crypt(3) algorithms, as specified here: 3 https://akkadia.org/drepper/SHA-crypt.txt 4 5 The main exports are the `SHA256Crypt` and `SHA512Crypt` structs. Their `genSalt()` static members take an optional count of rounds. More rounds means more work in computing the hash — both for friendly applications and password bruteforcers. The default (as in the spec) is 5000 rounds. 6 */ 7 module passwd.sha; 8 9 @safe: 10 11 import std.algorithm.mutation : swap; 12 import std.digest.sha; 13 import std.range.primitives; 14 import std..string : representation; 15 import std.traits : ReturnType; 16 public import std.typecons : Flag, No, Yes; 17 import std.utf : byCodeUnit; 18 19 import passwd.exception; 20 import passwd.util; 21 22 /// Implementation of Ulrich Drepper's SHA256-based crypt algorithm 23 alias SHA256Crypt = SHACrypt!(SHA256, sha256crypt_output_swaps, "$5$"); 24 /// Implementation of Ulrich Drepper's SHA512-based crypt algorithm 25 alias SHA512Crypt = SHACrypt!(SHA512, sha512crypt_output_swaps, "$6$"); 26 27 package: 28 29 /// Routines for SHA-based crypt(3) algorithms 30 struct SHACrypt(Hasher, alias output_swaps, string algo_id) 31 { 32 // Example: $5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA 33 /// Maximum length needed for output of genSalt() 34 enum kMaxSaltStrLength = "$x$rounds=999999999$".length + kSHACryptSaltBytes * 8 / 6; 35 /// Maximum length needed for output of crypt() 36 enum kMaxCryptStrLength = kMaxSaltStrLength + 1 + kDigestLength * 8 / 6; 37 38 /// Generate a good salt for this algorithm 39 static string genSalt(size_t num_rounds=kDefaultSHACryptRounds) 40 { 41 import std.array : appender; 42 auto ret_app = appender!string; 43 ret_app.genSHACryptSalt(algo_id, num_rounds); 44 return ret_app[]; 45 } 46 47 /// Generate a good salt for this algorithm and write to an output range 48 static void genSalt(Out)(ref Out output, size_t num_rounds=kDefaultSHACryptRounds) if (isOutputRange!(Out, char)) 49 { 50 output.genSHACryptSalt(algo_id, num_rounds); 51 } 52 53 /// Hash password and write full crypt(3) string or just encoded digest to an output range 54 static void crypt(Out)(const(char)[] password, ref Out output, ref const(CryptPieces) salt_data, Flag!"writeSalt" write_salt = Yes.writeSalt) if (isOutputRange!(Out, char)) 55 { 56 if (write_salt) salt_data.writeSHACryptSalt(output); 57 auto digest = digestOf(password, salt_data); 58 digest.cryptB64Encode(output); 59 } 60 61 private: 62 63 /// Length in bytes of a binary digest 64 enum kDigestLength = ReturnType!(Hasher.finish).length; 65 66 /// Generate only the raw binary output of this hashing algorithm 67 static ubyte[kDigestLength] digestOf(const(char)[] password, ref const(CryptPieces) salt_data) 68 { 69 return shaCryptHash!(Hasher, output_swaps)(password, salt_data); 70 } 71 } 72 73 private: 74 75 enum kDefaultSHACryptRounds = 5000; 76 enum kMinSHACryptRounds = 1000; 77 enum kMaxSHACryptRounds = 999_999_999; 78 enum kMaxSHACryptSaltValueLength = 16; 79 enum kSHACryptSaltBytes = 12; 80 81 /// Format existing salt data and write to an output range 82 void writeSHACryptSalt(Out)(ref const(CryptPieces) salt_data, ref Out output) if (isOutputRange!(Out, char)) 83 { 84 output.put('$'); 85 foreach (char c; salt_data.algo_id) output.put(c); 86 output.put('$'); 87 if (!salt_data.params.empty) 88 { 89 import std.conv : toChars; 90 auto num_rounds = getNumRounds(salt_data.params); 91 foreach (char c; "rounds=") output.put(c); 92 foreach (char c; toChars(num_rounds)) output.put(c); 93 output.put('$'); 94 } 95 const(char)[] salt_txt = salt_data.salt_txt; 96 if (salt_txt.length > kMaxSHACryptSaltValueLength) salt_txt = salt_txt[0..kMaxSHACryptSaltValueLength]; 97 foreach (char c; salt_txt) output.put(c); 98 output.put('$'); 99 } 100 101 /// Generate a new salt string 102 void genSHACryptSalt(Out)(ref Out output, string algo_id, size_t num_rounds) if (isOutputRange!(Out, char)) 103 { 104 foreach (char c; algo_id) output.put(c); 105 106 if (num_rounds != kDefaultSHACryptRounds) 107 { 108 import std.conv : toChars; 109 foreach (char c; "rounds=") output.put(c); 110 foreach (char c; toChars(num_rounds)) output.put(c); 111 output.put('$'); 112 } 113 114 ubyte[kSHACryptSaltBytes] random_buf; 115 fillSecureRandom(random_buf[]); 116 random_buf.cryptB64Encode(output); 117 } 118 119 /** 120 Parse the params field from the salt string to get the number of rounds (or return default value) 121 122 According to the spec, the number of rounds silently clips to the min/max values. 123 */ 124 size_t getNumRounds(const(char)[] params_str) pure 125 { 126 import std.algorithm.searching : startsWith; 127 import std.utf : byCodeUnit; 128 size_t num_rounds = kDefaultSHACryptRounds; 129 if (params_str.startsWith("rounds=")) 130 { 131 params_str = params_str["rounds=".length..$]; 132 num_rounds = 0; 133 static assert (kMinSHACryptRounds * 10 < size_t.max, "Integer overflow possible"); 134 while (!params_str.empty && params_str[0] >= '0' && params_str[0] <= '9') 135 { 136 num_rounds *= 10; 137 num_rounds += params_str[0] - '0'; 138 if (num_rounds > kMaxSHACryptRounds) num_rounds = kMaxSHACryptRounds; 139 params_str.popFront(); 140 } 141 if (num_rounds < kMinSHACryptRounds) num_rounds = kMinSHACryptRounds; 142 } 143 enforce!ValueException(params_str.empty, "Field contains = character but can't be parsed as SHA crypt parameters"); 144 return num_rounds; 145 } 146 147 /// 148 unittest 149 { 150 assert (getNumRounds("") == kDefaultSHACryptRounds); 151 assert (getNumRounds("rounds=4242") == 4242); 152 assert (getNumRounds("rounds=0") == kMinSHACryptRounds); 153 assert (getNumRounds("rounds=2") == kMinSHACryptRounds); 154 assert (getNumRounds("rounds=1000000000000000000000000000000000000") == kMaxSHACryptRounds); 155 } 156 157 /// The heavy lifting of hashing the password to a binary digest 158 ReturnType!(Hasher.finish) shaCryptHash(Hasher, alias output_swaps)(const(char)[] password, ref const(CryptPieces) salt_data) 159 { 160 alias Digest = ReturnType!(Hasher.finish); 161 auto num_rounds = getNumRounds(salt_data.params); 162 auto salt_bytes = salt_data.salt_txt.representation; 163 auto password_bytes = password.representation; 164 165 if (salt_bytes.length > kMaxSHACryptSaltValueLength) salt_bytes = salt_bytes[0..kMaxSHACryptSaltValueLength]; 166 167 // Numbers correspond to steps in Ulrich Drepper's spec 168 // 1 169 Hasher hasher_out; 170 scope (exit) secureWipe(hasher_out); 171 // 2 172 hasher_out.put(password_bytes); 173 // 3 174 hasher_out.put(salt_bytes); 175 176 Digest digest_out, digest_b; 177 scope (exit) secureWipe(digest_b); 178 { 179 // 4, 5, 6 180 Hasher hasher_b = hasher_out; 181 scope (exit) secureWipe(hasher_b); 182 // 7 183 hasher_b.put(password_bytes); 184 // 8 185 digest_b = hasher_b.finish(); 186 } 187 188 // 9, 10 189 hasher_out.stretchPut(digest_b, password_bytes.length); 190 191 // 11 192 for (size_t j = password_bytes.length; j; j >>= 1) 193 { 194 if (j & 1) 195 { 196 // 11a 197 hasher_out.put(digest_b[]); 198 } 199 else 200 { 201 // 11b 202 hasher_out.put(password_bytes); 203 } 204 } 205 206 // 12 207 digest_out = hasher_out.finish(); 208 209 Digest digest_dp; 210 scope (exit) secureWipe(digest_dp); 211 { 212 // 13 213 Hasher hasher_dp; 214 scope (exit) secureWipe(hasher_dp); 215 // 14 216 foreach (_; 0..password_bytes.length) hasher_dp.put(password_bytes); 217 // 1 218 digest_dp = hasher_dp.finish(); 219 } 220 221 Digest digest_ds; 222 scope (exit) secureWipe(digest_ds); 223 { 224 // 17 225 Hasher hasher_ds; 226 scope (exit) secureWipe(hasher_ds); 227 // 18 228 foreach (_; 0..16+digest_out[0]) 229 { 230 hasher_ds.put(salt_bytes); 231 } 232 // 19 233 digest_ds = hasher_ds.finish(); 234 } 235 236 // 21 237 Hasher hasher_c; 238 scope(exit) secureWipe(hasher_c); 239 foreach (j; 0..num_rounds) 240 { 241 // 21a 242 if (j & 1) 243 { 244 // 21b 245 stretchPut(hasher_c, digest_dp, password_bytes.length); 246 } 247 else 248 { 249 // 21c 250 hasher_c.put(digest_out); 251 } 252 253 // 21d 254 if (j % 3 != 0) stretchPut(hasher_c, digest_ds, salt_bytes.length); 255 // 21e 256 if (j % 7 != 0) stretchPut(hasher_c, digest_dp, password_bytes.length); 257 if (j & 1) 258 { 259 // 21f 260 hasher_c.put(digest_out); 261 } 262 else 263 { 264 // 21g 265 stretchPut(hasher_c, digest_dp, password_bytes.length); 266 } 267 digest_out = hasher_c.finish(); 268 hasher_c.start(); 269 } 270 271 // The output gets permuted as a final flourish 272 foreach (j; 0..output_swaps.length) 273 { 274 swap(digest_out[j], digest_out[output_swaps[j]]); 275 } 276 277 return digest_out; 278 } 279 280 immutable(size_t[]) sha256crypt_output_swaps = permSwapDecomposition([ 281 20, 10, 0, 282 11, 1, 21, 283 2, 22, 12, 284 23, 13, 3, 285 14, 4, 24, 286 5, 25, 15, 287 26, 16, 6, 288 17, 7, 27, 289 8, 28, 18, 290 29, 19, 9, 291 30, 31 292 ]); 293 294 immutable(size_t[]) sha512crypt_output_swaps = permSwapDecomposition([ 295 42, 21, 0, 296 1, 43, 22, 297 23, 2, 44, 298 45, 24, 3, 299 4, 46, 25, 300 26, 5, 47, 301 48, 27, 6, 302 7, 49, 28, 303 29, 8, 50, 304 51, 30, 9, 305 10, 52, 31, 306 32, 11, 53, 307 54, 33, 12, 308 13, 55, 34, 309 35, 14, 56, 310 57, 36, 15, 311 16, 58, 37, 312 38, 17, 59, 313 60, 39, 18, 314 19, 61, 40, 315 41, 20, 62, 316 63 317 ]);