1 /* This file is part of the Amalthea library.
2  *
3  * Copyright (C) 2018-2021, 2024 Eugene 'Vindex' Stulin
4  *
5  * Distributed under the Boost Software License 1.0 or (at your option)
6  * the GNU Lesser General Public License 3.0 or later.
7  */
8 
9 module amalthea.crypto;
10 
11 import std.traits : isSomeChar;
12 
13 public import amalthea.libcore;
14 
15 import std.digest.md, std.range, std.string;
16 
17 
18 /*******************************************************************************
19  * The function computes MD5 digest for the file
20  * (with the dmd compiler it works extremely inefficiently).
21  */
22 string getFileMD5Sum(string filepath) {
23     auto file = File(filepath, "r");
24     auto md5 = new MD5Digest();
25     foreach(buffer; file.byChunk(1024)) {
26         md5.put(buffer);
27     }
28     auto hash = md5.finish();
29     return hash.toHexString.toLower;
30 }
31 
32 
33 /*******************************************************************************
34  * The function computes Streebog hash sum for the file.
35  */
36 string getFileStreebogSum(alias bits)(string filepath)
37 if (bits == 256 || bits == 512) {
38     int chunkSize = 64*16;
39     auto streebogObj = new Streebog(bits);
40     auto file = File(filepath, "r");
41     long filesize = getSize(filepath);
42     while(filesize > 0) {
43         size_t portionSize;
44         if (filesize < chunkSize) {
45             portionSize = cast(size_t)filesize;
46             filesize = 0;
47         } else {
48             portionSize = chunkSize;
49             filesize -= chunkSize;
50         }
51         file.seek(filesize, SEEK_SET);
52         ubyte[] buffer = file.rawRead(new ubyte[portionSize]);
53         streebogObj.pushFront(buffer);
54     }
55     auto hash = streebogObj.finish();
56     return hash.toHexString.toLower;
57 }
58 
59 
60 /*******************************************************************************
61  * 256-bit version of file Streebog hash sum.
62  */
63 string getFileStreebog256Sum(string filepath) {
64     return getFileStreebogSum!256(filepath);
65 }
66 
67 
68 /*******************************************************************************
69  * 512-bit version of file Streebog hash sum.
70  */
71 string getFileStreebog512Sum(string filepath) {
72     return getFileStreebogSum!512(filepath);
73 }
74 
75 
76 unittest {
77     ubyte[68] bytes = 0x25;
78     bytes[0 .. 4] = [0xA1, 0x77, 0x19, 0xFC];
79     string experimentalFile = "build/test.bin";
80     std.file.write(experimentalFile, bytes);
81     string expected = Streebog.calcHash!512(bytes).toHexString.toLower;
82     assert(getFileStreebogSum!512(experimentalFile) == expected);
83     expected = Streebog.calcHash!256(bytes).toHexString.toLower;
84     assert(getFileStreebogSum!256(experimentalFile) == expected);
85     std.file.remove(experimentalFile);
86 }
87 
88 
89 /*******************************************************************************
90  * Auxiliary function of the remainder of the division
91  * for the Streebog algorithm.
92  */
93 ubyte[64] modulo512(in ubyte[64] a, in ubyte[64] b) {
94     auto t = 0;
95     ubyte[64] c;
96     for(int i = 63; i >= 0; --i) {
97         t = a[i] + b[i] + (t >> 8);
98         c[i] = t & 0xFF;
99     }
100     return c;
101 }
102 
103 
104 /*******************************************************************************
105  * Auxiliary XOR-function for the Streebog algorithm.
106  */
107 ubyte[64] xor512(in ubyte[64] a, in ubyte[64] b) {
108     ubyte[64] c;
109     foreach(i; 0 .. 64) {
110         c[i] = a[i] ^ b[i];
111     }
112     return c;
113 }
114 
115 
116 /*******************************************************************************
117  * The Streebog class implements GOST R 34.11-2012
118  * for calculating a hash value.
119  */
120 class Streebog {
121     size_t bitMode;
122     this() {
123         bitMode = 512;
124     }
125     this(size_t bitMode) {
126         enforce(bitMode == 256 || bitMode == 512);
127         this.bitMode = bitMode;
128     }
129 
130     /***************************************************************************
131      * The static template function takes an array of bytes
132      * and returns a hash sum.
133      */
134     static ubyte[] calcHash(alias bits)(in ubyte[] msg)
135     if (bits == 512 || bits == 256) {
136         ubyte[] message = msg.dup;
137         ubyte[64] v512;
138         v512[$-2] = 0x02;
139         ubyte[64] v0;
140         ubyte[64] sigma;
141         ubyte[64] N;
142         ubyte[64] m;
143         ubyte[64] hash = bits == 512 ? IV512 : IV256;
144         size_t len = message.length*8;
145         while (len >= 512) {
146             m = message[$-64 .. $];
147             hash = g(N, hash, m);
148             N = modulo512(N, v512);
149             sigma = modulo512(sigma, m);
150             len -= 512;
151             message = message[0 .. $-64];
152         }
153         m[] = 0;
154         size_t l = len/8 + 1 - ((len & 0x7) == 0);
155         size_t mshift = 63 - len/8 + ((len & 0x7) == 0);
156         m[mshift .. mshift + l] = message[0 .. l];
157 
158         m[63 - len/8] |= (1 << (len & 0x7));
159 
160         hash = g(N, hash, m);
161         v512[63] = len & 0xFF;
162         v512[62] = cast(ubyte)(len >> 8);
163         N = modulo512(N, v512);
164         sigma = modulo512(sigma, m);
165 
166         hash = g(v0, hash, N);
167         hash = g(v0, hash, sigma);
168         return bits == 256 ? hash[0 .. $/2].dup : hash.dup;
169     }
170 
171 
172     /***************************************************************************
173      * Accepts a sequence of data.
174      * Each new sequence is perceived as preceding relative to the previous one.
175      */
176     void pushFront(in ubyte[] portion) {
177         ubyte[] message = portion.dup ~ currState.residue;
178         if (currState == ObjectState.init) {
179             currState.hash = bitMode == 512 ? IV512 : IV256;
180             currState.v512[$-2] = 0x02;
181         }
182 
183         ulong bitlen = message.length * 8;
184         while (bitlen >= 512) {
185             ubyte[64] m = message[$-64 .. $];
186             message = message[0 .. $-64];
187             currState.hash = g(currState.N, currState.hash, m);
188             currState.N = modulo512(currState.N, currState.v512);
189             currState.sigma = modulo512(currState.sigma, m);
190             bitlen -= 512;
191         }
192         currState.residue = message.dup;
193     }
194 
195     /***************************************************************************
196      * Finishes the calculation of the hash sum and returns it.
197      */
198     ubyte[] finish() {
199         ubyte[64] v0;
200         ubyte[64] m;
201         size_t len = currState.residue.length * 8;
202         size_t l = len/8 + 1 - ((len & 0x7) == 0);
203         size_t mshift = 63 - len/8 + ((len & 0x7) == 0);
204         m[mshift .. mshift + l] = currState.residue[0 .. l];
205         m[63 - len/8] |= (1 << (len & 0x7));
206 
207         currState.hash = g(currState.N, currState.hash, m);
208         currState.v512[63] = len & 0xFF;
209         currState.v512[62] = cast(ubyte)(len >> 8);
210         currState.N = modulo512(currState.N, currState.v512);
211         currState.sigma = modulo512(currState.sigma, m);
212 
213         currState.hash = g(v0, currState.hash, currState.N);
214         currState.hash = g(v0, currState.hash, currState.sigma);
215         return bitMode == 256 ? currState.hash[0 .. $/2].dup
216                               : currState.hash.dup;
217     }
218 
219 protected:
220 
221     struct ObjectState {
222         ubyte[64] hash;
223         ubyte[64] N;
224         ubyte[64] sigma;
225         ubyte[] residue;
226         ubyte[64] v512;
227     }
228     ObjectState currentObjectState;
229     alias currState = currentObjectState;
230 
231 
232     static immutable ubyte[64] IV512 = 0x00;
233     static immutable ubyte[64] IV256 = 0x01;
234 
235     static ubyte[64] S(in ubyte[64] state) {
236         ubyte[64] newState = state;
237         foreach(i; 0 .. 64) {
238             newState[i] = piTable[state[i]];
239         }
240         return newState;
241     }
242 
243     static ubyte[64] P(ubyte[64] state) {
244         ubyte[64] t;
245         foreach(i; 0 .. 64) {
246             t[i] = state[tauTable[i]];
247         }
248         return t;
249     }
250 
251     static ubyte[64] L(ubyte[64] state) {
252         ubyte[64] newState = state;
253         ulong v = 0;
254         foreach(i; 0 .. 8) {
255             v = 0;
256             foreach(k; 0 .. 8) {
257                 foreach(j; 0 .. 8) {
258                     if ((newState[i*8+k] & (1 << (7-j))) != 0) {
259                         v ^= A[k*8+j];
260                     }
261                 }
262             }
263             foreach(k; 0 .. 8) {
264                 auto value = (v & (0xFFL << (7-k)*8)) >> (7-k)*8;
265                 newState[i*8+k] = cast(ubyte)value;
266             }
267         }
268         return newState;
269     }
270 
271     static ubyte[64] g(in ubyte[64] N, in ubyte[64] h, in ubyte[64] m) {
272         ubyte[64] K = xor512(h, N);
273         K = L(P(S(K)));
274         ubyte[64] t = E(K, m);
275         t = xor512(h, t);
276         ubyte[64] G = xor512(t, m);
277         return G;
278     }
279 
280     static ubyte[64] E(ubyte[64] K, ubyte[64] m) {
281         void keySchedule(size_t i) {
282             K = xor512(K, C[i]);
283             K = L(P(S(K)));
284         }
285         ubyte[64] state = xor512(K, m);
286         foreach(i; 0 .. 12) {
287             state = L(P(S(state)));
288             keySchedule(i);
289             state = xor512(state, K);
290         }
291         return state;
292     }
293 
294     static immutable ulong[64] A = [
295         0x8e20faa72ba0b470, 0x47107ddd9b505a38, 0xad08b0e0c3282d1c,
296         0xd8045870ef14980e, 0x6c022c38f90a4c07, 0x3601161cf205268d,
297         0x1b8e0b0e798c13c8, 0x83478b07b2468764, 0xa011d380818e8f40,
298         0x5086e740ce47c920, 0x2843fd2067adea10, 0x14aff010bdd87508,
299         0x0ad97808d06cb404, 0x05e23c0468365a02, 0x8c711e02341b2d01,
300         0x46b60f011a83988e, 0x90dab52a387ae76f, 0x486dd4151c3dfdb9,
301         0x24b86a840e90f0d2, 0x125c354207487869, 0x092e94218d243cba,
302         0x8a174a9ec8121e5d, 0x4585254f64090fa0, 0xaccc9ca9328a8950,
303         0x9d4df05d5f661451, 0xc0a878a0a1330aa6, 0x60543c50de970553,
304         0x302a1e286fc58ca7, 0x18150f14b9ec46dd, 0x0c84890ad27623e0,
305         0x0642ca05693b9f70, 0x0321658cba93c138, 0x86275df09ce8aaa8,
306         0x439da0784e745554, 0xafc0503c273aa42a, 0xd960281e9d1d5215,
307         0xe230140fc0802984, 0x71180a8960409a42, 0xb60c05ca30204d21,
308         0x5b068c651810a89e, 0x456c34887a3805b9, 0xac361a443d1c8cd2,
309         0x561b0d22900e4669, 0x2b838811480723ba, 0x9bcf4486248d9f5d,
310         0xc3e9224312c8c1a0, 0xeffa11af0964ee50, 0xf97d86d98a327728,
311         0xe4fa2054a80b329c, 0x727d102a548b194e, 0x39b008152acb8227,
312         0x9258048415eb419d, 0x492c024284fbaec0, 0xaa16012142f35760,
313         0x550b8e9e21f7a530, 0xa48b474f9ef5dc18, 0x70a6a56e2440598e,
314         0x3853dc371220a247, 0x1ca76e95091051ad, 0x0edd37c48a08a6d8,
315         0x07e095624504536c, 0x8d70c431ac02a736, 0xc83862965601dd1b,
316         0x641c314b2b8ee083
317     ];
318 
319     static immutable ubyte[256] piTable = [
320         0xFC, 0xEE, 0xDD, 0x11, 0xCF, 0x6E, 0x31, 0x16,
321         0xFB, 0xC4, 0xFA, 0xDA, 0x23, 0xC5, 0x04, 0x4D,
322         0xE9, 0x77, 0xF0, 0xDB, 0x93, 0x2E, 0x99, 0xBA,
323         0x17, 0x36, 0xF1, 0xBB, 0x14, 0xCD, 0x5F, 0xC1,
324         0xF9, 0x18, 0x65, 0x5A, 0xE2, 0x5C, 0xEF, 0x21,
325         0x81, 0x1C, 0x3C, 0x42, 0x8B, 0x01, 0x8E, 0x4F,
326         0x05, 0x84, 0x02, 0xAE, 0xE3, 0x6A, 0x8F, 0xA0,
327         0x06, 0x0B, 0xED, 0x98, 0x7F, 0xD4, 0xD3, 0x1F,
328 
329         0xEB, 0x34, 0x2C, 0x51, 0xEA, 0xC8, 0x48, 0xAB,
330         0xF2, 0x2A, 0x68, 0xA2, 0xFD, 0x3A, 0xCE, 0xCC,
331         0xB5, 0x70, 0x0E, 0x56, 0x08, 0x0C, 0x76, 0x12,
332         0xBF, 0x72, 0x13, 0x47, 0x9C, 0xB7, 0x5D, 0x87,
333         0x15, 0xA1, 0x96, 0x29, 0x10, 0x7B, 0x9A, 0xC7,
334         0xF3, 0x91, 0x78, 0x6F, 0x9D, 0x9E, 0xB2, 0xB1,
335         0x32, 0x75, 0x19, 0x3D, 0xFF, 0x35, 0x8A, 0x7E,
336         0x6D, 0x54, 0xC6, 0x80, 0xC3, 0xBD, 0x0D, 0x57,
337 
338         0xDF, 0xF5, 0x24, 0xA9, 0x3E, 0xA8, 0x43, 0xC9,
339         0xD7, 0x79, 0xD6, 0xF6, 0x7C, 0x22, 0xB9, 0x03,
340         0xE0, 0x0F, 0xEC, 0xDE, 0x7A, 0x94, 0xB0, 0xBC,
341         0xDC, 0xE8, 0x28, 0x50, 0x4E, 0x33, 0x0A, 0x4A,
342         0xA7, 0x97, 0x60, 0x73, 0x1E, 0x00, 0x62, 0x44,
343         0x1A, 0xB8, 0x38, 0x82, 0x64, 0x9F, 0x26, 0x41,
344         0xAD, 0x45, 0x46, 0x92, 0x27, 0x5E, 0x55, 0x2F,
345         0x8C, 0xA3, 0xA5, 0x7D, 0x69, 0xD5, 0x95, 0x3B,
346 
347         0x07, 0x58, 0xB3, 0x40, 0x86, 0xAC, 0x1D, 0xF7,
348         0x30, 0x37, 0x6B, 0xE4, 0x88, 0xD9, 0xE7, 0x89,
349         0xE1, 0x1B, 0x83, 0x49, 0x4C, 0x3F, 0xF8, 0xFE,
350         0x8D, 0x53, 0xAA, 0x90, 0xCA, 0xD8, 0x85, 0x61,
351         0x20, 0x71, 0x67, 0xA4, 0x2D, 0x2B, 0x09, 0x5B,
352         0xCB, 0x9B, 0x25, 0xD0, 0xBE, 0xE5, 0x6C, 0x52,
353         0x59, 0xA6, 0x74, 0xD2, 0xE6, 0xF4, 0xB4, 0xC0,
354         0xD1, 0x66, 0xAF, 0xC2, 0x39, 0x4B, 0x63, 0xB6
355     ];
356 
357     static immutable ubyte[64] tauTable = [
358         0,  8, 16, 24, 32, 40, 48, 56,
359         1,  9, 17, 25, 33, 41, 49, 57,
360         2, 10, 18, 26, 34, 42, 50, 58,
361         3, 11, 19, 27, 35, 43, 51, 59,
362         4, 12, 20, 28, 36, 44, 52, 60,
363         5, 13, 21, 29, 37, 45, 53, 61,
364         6, 14, 22, 30, 38, 46, 54, 62,
365         7, 15, 23, 31, 39, 47, 55, 63
366     ];
367 
368     static immutable ubyte[64][12] C = [
369         [
370             0xb1, 0x08, 0x5b, 0xda, 0x1e, 0xca, 0xda, 0xe9,
371             0xeb, 0xcb, 0x2f, 0x81, 0xc0, 0x65, 0x7c, 0x1f,
372             0x2f, 0x6a, 0x76, 0x43, 0x2e, 0x45, 0xd0, 0x16,
373             0x71, 0x4e, 0xb8, 0x8d, 0x75, 0x85, 0xc4, 0xfc,
374             0x4b, 0x7c, 0xe0, 0x91, 0x92, 0x67, 0x69, 0x01,
375             0xa2, 0x42, 0x2a, 0x08, 0xa4, 0x60, 0xd3, 0x15,
376             0x05, 0x76, 0x74, 0x36, 0xcc, 0x74, 0x4d, 0x23,
377             0xdd, 0x80, 0x65, 0x59, 0xf2, 0xa6, 0x45, 0x07
378         ],[
379             0x6f, 0xa3, 0xb5, 0x8a, 0xa9, 0x9d, 0x2f, 0x1a,
380             0x4f, 0xe3, 0x9d, 0x46, 0x0f, 0x70, 0xb5, 0xd7,
381             0xf3, 0xfe, 0xea, 0x72, 0x0a, 0x23, 0x2b, 0x98,
382             0x61, 0xd5, 0x5e, 0x0f, 0x16, 0xb5, 0x01, 0x31,
383             0x9a, 0xb5, 0x17, 0x6b, 0x12, 0xd6, 0x99, 0x58,
384             0x5c, 0xb5, 0x61, 0xc2, 0xdb, 0x0a, 0xa7, 0xca,
385             0x55, 0xdd, 0xa2, 0x1b, 0xd7, 0xcb, 0xcd, 0x56,
386             0xe6, 0x79, 0x04, 0x70, 0x21, 0xb1, 0x9b, 0xb7
387         ],[
388             0xf5, 0x74, 0xdc, 0xac, 0x2b, 0xce, 0x2f, 0xc7,
389             0x0a, 0x39, 0xfc, 0x28, 0x6a, 0x3d, 0x84, 0x35,
390             0x06, 0xf1, 0x5e, 0x5f, 0x52, 0x9c, 0x1f, 0x8b,
391             0xf2, 0xea, 0x75, 0x14, 0xb1, 0x29, 0x7b, 0x7b,
392             0xd3, 0xe2, 0x0f, 0xe4, 0x90, 0x35, 0x9e, 0xb1,
393             0xc1, 0xc9, 0x3a, 0x37, 0x60, 0x62, 0xdb, 0x09,
394             0xc2, 0xb6, 0xf4, 0x43, 0x86, 0x7a, 0xdb, 0x31,
395             0x99, 0x1e, 0x96, 0xf5, 0x0a, 0xba, 0x0a, 0xb2
396         ],[
397             0xef, 0x1f, 0xdf, 0xb3, 0xe8, 0x15, 0x66, 0xd2,
398             0xf9, 0x48, 0xe1, 0xa0, 0x5d, 0x71, 0xe4, 0xdd,
399             0x48, 0x8e, 0x85, 0x7e, 0x33, 0x5c, 0x3c, 0x7d,
400             0x9d, 0x72, 0x1c, 0xad, 0x68, 0x5e, 0x35, 0x3f,
401             0xa9, 0xd7, 0x2c, 0x82, 0xed, 0x03, 0xd6, 0x75,
402             0xd8, 0xb7, 0x13, 0x33, 0x93, 0x52, 0x03, 0xbe,
403             0x34, 0x53, 0xea, 0xa1, 0x93, 0xe8, 0x37, 0xf1,
404             0x22, 0x0c, 0xbe, 0xbc, 0x84, 0xe3, 0xd1, 0x2e
405         ],[
406             0x4b, 0xea, 0x6b, 0xac, 0xad, 0x47, 0x47, 0x99,
407             0x9a, 0x3f, 0x41, 0x0c, 0x6c, 0xa9, 0x23, 0x63,
408             0x7f, 0x15, 0x1c, 0x1f, 0x16, 0x86, 0x10, 0x4a,
409             0x35, 0x9e, 0x35, 0xd7, 0x80, 0x0f, 0xff, 0xbd,
410             0xbf, 0xcd, 0x17, 0x47, 0x25, 0x3a, 0xf5, 0xa3,
411             0xdf, 0xff, 0x00, 0xb7, 0x23, 0x27, 0x1a, 0x16,
412             0x7a, 0x56, 0xa2, 0x7e, 0xa9, 0xea, 0x63, 0xf5,
413             0x60, 0x17, 0x58, 0xfd, 0x7c, 0x6c, 0xfe, 0x57
414         ],[
415             0xae, 0x4f, 0xae, 0xae, 0x1d, 0x3a, 0xd3, 0xd9,
416             0x6f, 0xa4, 0xc3, 0x3b, 0x7a, 0x30, 0x39, 0xc0,
417             0x2d, 0x66, 0xc4, 0xf9, 0x51, 0x42, 0xa4, 0x6c,
418             0x18, 0x7f, 0x9a, 0xb4, 0x9a, 0xf0, 0x8e, 0xc6,
419             0xcf, 0xfa, 0xa6, 0xb7, 0x1c, 0x9a, 0xb7, 0xb4,
420             0x0a, 0xf2, 0x1f, 0x66, 0xc2, 0xbe, 0xc6, 0xb6,
421             0xbf, 0x71, 0xc5, 0x72, 0x36, 0x90, 0x4f, 0x35,
422             0xfa, 0x68, 0x40, 0x7a, 0x46, 0x64, 0x7d, 0x6e
423         ],[
424             0xf4, 0xc7, 0x0e, 0x16, 0xee, 0xaa, 0xc5, 0xec,
425             0x51, 0xac, 0x86, 0xfe, 0xbf, 0x24, 0x09, 0x54,
426             0x39, 0x9e, 0xc6, 0xc7, 0xe6, 0xbf, 0x87, 0xc9,
427             0xd3, 0x47, 0x3e, 0x33, 0x19, 0x7a, 0x93, 0xc9,
428             0x09, 0x92, 0xab, 0xc5, 0x2d, 0x82, 0x2c, 0x37,
429             0x06, 0x47, 0x69, 0x83, 0x28, 0x4a, 0x05, 0x04,
430             0x35, 0x17, 0x45, 0x4c, 0xa2, 0x3c, 0x4a, 0xf3,
431             0x88, 0x86, 0x56, 0x4d, 0x3a, 0x14, 0xd4, 0x93
432         ],[
433             0x9b, 0x1f, 0x5b, 0x42, 0x4d, 0x93, 0xc9, 0xa7,
434             0x03, 0xe7, 0xaa, 0x02, 0x0c, 0x6e, 0x41, 0x41,
435             0x4e, 0xb7, 0xf8, 0x71, 0x9c, 0x36, 0xde, 0x1e,
436             0x89, 0xb4, 0x44, 0x3b, 0x4d, 0xdb, 0xc4, 0x9a,
437             0xf4, 0x89, 0x2b, 0xcb, 0x92, 0x9b, 0x06, 0x90,
438             0x69, 0xd1, 0x8d, 0x2b, 0xd1, 0xa5, 0xc4, 0x2f,
439             0x36, 0xac, 0xc2, 0x35, 0x59, 0x51, 0xa8, 0xd9,
440             0xa4, 0x7f, 0x0d, 0xd4, 0xbf, 0x02, 0xe7, 0x1e
441         ],[
442             0x37, 0x8f, 0x5a, 0x54, 0x16, 0x31, 0x22, 0x9b,
443             0x94, 0x4c, 0x9a, 0xd8, 0xec, 0x16, 0x5f, 0xde,
444             0x3a, 0x7d, 0x3a, 0x1b, 0x25, 0x89, 0x42, 0x24,
445             0x3c, 0xd9, 0x55, 0xb7, 0xe0, 0x0d, 0x09, 0x84,
446             0x80, 0x0a, 0x44, 0x0b, 0xdb, 0xb2, 0xce, 0xb1,
447             0x7b, 0x2b, 0x8a, 0x9a, 0xa6, 0x07, 0x9c, 0x54,
448             0x0e, 0x38, 0xdc, 0x92, 0xcb, 0x1f, 0x2a, 0x60,
449             0x72, 0x61, 0x44, 0x51, 0x83, 0x23, 0x5a, 0xdb
450         ],[
451             0xab, 0xbe, 0xde, 0xa6, 0x80, 0x05, 0x6f, 0x52,
452             0x38, 0x2a, 0xe5, 0x48, 0xb2, 0xe4, 0xf3, 0xf3,
453             0x89, 0x41, 0xe7, 0x1c, 0xff, 0x8a, 0x78, 0xdb,
454             0x1f, 0xff, 0xe1, 0x8a, 0x1b, 0x33, 0x61, 0x03,
455             0x9f, 0xe7, 0x67, 0x02, 0xaf, 0x69, 0x33, 0x4b,
456             0x7a, 0x1e, 0x6c, 0x30, 0x3b, 0x76, 0x52, 0xf4,
457             0x36, 0x98, 0xfa, 0xd1, 0x15, 0x3b, 0xb6, 0xc3,
458             0x74, 0xb4, 0xc7, 0xfb, 0x98, 0x45, 0x9c, 0xed
459         ],[
460             0x7b, 0xcd, 0x9e, 0xd0, 0xef, 0xc8, 0x89, 0xfb,
461             0x30, 0x02, 0xc6, 0xcd, 0x63, 0x5a, 0xfe, 0x94,
462             0xd8, 0xfa, 0x6b, 0xbb, 0xeb, 0xab, 0x07, 0x61,
463             0x20, 0x01, 0x80, 0x21, 0x14, 0x84, 0x66, 0x79,
464             0x8a, 0x1d, 0x71, 0xef, 0xea, 0x48, 0xb9, 0xca,
465             0xef, 0xba, 0xcd, 0x1d, 0x7d, 0x47, 0x6e, 0x98,
466             0xde, 0xa2, 0x59, 0x4a, 0xc0, 0x6f, 0xd8, 0x5d,
467             0x6b, 0xca, 0xa4, 0xcd, 0x81, 0xf3, 0x2d, 0x1b
468         ],[
469             0x37, 0x8e, 0xe7, 0x67, 0xf1, 0x16, 0x31, 0xba,
470             0xd2, 0x13, 0x80, 0xb0, 0x04, 0x49, 0xb1, 0x7a,
471             0xcd, 0xa4, 0x3c, 0x32, 0xbc, 0xdf, 0x1d, 0x77,
472             0xf8, 0x20, 0x12, 0xd4, 0x30, 0x21, 0x9f, 0x9b,
473             0x5d, 0x80, 0xef, 0x9d, 0x18, 0x91, 0xcc, 0x86,
474             0xe7, 0x1d, 0xa4, 0xaa, 0x88, 0xe1, 0x28, 0x52,
475             0xfa, 0xf4, 0x17, 0xd5, 0xd9, 0xb2, 0x1b, 0x99,
476             0x48, 0xbc, 0x92, 0x4a, 0xf1, 0x1b, 0xd7, 0x20
477         ]
478     ];
479 }
480 
481 
482 unittest {
483     ubyte[] message;
484     ubyte[] hash;
485     string expected;
486 
487     string line =
488     "ыверогИ ыкълп яырбарх ан ималертс яром с ътюев ,ицунв ижобиртС ,иртев еС";
489     import std.encoding : Windows1251String, transcode;
490     Windows1251String cp1251string;
491     transcode(line, cp1251string);
492     message = cast(ubyte[])cp1251string;
493 
494     hash = Streebog.calcHash!512(message);
495     expected = "28fbc9bada033b1460642bdcddb90c3f"
496              ~ "b3e56c497ccd0f62b8a2ad4935e85f03"
497              ~ "7613966de4ee00531ae60f3b5a47f8da"
498              ~ "e06915d5f2f194996fcabf2622e6881e";
499     assert(expected == hash.toHexString.toLower);
500 
501     hash = Streebog.calcHash!256(message);
502     expected = "508f7e553c06501d749a66fc28c6cac0"
503              ~ "b005746d97537fa85d9e40904efed29d";
504     assert(expected == hash.toHexString.toLower);
505 
506     message = [
507         0x32, 0x31, 0x30, 0x39, 0x38, 0x37, 0x36, 0x35,
508         0x34, 0x33, 0x32, 0x31, 0x30, 0x39, 0x38, 0x37,
509 		0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x39,
510         0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31,
511 		0x30, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33,
512         0x32, 0x31, 0x30, 0x39, 0x38, 0x37, 0x36, 0x35,
513 		0x34, 0x33, 0x32, 0x31, 0x30, 0x39, 0x38, 0x37,
514         0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30
515     ];
516 
517     hash = Streebog.calcHash!256(message);
518     expected = "00557be5e584fd52a449b16b0251d05d"
519              ~ "27f94ab76cbaa6da890b59d8ef1e159d";
520     assert(expected == hash.toHexString.toLower);
521 
522     hash = Streebog.calcHash!512(message);
523     expected = "486f64c1917879417fef082b3381a4e2"
524              ~ "11c324f074654c38823a7b76f830ad00"
525              ~ "fa1fbae42b1285c0352f227524bc9ab1"
526              ~ "6254288dd6863dccd5b9f54a1ad0541b";
527     assert(expected == hash.toHexString.toLower);
528 
529     auto hashObj = new Streebog(512);
530         hashObj.pushFront(message[56 .. $]);
531         hashObj.pushFront(message[48 .. 56]);
532         hashObj.pushFront(message[32 .. 48]);
533         hashObj.pushFront(message[16 .. 32]);
534         hashObj.pushFront(message[0 .. 16]);
535     assert(expected == hashObj.finish.toHexString.toLower);
536 }
537 
538 
539 //Atbash, Caeser and Vernam functions inspired
540 //by https://habr.com/ru/post/444176/
541 
542 static immutable dchar[] enAlphabetL;
543 static immutable dchar[] enAlphabetU;
544 static immutable dchar[] ruAlphabetL;
545 static immutable dchar[] ruAlphabetU;
546 shared static this() {
547     enAlphabetL  = iota(cast(dchar)'a', cast(dchar)('z'+1)).array;
548     enAlphabetU = enAlphabetL.toUpper;
549     ruAlphabetL = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"d.dup;
550     ruAlphabetU = ruAlphabetL.toUpper;
551 }
552 
553 
554 /*******************************************************************************
555  * Atbash encryption.
556  * Params:
557  *     message = Text for encryption.
558  *     alphabets = Array of alphabets to use when flipping.
559  * Returns: Encrypted message.
560  */
561 immutable(Char)[] atbash(Char)(immutable(Char)[] message,
562                                immutable(Char)[][] alphabets)
563 if (isSomeChar!Char) {
564     dchar[] result = message.dup.to!dstring.dup;
565     foreach(ref c; result) {
566         foreach(ABC; alphabets) {
567             foreach(i; 0 .. ABC.length) {
568                 if (ABC[i] == c) {
569                     c = ABC[ABC.length-i-1];
570                     break;
571                 }
572             }
573         }
574     }
575     return result.to!(immutable(Char)[]);
576 }
577 
578 
579 /*******************************************************************************
580  * A special case of Atbash encryption — for English and Russian alphabets.
581  * Params:
582  *     message = Text for encryption.
583  * Returns: Encrypted message.
584  */
585 immutable(Char)[] atbashEnRu(Char)(immutable(Char)[] message)
586 if (isSomeChar!Char) {
587     alias genstring = immutable(Char)[];
588     auto alphabets = [enAlphabetL.to!genstring, enAlphabetU.to!genstring,
589                       ruAlphabetL.to!genstring, ruAlphabetU.to!genstring];
590     return atbash(message, alphabets);
591 }
592 unittest {
593     assert(atbashEnRu("Hello, World!") == "Svool, Dliow!");
594     assert(atbashEnRu("I love habr") == "R olev szyi");
595 }
596 
597 
598 /*******************************************************************************
599  * Caesar encryption.
600  * Params:
601  *     message = Text for encryption.
602  *     alphabets = Array of alphabets for internal shifting.
603  *     shift = Numerical value of the shift.
604  * Returns: Encrypted message.
605  */
606 immutable(Char)[] caesarCypher(Char)(immutable(Char)[] message,
607                                      immutable(Char)[][] alphabets,
608                                      ssize_t shift)
609 if (isSomeChar!Char) {
610     dstring[] dAlphabets;
611     foreach(ABC; alphabets) {
612         dAlphabets ~= ABC.to!dstring;
613     }
614     dchar[][] cyphers;
615     cyphers.length = dAlphabets.length;
616     foreach(i, ABC; dAlphabets) {
617         if (shift == 0) {
618             cyphers[i] = ABC.dup;
619         } else if (shift > 0) {
620             cyphers[i] = ABC[$-shift .. $].dup ~ ABC[0 .. $-shift].dup;
621         } else {
622             cyphers[i] = ABC[-shift .. $].dup ~ ABC[0 .. -shift].dup;
623         }
624     }
625     dchar[] result = message.to!dstring.dup;
626     foreach(i, c; message) {
627         foreach(x; 0 .. dAlphabets.length) {
628             auto ABC = dAlphabets[x];
629             auto cypher = cyphers[x];
630             foreach(n; 0 .. ABC.length) {
631                 if (ABC[n] == c) {
632                     result[i] = cypher[n];
633                 }
634             }
635         }
636     }
637     return result.to!dstring.to!(immutable(Char)[]);
638 }
639 unittest {
640     assert(caesarCypher("hello world"d, [enAlphabetL], 4) == "dahhk sknhz");
641     dstring message, expected;
642     message = "Широкая электрификация южных губерний даст "d
643             ~ "мощный толчок подъёму сельского хозяйства."d;
644     expected = "Удлйёыъ шжаёнлдпдёысдъ щвицр юоьалиде яымн "d
645              ~ "зйфице нйжтйё кйяхбзо мажчмёйюй рйгъемнэы."d;
646     assert(expected == caesarCypher(message, [ruAlphabetU, ruAlphabetL], 5));
647 }
648 
649 
650 /*******************************************************************************
651  * Vernam encryption.
652  * Params:
653  *     message = Byte array for encryption.
654  *     key = Encryption key.
655  *           Its length must be equal to the length of the message.
656  * Returns: Encrypted message.
657  */
658 ubyte[] vernamByteCipher(in ubyte[] message, in ubyte[] key) {
659     assert(message.length == key.length);
660     auto len = message.length;
661     ubyte[] result = new ubyte[](len);
662     foreach(i; 0 .. len) {
663         result[i] = message[i] ^ key[i];
664     }
665     return result;
666 }
667 inout(T)[] vernamCipher(T)(inout(T)[] message, inout(T)[] key) {
668     ubyte[] byteMessage = cast(ubyte[])message.dup;
669     ubyte[] byteKey = cast(ubyte[])key.dup;
670     return cast(inout(T)[])vernamByteCipher(byteMessage, byteKey);
671 }
672 unittest {
673     auto encryptedMessage = vernamCipher("LONDON", "SYSTEM");
674     assert("LONDON" != encryptedMessage);
675     auto originalMessage = vernamCipher(encryptedMessage, "SYSTEM");
676     assert("LONDON" == originalMessage);
677 }
678