1 /// Module handling multiple hash types dynamically. 2 /// 3 /// Authors: dd86k <dd@dax.moe> 4 /// Copyright: No rights reserved 5 /// License: CC0 6 module ddh; 7 8 import std.digest; 9 import std.digest.sha, std.digest.md, std.digest.ripemd, std.digest.crc, std.digest.murmurhash; 10 import sha3d, blake2d; 11 import std.base64; 12 import std.format : formattedRead; 13 14 // Adds dynamic seeding to supported hashes 15 private class HashSeeded(T) if (isDigest!T && hasBlockSize!T) : WrapperDigest!T 16 { 17 @trusted nothrow void seed(uint input) 18 { 19 _digest = T(input); 20 } 21 } 22 23 private alias MurmurHash3_32_SeededDigest = HashSeeded!(MurmurHash3!32); 24 private alias MurmurHash3_128_32_SeededDigest = HashSeeded!(MurmurHash3!(128, 32)); 25 private alias MurmurHash3_128_64_SeededDigest = HashSeeded!(MurmurHash3!(128, 64)); 26 27 enum HashType 28 { 29 CRC32, 30 CRC64ISO, 31 CRC64ECMA, 32 MurmurHash3_32, 33 MurmurHash3_128_32, 34 MurmurHash3_128_64, 35 MD5, 36 RIPEMD160, 37 SHA1, 38 SHA224, 39 SHA256, 40 SHA384, 41 SHA512, 42 SHA3_224, 43 SHA3_256, 44 SHA3_384, 45 SHA3_512, 46 SHAKE128, 47 SHAKE256, 48 BLAKE2b512, 49 BLAKE2s256, 50 } 51 52 enum HashCount = HashType.max + 1; 53 enum InvalidHash = cast(HashType)-1; 54 55 struct HashInfo 56 { 57 HashType type; 58 string fullName, alias_, tag, tag2; 59 } 60 61 immutable 62 { 63 string crc32 = "crc32"; 64 string crc64iso = "crc64iso"; 65 string crc64ecma = "crc64ecma"; 66 string murmur3a = "murmur3a"; 67 string murmur3c = "murmur3c"; 68 string murmur3f = "murmur3f"; 69 string md5 = "md5"; 70 string ripemd160 = "ripemd160"; 71 string sha1 = "sha1"; 72 string sha224 = "sha224"; 73 string sha256 = "sha256"; 74 string sha384 = "sha384"; 75 string sha512 = "sha512"; 76 string sha3_224 = "sha3-224"; 77 string sha3_256 = "sha3-256"; 78 string sha3_384 = "sha3-384"; 79 string sha3_512 = "sha3-512"; 80 string shake128 = "shake128"; 81 string shake256 = "shake256"; 82 string blake2b512 = "blake2b512"; 83 string blake2s256 = "blake2s256"; 84 } 85 86 // Full name: Should be based on their full specification name 87 // Alias: Should be based on a simple lowercase name. See `openssl dgst -list` for examples. 88 // Tag name: Should be based on an full uppercase name. See openssl dgst output for examples. 89 //TODO: Alternative Alias name 90 // Some aliases, like sha3-256 and ripemd160, are a little long to type 91 // "sha3" and "rmd160" fit better. 92 //TODO: Alternative Tag name 93 // For some reason, NetBSD seems to be using other names such as RMD160, 94 // SHA512, etc. under OpenSSL. Is this a GNU/BSD thing? 95 immutable HashInfo[HashCount] hashInfo = [ 96 // HashType, Full, Alias, Tag (openssl), Tag2 (gnu) 97 { HashType.CRC32, 98 "CRC-32", crc32, "CRC32", }, 99 { HashType.CRC64ISO, 100 "CRC-64-ISO", crc64iso, "CRC64ISO", }, 101 { HashType.CRC64ECMA, 102 "CRC-64-ECMA", crc64ecma, "CRC64ECMA", }, 103 { HashType.MurmurHash3_32, 104 "MurmurHash3-32", murmur3a, "MURMURHASH3-32", }, 105 { HashType.MurmurHash3_128_32, 106 "MurmurHash3-128/32", murmur3c, "MURMURHASH3-128-32", }, 107 { HashType.MurmurHash3_128_64, 108 "MurmurHash3-128/64", murmur3f, "MURMURHASH3-128-64", }, 109 { HashType.MD5, 110 "MD5-128", md5, "MD5", }, 111 { HashType.RIPEMD160, 112 "RIPEMD-160", ripemd160, "RIPEMD160", "RMD160" }, 113 { HashType.SHA1, 114 "SHA-1-160", sha1, "SHA1", }, 115 { HashType.SHA224, 116 "SHA-2-224", sha224, "SHA2-224", "SHA224" }, 117 { HashType.SHA256, 118 "SHA-2-256", sha256, "SHA2-256", "SHA256" }, 119 { HashType.SHA384, 120 "SHA-2-384", sha384, "SHA2-384", "SHA384" }, 121 { HashType.SHA512, 122 "SHA-2-512", sha512, "SHA2-512", "SHA512" }, 123 { HashType.SHA3_224, 124 "SHA-3-224", sha3_224, "SHA3-224", }, 125 { HashType.SHA3_256, 126 "SHA-3-256", sha3_256, "SHA3-256", }, 127 { HashType.SHA3_384, 128 "SHA-3-384", sha3_384, "SHA3-384", }, 129 { HashType.SHA3_512, 130 "SHA-3-512", sha3_512, "SHA3-512", }, 131 { HashType.SHAKE128, 132 "SHAKE-128", shake128, "SHAKE-128", }, 133 { HashType.SHAKE256, 134 "SHAKE-256", shake256, "SHAKE-256", }, 135 { HashType.BLAKE2b512, 136 "BLAKE2b-512", blake2b512, "BLAKE2B-512", }, 137 { HashType.BLAKE2s256, 138 "BLAKE2s-256", blake2s256, "BLAKE2S-256", }, 139 ]; 140 141 struct Ddh 142 { 143 Digest digest; 144 HashType type; 145 ubyte[] result; 146 immutable(HashInfo)* info; 147 bool checksum; 148 149 int initiate(HashType t) 150 { 151 final switch (t) with (HashType) 152 { 153 case CRC32: digest = new CRC32Digest(); break; 154 case CRC64ISO: digest = new CRC64ISODigest(); break; 155 case CRC64ECMA: digest = new CRC64ECMADigest(); break; 156 case MD5: digest = new MD5Digest(); break; 157 case RIPEMD160: digest = new RIPEMD160Digest(); break; 158 case SHA1: digest = new SHA1Digest(); break; 159 case SHA224: digest = new SHA224Digest(); break; 160 case SHA256: digest = new SHA256Digest(); break; 161 case SHA384: digest = new SHA384Digest(); break; 162 case SHA512: digest = new SHA512Digest(); break; 163 case SHA3_224: digest = new SHA3_224Digest(); break; 164 case SHA3_256: digest = new SHA3_256Digest(); break; 165 case SHA3_384: digest = new SHA3_384Digest(); break; 166 case SHA3_512: digest = new SHA3_512Digest(); break; 167 case SHAKE128: digest = new SHAKE128Digest(); break; 168 case SHAKE256: digest = new SHAKE256Digest(); break; 169 case BLAKE2b512: digest = new BLAKE2b512Digest(); break; 170 case BLAKE2s256: digest = new BLAKE2s256Digest(); break; 171 case MurmurHash3_32: digest = new MurmurHash3_32_SeededDigest(); break; 172 case MurmurHash3_128_32: digest = new MurmurHash3_128_32_SeededDigest(); break; 173 case MurmurHash3_128_64: digest = new MurmurHash3_128_64_SeededDigest(); break; 174 } 175 176 type = t; 177 info = &hashInfo[t]; 178 checksum = t <= HashType.CRC64ECMA; 179 180 return 0; 181 } 182 183 void key(const(ubyte)[] input...) 184 { 185 switch (type) with (HashType) 186 { 187 case BLAKE2b512: (cast(BLAKE2b512Digest)digest).key(input); break; 188 case BLAKE2s256: (cast(BLAKE2s256Digest)digest).key(input); break; 189 default: throw new Exception("Digest does not support keying."); 190 } 191 } 192 193 void seed(uint input) 194 { 195 switch (type) with (HashType) 196 { 197 case MurmurHash3_32: (cast(MurmurHash3_32_SeededDigest)digest).seed(input); break; 198 case MurmurHash3_128_32: (cast(MurmurHash3_128_32_SeededDigest)digest).seed(input); break; 199 case MurmurHash3_128_64: (cast(MurmurHash3_128_64_SeededDigest)digest).seed(input); break; 200 default: throw new Exception("Digest does not support seeding."); 201 } 202 } 203 204 void reset() 205 { 206 digest.reset(); 207 } 208 209 size_t length() 210 { 211 return digest.length(); 212 } 213 214 void put(scope const(ubyte)[] input...) 215 { 216 digest.put(input); 217 } 218 219 ubyte[] finish() 220 { 221 return (result = digest.finish()); 222 } 223 224 const(char)[] toHex() 225 { 226 //TODO: Test if endianness messes results with checksums 227 return checksum ? 228 result.toHexString!(LetterCase.lower, Order.decreasing) : 229 result.toHexString!(LetterCase.lower); 230 } 231 232 const(char)[] toBase64() 233 { 234 return Base64.encode(result); 235 } 236 237 string fullName() 238 { 239 return info.fullName; 240 } 241 242 string aliasName() 243 { 244 return info.alias_; 245 } 246 247 string tagName() 248 { 249 return info.tag; 250 } 251 } 252 253 /// 254 @system unittest 255 { 256 import std.conv : hexString; 257 258 Ddh ddh = void; 259 ddh.initiate(HashType.CRC32); 260 ddh.put(cast(ubyte[]) "abc"); 261 assert(ddh.finish() == cast(ubyte[]) hexString!"c2412435"); 262 assert(ddh.toHex() == "352441c2"); 263 264 ddh.initiate(HashType.SHA3_256); 265 ddh.put(cast(ubyte[]) "abc"); 266 assert(ddh.finish() == cast(ubyte[]) hexString!( 267 "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532")); 268 assert(ddh.toHex() == 269 "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"); 270 } 271 272 /// Read a formatted GNU tag line. 273 /// Params: 274 /// line = Full GNU formatted tag string 275 /// hash = Hexadecimal hash string (e.g., "aabbccdd") 276 /// file = File string (e.g., "nightly.iso") 277 /// Returns: True on error. 278 bool readGNULine(string line, ref const(char)[] hash, ref const(char)[] file) 279 { 280 // Tested to work with one or many spaces 281 return formattedRead(line, "%s %s", hash, file) != 2; 282 } 283 284 unittest 285 { 286 string line = "f6067df486cbdbb0aac026b799b26261c92734a3 LICENSE"; 287 const(char)[] hash, file; 288 assert(readGNULine(line, hash, file) == false); 289 assert(hash == "f6067df486cbdbb0aac026b799b26261c92734a3"); 290 assert(file == "LICENSE"); 291 } 292 293 /// Read a formatted BSD tag line. 294 /// Params: 295 /// line = Full BSD formatted tag string 296 /// type = Hash type string (e.g., "SHA256") 297 /// file = File string (e.g., "nightly.iso") 298 /// hash = Hexadecimal hash string (e.g., "8080aabb") 299 /// Returns: True on error. 300 bool readBSDLine(string line, 301 ref const(char)[] type, ref const(char)[] file, ref const(char)[] hash) 302 { 303 // Tested to work with and without spaces 304 return formattedRead(line, "%s (%s) = %s", type, file, hash) != 3; 305 } 306 307 unittest 308 { 309 string line = 310 "SHA256 (Fedora-Workstation-Live-x86_64-36-1.5.iso) = " ~ 311 "80169891cb10c679cdc31dc035dab9aae3e874395adc5229f0fe5cfcc111cc8c"; 312 const(char)[] type, file, hash; 313 assert(readBSDLine(line, type, file, hash) == false); 314 assert(type == "SHA256"); 315 assert(file == "Fedora-Workstation-Live-x86_64-36-1.5.iso"); 316 assert(hash == "80169891cb10c679cdc31dc035dab9aae3e874395adc5229f0fe5cfcc111cc8c"); 317 } 318 319 /// Read a formatted SRI tag line. 320 /// Params: 321 /// line = Full RSI formatted tag string 322 /// type = Hash type string (e.g., "md5") 323 /// hash = Base64 hash string (e.g., "OFPip4okcUW0qhZmdzb23g==") 324 bool readSRILine(string line, ref const(char)[] type, ref const(char)[] hash) 325 { 326 return formattedRead(line, "%s-%s", type, hash) != 2; 327 } 328 329 unittest 330 { 331 string line = "sha1-9gZ99IbL27CqwCa3mbJiYcknNKM="; 332 const(char)[] type, hash; 333 assert(readSRILine(line, type, hash) == false); 334 assert(type == "sha1"); 335 assert(hash == "9gZ99IbL27CqwCa3mbJiYcknNKM="); 336 } 337 338 /+bool readPGPMessage(string line) 339 { 340 /* PGP Message example: 341 -----BEGIN PGP SIGNED MESSAGE----- 342 Hash: SHA256 343 344 # Fedora-Workstation-Live-x86_64-36-1.5.iso: 2018148352 bytes 345 SHA256 (Fedora-Workstation-Live-x86_64-36-1.5.iso) = 80169891cb10c679cdc31dc035dab9aae3e874395adc5229f0fe5cfcc111cc8c 346 -----BEGIN PGP SIGNATURE----- 347 ...*/ 348 }+/ 349 350 /// Guess hash type by extension name. 351 /// Params: path = Path, filename will be extract from this. 352 /// Returns: Hash type. 353 HashType guessHash(const(char)[] path) @safe 354 { 355 import std.string : toLower, indexOf; 356 import std.path : extension, baseName, globMatch, CaseSensitive; 357 import std.algorithm.searching : canFind, startsWith; 358 359 const(char)[] name = baseName(path).toLower; 360 361 foreach (info; hashInfo) 362 { 363 if (indexOf(name, info.alias_) >= 0) 364 return info.type; 365 } 366 367 // aliases 368 struct Alias 369 { 370 string name; 371 HashType type; 372 } 373 static immutable Alias[] aliases = [ 374 { "sha3", HashType.SHA3_256 } 375 ]; 376 foreach (alias_; aliases) 377 { 378 if (indexOf(name, alias_.name) >= 0) 379 return alias_.type; 380 } 381 382 return InvalidHash; 383 } 384 385 @safe unittest 386 { 387 assert(guessHash("sha1sum") == HashType.SHA1); 388 assert(guessHash(".SHA256SUM") == HashType.SHA256); 389 assert(guessHash("GE-Proton7-38.sha512sum") == HashType.SHA512); 390 assert(guessHash("CHECKSUM.SHA512-FreeBSD-13.1-RELEASE-amd64") == HashType.SHA512); 391 assert(guessHash("test.crc32") == HashType.CRC32); 392 assert(guessHash("test.sha256") == HashType.SHA256); 393 assert(guessHash("test.md5sum") == HashType.MD5); 394 assert(guessHash("test.sha3sums") == HashType.SHA3_256); 395 } 396 397 // Check by context 398 // This can be a GNU list, BSD list, or PGP signed message with tag 399 /*HashType guessHashFile(string content) @safe 400 { 401 402 403 } 404 405 @safe unittest 406 { 407 }*/