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 }