1 module secured.symmetric;
2 
3 import std.conv;
4 import std.outbuffer;
5 
6 import deimos.openssl.evp;
7 
8 import secured.hash;
9 import secured.mac;
10 import secured.kdf;
11 import secured.random;
12 import secured.util;
13 import secured.openssl;
14 
15 public enum CryptoHeaderVersion = 1;
16 public enum uint defaultKdfIterations = 1_048_576;
17 public enum ushort defaultSCryptR = 8;
18 public enum ushort defaultSCryptP = 1;
19 public enum uint defaultChunkSize = 268_435_456;
20 public enum ulong maxSCryptMemory = 4_294_967_296;
21 
22 public enum KdfAlgorithm : ubyte {
23     None,
24     PBKDF2,
25     SCrypt,
26     PBKDF2_HKDF,
27     SCrypt_HKDF,
28     Default = SCrypt_HKDF,
29 }
30 
31 public enum SymmetricAlgorithm : ubyte {
32     AES128_GCM,
33     AES128_CTR,
34     AES128_OFB,
35     AES128_CFB,
36     AES128_CBC,
37     AES192_GCM,
38     AES192_CTR,
39     AES192_OFB,
40     AES192_CFB,
41     AES192_CBC,
42     AES256_GCM,
43     AES256_CTR,
44     AES256_OFB,
45     AES256_CFB,
46     AES256_CBC,
47     ChaCha20,
48     ChaCha20_Poly1305,
49     Default = AES256_GCM,
50 }
51 
52 @safe private ushort getHeaderSize(ubyte headerVersion) {
53     if(headerVersion == 1) {
54         return 28;
55     } else {
56         throw new CryptographicException("Unsupported cryptographic header version.");
57     }
58 }
59 
60 public immutable struct CryptoBlockHeader {
61     public immutable ubyte headerVersion;           // The version of the block header
62     public immutable SymmetricAlgorithm symmetric;  // The Symmetric algorithm
63     public immutable HashAlgorithm hash;            // The Hash algorithm
64     public immutable KdfAlgorithm kdf;              // The KDF algorithm
65     public immutable uint kdfIterations;            // The number of KDF iterations
66     public immutable ushort scryptMemory;           // The amount SCrypt memory to use
67     public immutable ushort scryptParallelism;      // The SCrypt parallelism
68 
69     public immutable uint sectionCount;             // The total number of sections
70     public immutable uint sectionNumber;            // The current section number
71 
72     public immutable uint additionalLength;         // The length of the additional data
73     public immutable uint encryptedLength;          // The length of the encrypted data
74 
75     @safe public this(SymmetricAlgorithm symAlg, HashAlgorithm hashAlg, KdfAlgorithm kdfAlg, 
76         uint iterations, ushort memory, ushort parallelism,
77         uint sectionCount, uint sectionNumber, uint additionalLength, uint encryptedLength)
78     {
79         this.headerVersion = 1;
80         this.symmetric = symAlg;
81         this.hash = hashAlg;
82         this.kdf = kdfAlg;
83         this.kdfIterations = iterations;
84         this.scryptMemory = memory;
85         this.scryptParallelism = parallelism;
86         this.sectionCount = sectionCount;
87         this.sectionNumber = sectionNumber;
88         this.additionalLength = additionalLength;
89         this.encryptedLength = encryptedLength;
90     }
91 
92     @trusted public this(const ubyte[] header) {
93         import std.bitmanip : read;
94         ubyte[] bytes = cast(ubyte[])header;
95 
96         this.headerVersion = cast(immutable)bytes.read!ubyte();
97         this.symmetric = cast(immutable SymmetricAlgorithm)bytes.read!ubyte();
98         this.hash = cast(immutable HashAlgorithm)bytes.read!ubyte();
99         this.kdf = cast(immutable KdfAlgorithm)bytes.read!ubyte();
100 
101         this.kdfIterations = cast(immutable)bytes.read!uint();
102         this.scryptMemory = cast(immutable)bytes.read!ushort();
103         this.scryptParallelism = cast(immutable)bytes.read!ushort();
104 
105         this.sectionCount = cast(immutable)bytes.read!uint();
106         this.sectionNumber = cast(immutable)bytes.read!uint();
107 
108         this.additionalLength = cast(immutable)bytes.read!uint();
109         this.encryptedLength = cast(immutable)bytes.read!uint();
110     }
111 
112     @safe public @property ulong getBlockLength() {
113         const uint saltLen = getHashLength(this.hash);
114         const uint ivLen = getCipherIVLength(this.symmetric);
115         const uint authLen = getAuthLength(this.symmetric, this.hash);
116         return saltLen + ivLen + authLen + additionalLength + encryptedLength;
117     }
118 
119     @safe public @property ushort getHeaderLength() {
120         return getHeaderSize(1);
121     }
122 
123     @trusted private ubyte[] toBytes() {
124         import std.bitmanip : write;
125         ubyte[] header = new ubyte[28];
126         
127         header[0] = this.headerVersion;
128         header[1] = this.symmetric;
129         header[2] = this.hash;
130         header[3] = this.kdf;
131 
132         header.write(this.kdfIterations, 4);
133         header.write(this.scryptMemory, 8);
134         header.write(this.scryptParallelism, 10);
135 
136         header.write(this.sectionCount, 12);
137         header.write(this.sectionNumber, 16);
138 
139         header.write(this.additionalLength, 20);
140         header.write(this.encryptedLength, 24);
141 
142         return header;
143     }
144 }
145 
146 private immutable struct CryptoBlock {
147     public immutable CryptoBlockHeader header;
148     public immutable ubyte[] salt;
149     public immutable ubyte[] iv;
150     public immutable ubyte[] auth;
151     public immutable ubyte[] additional;
152     public immutable ubyte[] encrypted;
153 
154     @trusted public this(SymmetricAlgorithm symAlg, HashAlgorithm hashAlg, KdfAlgorithm kdfAlg,
155         uint iterations, ushort memory, ushort parallelism, uint sectionCount, uint sectionNumber,
156         const ubyte[] salt, const ubyte[] iv, const ubyte[] auth, const ubyte[] additional, const ubyte[] encrypted)
157     {
158         this.header = CryptoBlockHeader(symAlg, hashAlg, kdfAlg, iterations, memory, parallelism, sectionCount, sectionNumber, cast(uint)additional.length, cast(uint)encrypted.length);
159 
160         this.salt = cast(immutable)salt;
161         this.iv = cast(immutable)iv;
162         this.auth = cast(immutable)auth;
163         this.additional = cast(immutable)additional;
164         this.encrypted = cast(immutable)encrypted;
165     }
166 
167     @trusted public this(immutable CryptoBlockHeader header, const ubyte[] data) {
168         ubyte[] bytes = cast(ubyte[])data;
169         this.header = header;
170         const uint saltLen = getHashLength(this.header.hash);
171         const uint ivLen = getCipherIVLength(this.header.symmetric);
172         const uint authLen = getAuthLength(this.header.symmetric, this.header.hash);
173 
174         this.salt = cast(immutable)bytes[0..saltLen];
175         bytes = bytes[saltLen..$];
176         this.iv = cast(immutable)bytes[0..ivLen];
177         bytes = bytes[ivLen..$];
178         this.auth = cast(immutable)bytes[0..authLen];
179         bytes = bytes[authLen..$];
180         this.additional = cast(immutable)bytes[0..header.additionalLength];
181         bytes = bytes[header.additionalLength..$];
182         this.encrypted = cast(immutable)bytes[0..header.encryptedLength];
183     }
184 
185     @trusted private ubyte[] toBytes() {
186         import std.bitmanip : write;
187 
188         OutBuffer buffer = new OutBuffer();
189         buffer.reserve(getHeaderSize(1) + this.header.getBlockLength());
190         buffer.write(this.header.toBytes());
191         buffer.write(salt);
192         buffer.write(iv);
193         buffer.write(auth);
194         buffer.write(additional);
195         buffer.write(encrypted);
196 
197         return buffer.toBytes();
198     }
199 }
200 
201 @safe public ubyte[] encrypt(const ubyte[] key, const ubyte[] data, const ubyte[] additional) {
202     return encrypt_ex(key, data, additional, defaultChunkSize, SymmetricAlgorithm.Default, KdfAlgorithm.Default, defaultKdfIterations, defaultSCryptR, defaultSCryptP, HashAlgorithm.SHA2_384);
203 }
204 
205 @safe public ubyte[] encrypt_ex(const ubyte[] key, const ubyte[] data, const ubyte[] additional, uint chunkSize, SymmetricAlgorithm symmetric, KdfAlgorithm kdf, uint n, ushort r, ushort p, HashAlgorithm hash) {
206     import std.math : floor;
207     const real tcc = data.length / chunkSize;
208     const ushort chunks = to!ushort(floor(tcc)+1);
209 
210     CryptoBlock[] blocks;
211     ulong processed = 0;
212     ulong totalSize = additional.length;
213     for(ushort i = 0; i < chunks; i++) {
214         //Prepare data for encryption
215         const ulong chunkLen = (data.length-processed) >= chunkSize ? chunkSize : (data.length-processed);
216         const ubyte[] ad = (chunkLen < chunkSize) ? additional : null;
217         const ubyte[] ep = data[processed..processed+chunkLen];
218         ubyte[] iv = random(getCipherIVLength(symmetric));
219         ubyte[] auth = null;
220 
221         //Get Derived Key
222         KdfResult derivedKey = deriveKey(key, null, symmetric, kdf, n, r, p, hash);
223 
224         //Encrypt data
225         const ubyte[] result = encrypt_ex(symmetric, derivedKey.key, iv, ep, ad, auth);
226 
227         //Authentiate data if the cipher is not an AEAD cipher
228         auth = !isAeadCipher(symmetric) ? hmac_ex(derivedKey.key, result, hash) : auth;
229 
230         //Store results
231         blocks ~= CryptoBlock(symmetric, hash, kdf, n, r, p, chunks, i, derivedKey.salt, iv, auth, ad, result);
232         totalSize += (blocks[i].header.getHeaderLength() + blocks[i].header.getBlockLength());
233         processed += chunkLen;
234     }
235 
236     //Write results to buffer
237     OutBuffer buffer = new OutBuffer();
238     buffer.reserve(totalSize);
239     foreach(CryptoBlock block; blocks) {
240         buffer.write(block.toBytes());
241     }
242 
243     return buffer.toBytes();
244 }
245 
246 @trusted public ubyte[] encrypt_ex(SymmetricAlgorithm algorithm, const ubyte[] key, const ubyte[] iv, const ubyte[] data, const ubyte[] additional, out ubyte[] auth) {
247     //Get the OpenSSL cipher context
248     EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
249     if (ctx is null) {
250         throw new CryptographicException("Cannot get an OpenSSL cipher context.");
251     }
252     scope(exit) {
253         if (ctx !is null) {
254             EVP_CIPHER_CTX_free(ctx);
255         }
256     }
257 
258     //Initialize the cipher context
259     if (EVP_EncryptInit_ex(ctx, getOpenSslCipher(algorithm), null, null, null) != 1) {
260         throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
261     }
262 
263     //Initialize the AEAD context
264     if (isAeadCipher(algorithm)) {
265         if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, cast(int)iv.length, null) != 1) {
266             throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
267         }
268     }
269 
270     //Set the Key and IV
271     if (EVP_EncryptInit_ex(ctx, null, null, key.ptr, iv.ptr) != 1) {
272         throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
273     }
274 
275     //Write the additional data to the cipher context, if any
276     if (additional !is null && isAeadCipher(algorithm)) {
277         int aadLen = 0;
278         if (EVP_EncryptUpdate(ctx, null, &aadLen, additional.ptr, cast(int)additional.length) != 1) {
279             throw new CryptographicException("Unable to write bytes to cipher context.");
280         }
281     }
282 
283     //Write data to the cipher context
284     int written = 0;
285     int len = 0;
286     ubyte[] output = new ubyte[data.length + 32];
287     if (EVP_EncryptUpdate(ctx, &output[written], &len, data.ptr, cast(int)data.length) != 1) {
288         throw new CryptographicException("Unable to write bytes to cipher context.");
289     }
290     written += len;
291 
292     //Extract the complete ciphertext
293     if (EVP_EncryptFinal_ex(ctx, &output[written-1], &len) != 1) {
294         throw new CryptographicException("Unable to extract the ciphertext from the cipher context.");
295     }
296     written += len;
297 
298     //Extract the auth tag
299     ubyte[] _auth = isAeadCipher(algorithm) ? new ubyte[getAuthLength(algorithm)] : null;
300     if (isAeadCipher(algorithm)) {
301         if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, getAuthLength(algorithm), _auth.ptr) != 1) {
302             throw new CryptographicException("Unable to extract the authentication tag from the cipher context.");
303         }
304     }
305 
306     auth = _auth;
307     return output[0..written];
308 }
309 
310 @trusted public ubyte[] decrypt(const ubyte[] key, const ubyte[] data, out ubyte[] additional) {
311     ubyte[] bytes = cast(ubyte[])data;
312     CryptoBlockHeader firstHeader = CryptoBlockHeader(bytes);
313     const uint totalSections = firstHeader.sectionCount;
314 
315     OutBuffer adBuffer = new OutBuffer();
316     OutBuffer buffer = new OutBuffer();
317     for(uint i = 0; i < totalSections; i++) {
318         //Extract block info
319         CryptoBlockHeader header = CryptoBlockHeader(bytes);
320         auto hdrLen = header.getHeaderLength();
321         auto blockLen = header.getBlockLength();
322         bytes = bytes[hdrLen..$];
323         CryptoBlock block = CryptoBlock(header, bytes[0..blockLen]);
324         bytes = bytes[blockLen..$];
325 
326         //Derive block key and verify block if required
327         KdfResult derivedKey = deriveKey(key, block.salt, block.header.symmetric, block.header.kdf, block.header.kdfIterations, block.header.scryptMemory, block.header.scryptParallelism, block.header.hash);
328         ubyte[] auth = !isAeadCipher(block.header.symmetric) ? hmac_ex(derivedKey.key, block.encrypted, block.header.hash) : null;
329         if (auth !is null && !constantTimeEquality(block.auth, auth)) {
330             throw new CryptographicException("Block authentication failed!");
331         }
332 
333         //Decrypt block
334         ubyte[] result = decrypt_ex(derivedKey.key, block.iv, block.encrypted, block.auth, block.additional, block.header.symmetric);
335 
336         //Write results to buffers
337         adBuffer.write(cast(ubyte[])block.additional);
338         buffer.write(result);
339     }
340 
341     additional = adBuffer.toBytes();
342     return buffer.toBytes();
343 }
344 
345 @trusted public ubyte[] decrypt_ex(const ubyte[] key, const ubyte[] iv, const ubyte[] data, const ubyte[] auth, const ubyte[] additional, SymmetricAlgorithm algorithm) {
346     //Get the OpenSSL cipher context
347     EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
348     if (ctx is null) {
349         throw new CryptographicException("Cannot get an OpenSSL cipher context.");
350     }
351     scope(exit) {
352         if (ctx !is null) {
353             EVP_CIPHER_CTX_free(ctx);
354         }
355     }
356 
357     //Initialize the cipher context
358     if (!EVP_DecryptInit_ex(ctx, getOpenSslCipher(algorithm), null, null, null)) {
359         throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
360     }
361 
362     //Initialize the AEAD context
363     if (isAeadCipher(algorithm)) {
364         if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, cast(int)iv.length, null)) {
365             throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
366         }
367     }
368 
369     //Set the Key and IV
370     if (!EVP_DecryptInit_ex(ctx, null, null, key.ptr, iv.ptr)) {
371         throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
372     }
373 
374     //Write the additional data to the cipher context, if any
375     if (additional.length != 0 && isAeadCipher(algorithm)) {
376         int aadLen = 0;
377         if (!EVP_DecryptUpdate(ctx, null, &aadLen, additional.ptr, cast(int)additional.length)) {
378             throw new CryptographicException("Unable to write bytes to cipher context.");
379         }
380     }
381 
382     //Write data to the cipher context
383     int written = 0;
384     int len = 0;
385     ubyte[] output = new ubyte[data.length];
386     if (!EVP_DecryptUpdate(ctx, &output[written], &len, data.ptr, cast(int)data.length)) {
387         throw new CryptographicException("Unable to write bytes to cipher context.");
388     }
389     written += len;
390 
391     //Use the supplied tag to verify the message
392     if (isAeadCipher(algorithm)) {
393         if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, cast(int)auth.length, (cast(ubyte[])auth).ptr)) {
394             throw new CryptographicException("Unable to extract the authentication tag from the cipher context.");
395         }
396     }
397 
398     //Extract the complete plaintext
399     if (EVP_DecryptFinal_ex(ctx, &output[written-1], &len) <= 0) {
400         throw new CryptographicException("Unable to extract the plaintext from the cipher context.");
401     }
402     written += len;
403 
404     return output[0..written];
405 }
406 
407 @trusted private KdfResult deriveKey(const ubyte[] key, const ubyte[] salt, SymmetricAlgorithm symmetric, KdfAlgorithm kdf, uint n, ushort r, ushort p, HashAlgorithm hash) {
408     ubyte[] derivedKey;
409     ubyte[] _salt = salt is null ? random(getHashLength(hash)) : cast(ubyte[])salt;
410 
411     if (kdf == KdfAlgorithm.PBKDF2) {
412         derivedKey = pbkdf2_ex(to!string(key), _salt, hash, getCipherKeyLength(symmetric), n);
413     }
414     if (kdf == KdfAlgorithm.PBKDF2_HKDF) {
415         derivedKey = pbkdf2_ex(to!string(key), _salt, hash, getCipherKeyLength(symmetric), n);
416         derivedKey = hkdf_ex(derivedKey, _salt, string.init, getCipherKeyLength(symmetric), hash);
417     }
418     if (kdf == KdfAlgorithm.SCrypt) {
419         derivedKey = scrypt_ex(key, _salt, n, r, p, maxSCryptMemory, getCipherKeyLength(symmetric));
420     }
421     if (kdf == KdfAlgorithm.SCrypt_HKDF) {
422         derivedKey = scrypt_ex(key, _salt, n, r, p, maxSCryptMemory, getCipherKeyLength(symmetric));
423         derivedKey = hkdf_ex(derivedKey, _salt, string.init, getCipherKeyLength(symmetric), hash);
424     }
425     return KdfResult(_salt, derivedKey);
426 }
427 
428 @trusted package const(EVP_CIPHER*) getOpenSslCipher(SymmetricAlgorithm algo) {
429     switch(algo) {
430         case SymmetricAlgorithm.AES128_GCM: return EVP_aes_128_gcm();
431         case SymmetricAlgorithm.AES192_GCM: return EVP_aes_192_gcm();
432         case SymmetricAlgorithm.AES256_GCM: return EVP_aes_256_gcm();
433         case SymmetricAlgorithm.AES128_CTR: return EVP_aes_128_ctr();
434         case SymmetricAlgorithm.AES192_CTR: return EVP_aes_192_ctr();
435         case SymmetricAlgorithm.AES256_CTR: return EVP_aes_256_ctr();
436         case SymmetricAlgorithm.AES128_CFB: return EVP_aes_128_cfb();
437         case SymmetricAlgorithm.AES192_CFB: return EVP_aes_192_cfb();
438         case SymmetricAlgorithm.AES256_CFB: return EVP_aes_256_cfb();
439         case SymmetricAlgorithm.AES128_OFB: return EVP_aes_128_ofb();
440         case SymmetricAlgorithm.AES192_OFB: return EVP_aes_192_ofb();
441         case SymmetricAlgorithm.AES256_OFB: return EVP_aes_256_ofb();
442         case SymmetricAlgorithm.AES128_CBC: return EVP_aes_128_cbc();
443         case SymmetricAlgorithm.AES192_CBC: return EVP_aes_192_cbc();
444         case SymmetricAlgorithm.AES256_CBC: return EVP_aes_256_cbc();
445         case SymmetricAlgorithm.ChaCha20: return EVP_chacha20();
446         case SymmetricAlgorithm.ChaCha20_Poly1305: return EVP_chacha20_poly1305();
447         default: return EVP_aes_256_gcm();
448     }
449 }
450 
451 @safe package bool isAeadCipher(SymmetricAlgorithm algo) {
452     switch(algo) {
453         case SymmetricAlgorithm.AES128_GCM: return true;
454         case SymmetricAlgorithm.AES192_GCM: return true;
455         case SymmetricAlgorithm.AES256_GCM: return true;
456         case SymmetricAlgorithm.ChaCha20_Poly1305: return true;
457         default: return false;
458     }
459 }
460 
461 @safe package uint getCipherKeyLength(SymmetricAlgorithm algo) {
462     switch(algo) {
463         case SymmetricAlgorithm.AES128_GCM: return 16;
464         case SymmetricAlgorithm.AES192_GCM: return 24;
465         case SymmetricAlgorithm.AES256_GCM: return 32;
466         case SymmetricAlgorithm.AES128_CTR: return 16;
467         case SymmetricAlgorithm.AES192_CTR: return 24;
468         case SymmetricAlgorithm.AES256_CTR: return 32;
469         case SymmetricAlgorithm.AES128_CFB: return 16;
470         case SymmetricAlgorithm.AES192_CFB: return 24;
471         case SymmetricAlgorithm.AES256_CFB: return 32;
472         case SymmetricAlgorithm.AES128_OFB: return 16;
473         case SymmetricAlgorithm.AES192_OFB: return 24;
474         case SymmetricAlgorithm.AES256_OFB: return 32;
475         case SymmetricAlgorithm.AES128_CBC: return 16;
476         case SymmetricAlgorithm.AES192_CBC: return 24;
477         case SymmetricAlgorithm.AES256_CBC: return 32;
478         case SymmetricAlgorithm.ChaCha20: return 32;
479         case SymmetricAlgorithm.ChaCha20_Poly1305: return 32;
480         default: return 16;
481     }
482 }
483 
484 @safe package uint getCipherIVLength(SymmetricAlgorithm algo) {
485     switch(algo) {
486         case SymmetricAlgorithm.AES128_GCM: return 12;
487         case SymmetricAlgorithm.AES192_GCM: return 12;
488         case SymmetricAlgorithm.AES256_GCM: return 12;
489         case SymmetricAlgorithm.ChaCha20: return 12;
490         case SymmetricAlgorithm.ChaCha20_Poly1305: return 12;
491         default: return 16;
492     }
493 }
494 
495 @safe package uint getAuthLength(SymmetricAlgorithm symmetric, HashAlgorithm hash = HashAlgorithm.None) {
496     switch(symmetric) {
497         case SymmetricAlgorithm.AES128_GCM: return 16;
498         case SymmetricAlgorithm.AES192_GCM: return 16;
499         case SymmetricAlgorithm.AES256_GCM: return 16;
500         case SymmetricAlgorithm.ChaCha20_Poly1305: return 16;
501         default: return getHashLength(hash);
502     }
503 }
504 
505 unittest
506 {
507     import std.digest;
508     import std.stdio;
509     immutable string input = "The quick brown fox jumps over the lazy dog.";
510     immutable ubyte[32] key = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
511                                 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
512 
513     writeln("Testing Encryption (No Additional Data)");
514     ubyte[] enc = encrypt(key, cast(ubyte[])input, null);
515     writeln("Encryption Input: ", input);
516     writeln("Encryption Output: ", toHexString!(LetterCase.lower)(enc));
517 
518     writeln("Testing Decryption (No Additional Data)");
519     ubyte[] ad = null;
520     ubyte[] dec = decrypt(key, enc, ad);
521     writeln("Decryption Input: ", toHexString!(LetterCase.lower)(enc));
522     writeln("Decryption Output: ", cast(string)dec);
523 
524     assert(ad.length == 0);
525     assert((cast(string)dec) == input);
526 
527     writeln("Testing Encryption (With Additional Data)");
528     enc = encrypt(key, cast(ubyte[])input, cast(ubyte[])input);
529     writeln("Encryption Input: ", input);
530     writeln("Encryption AD: ", input);
531     writeln("Encryption Output: ", toHexString!(LetterCase.lower)(enc));
532 
533     writeln("Testing Decryption (With Additional Data)");
534     dec = decrypt(key, enc, ad);
535     writeln("Decryption Input: ", toHexString!(LetterCase.lower)(enc));
536     writeln("Decryption AD: ", cast(string)ad);
537     writeln("Decryption Output: ", cast(string)dec);
538 
539     assert((cast(string)ad) == input);
540     assert((cast(string)dec) == input);
541 }