1 module secured.hash; 2 3 import std.stdio; 4 5 import secured.openssl; 6 import deimos.openssl.evp; 7 8 import secured.util; 9 10 public enum HashAlgorithm : ubyte { 11 None, 12 SHA2_224, 13 SHA2_256, 14 SHA2_384, 15 SHA2_512, 16 SHA2_512_224, 17 SHA2_512_256, 18 SHA3_224, 19 SHA3_256, 20 SHA3_384, 21 SHA3_512, 22 } 23 24 @safe public ubyte[] hash(const ubyte[] data) { 25 return hash_ex(data, HashAlgorithm.SHA2_384); 26 } 27 28 @safe public bool hash_verify(ubyte[] test, ubyte[] data) { 29 ubyte[] hash = hash_ex(data, HashAlgorithm.SHA2_384); 30 return constantTimeEquality(hash, test); 31 } 32 33 @trusted public ubyte[] hash_ex(const ubyte[] data, HashAlgorithm func) 34 { 35 //Create the OpenSSL context 36 EVP_MD_CTX *mdctx; 37 if ((mdctx = EVP_MD_CTX_new()) == null) { 38 throw new CryptographicException("Unable to create OpenSSL context."); 39 } 40 scope(exit) { 41 if(mdctx !is null) { 42 EVP_MD_CTX_free(mdctx); 43 } 44 } 45 46 //Initialize the hash algorithm 47 if (EVP_DigestInit_ex(mdctx, getOpenSSLHashAlgorithm(func), null) < 0) { 48 throw new CryptographicException("Unable to create hash context."); 49 } 50 51 //Run the provided data through the digest algorithm 52 if (EVP_DigestUpdate(mdctx, data.ptr, data.length) < 0) { 53 throw new CryptographicException("Error while updating digest."); 54 } 55 56 //Copy the OpenSSL digest to our D buffer. 57 uint digestlen; 58 ubyte[] digest = new ubyte[getHashLength(func)]; 59 if (EVP_DigestFinal_ex(mdctx, digest.ptr, &digestlen) < 0) { 60 throw new CryptographicException("Error while retrieving the digest."); 61 } 62 63 return digest; 64 } 65 66 @safe public bool hash_verify_ex(const ubyte[] test, const ubyte[] data, HashAlgorithm func) { 67 ubyte[] hash = hash_ex(data, func); 68 return constantTimeEquality(hash, test); 69 } 70 71 unittest { 72 import std.digest; 73 74 writeln("Testing Byte Array Hash:"); 75 76 ubyte[] vec1 = hash_ex(cast(ubyte[])"", HashAlgorithm.SHA2_384); 77 ubyte[] vec2 = hash_ex(cast(ubyte[])"abc", HashAlgorithm.SHA2_384); 78 ubyte[] vec3 = hash_ex(cast(ubyte[])"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", HashAlgorithm.SHA2_384); 79 ubyte[] vec4 = hash_ex(cast(ubyte[])"The quick brown fox jumps over the lazy dog.", HashAlgorithm.SHA2_384); 80 81 writeln(toHexString!(LetterCase.lower)(vec1)); 82 writeln(toHexString!(LetterCase.lower)(vec2)); 83 writeln(toHexString!(LetterCase.lower)(vec3)); 84 writeln(toHexString!(LetterCase.lower)(vec4)); 85 86 assert(toHexString!(LetterCase.lower)(vec1) == "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"); 87 assert(toHexString!(LetterCase.lower)(vec2) == "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"); 88 assert(toHexString!(LetterCase.lower)(vec3) == "3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b"); 89 assert(toHexString!(LetterCase.lower)(vec4) == "ed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7"); 90 } 91 92 93 @safe public ubyte[] hash(string path) { 94 return hash_ex(path, HashAlgorithm.SHA2_384); 95 } 96 97 @safe public bool hash_verify(string path, ubyte[] test) { 98 ubyte[] hash = hash_ex(path, HashAlgorithm.SHA2_384); 99 return constantTimeEquality(hash, test); 100 } 101 102 @trusted public ubyte[] hash_ex(string path, HashAlgorithm func) 103 { 104 //Open the file for reading 105 auto fsfile = File(path, "rb"); 106 scope(exit) { 107 if(fsfile.isOpen()) { 108 fsfile.close(); 109 } 110 } 111 112 //Create the OpenSSL context 113 EVP_MD_CTX *mdctx; 114 if ((mdctx = EVP_MD_CTX_new()) == null) { 115 throw new CryptographicException("Unable to create OpenSSL context."); 116 } 117 scope(exit) { 118 if(mdctx !is null) { 119 EVP_MD_CTX_free(mdctx); 120 } 121 } 122 123 //Initialize the hash algorithm 124 if (EVP_DigestInit_ex(mdctx, getOpenSSLHashAlgorithm(func), null) < 0) { 125 throw new CryptographicException("Unable to create hash context."); 126 } 127 128 //Read the file in chunks and update the Digest 129 foreach(ubyte[] data; fsfile.byChunk(FILE_BUFFER_SIZE)) { 130 if (EVP_DigestUpdate(mdctx, data.ptr, data.length) < 0) { 131 throw new CryptographicException("Error while updating digest."); 132 } 133 } 134 135 //Copy the OpenSSL digest to our D buffer. 136 uint digestlen; 137 ubyte[] digest = new ubyte[getHashLength(func)]; 138 if (EVP_DigestFinal_ex(mdctx, digest.ptr, &digestlen) < 0) { 139 throw new CryptographicException("Error while retrieving the digest."); 140 } 141 142 return digest; 143 } 144 145 @safe public bool hash_verify_ex(string path, HashAlgorithm func, ubyte[] test) { 146 ubyte[] hash = hash_ex(path, func); 147 return constantTimeEquality(hash, test); 148 } 149 150 unittest { 151 import std.digest; 152 153 writeln("Testing File Hash:"); 154 155 auto f = File("hashtest.txt", "wb"); 156 f.rawWrite("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); 157 f.close(); 158 159 ubyte[] vec = hash_ex("hashtest.txt", HashAlgorithm.SHA2_384); 160 writeln(toHexString!(LetterCase.lower)(vec)); 161 assert(toHexString!(LetterCase.lower)(vec) == "3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b"); 162 163 remove("hashtest.txt"); 164 } 165 166 @trusted package const(EVP_MD)* getOpenSSLHashAlgorithm(HashAlgorithm func) { 167 import std.conv; 168 import std.format; 169 170 switch (func) { 171 case HashAlgorithm.SHA2_224: return EVP_sha224(); 172 case HashAlgorithm.SHA2_256: return EVP_sha256(); 173 case HashAlgorithm.SHA2_384: return EVP_sha384(); 174 case HashAlgorithm.SHA2_512: return EVP_sha512(); 175 default: 176 throw new CryptographicException(format("Hash Function '%s' is not supported by OpenSSL.", to!string(func))); 177 } 178 } 179 180 @safe package uint getHashLength(HashAlgorithm func) { 181 import std.conv; 182 import std.format; 183 184 switch (func) { 185 case HashAlgorithm.None: return 0; 186 case HashAlgorithm.SHA2_224: return 24; 187 case HashAlgorithm.SHA2_256: return 32; 188 case HashAlgorithm.SHA2_384: return 48; 189 case HashAlgorithm.SHA2_512: return 64; 190 case HashAlgorithm.SHA2_512_224: return 24; 191 case HashAlgorithm.SHA2_512_256: return 32; 192 case HashAlgorithm.SHA3_224: return 24; 193 case HashAlgorithm.SHA3_256: return 32; 194 case HashAlgorithm.SHA3_384: return 48; 195 case HashAlgorithm.SHA3_512: return 64; 196 default: 197 throw new CryptographicException(format("Hash Function '%s'", to!string(func))); 198 } 199 }