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 }