1 module secured.aes;
2 
3 import deimos.openssl.evp;
4 
5 import secured.mac;
6 import secured.random;
7 import secured.util;
8 
9 @trusted public ubyte[] encrypt (ubyte[] key, ubyte[] data)
10 in
11 {
12     assert(key.length == 32, "Encryption key must be 32 bytes in length.");
13 }
14 body
15 {
16     ubyte[] output = new ubyte[data.length];
17 
18     //Generate a random IV
19     ubyte[] iv = random(16);
20 
21     //Get the OpenSSL cipher context
22     EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
23     if (ctx is null)
24         throw new CryptographicException("Cannot get an OpenSSL cipher context.");
25     scope(exit)
26         if (ctx !is null)
27             EVP_CIPHER_CTX_free(ctx);
28 
29     //Initialize the cipher context
30     if (EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), null, key.ptr, iv.ptr) != 1)
31         throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
32 
33     //Write data to the cipher context
34     int written = 0;
35     int len = 0;
36     if (EVP_EncryptUpdate(ctx, &output[written], &len, data.ptr, cast(int)data.length) != 1)
37         throw new CryptographicException("Unable to write bytes to cipher context.");
38     written += len;
39 
40     //Extract the complete ciphertext
41     if (EVP_EncryptFinal_ex(ctx, &output[written-1], &len) != 1)
42         throw new CryptographicException("Unable to extract the ciphertext from the cipher context.");
43     written += len;
44 
45     //HMAC the combined cipher text
46     ubyte[] hashdata = iv ~ output;
47     ubyte[] hash = hmac(key, hashdata);
48 
49     //Return the HMAC + IV + Ciphertext as a single byte array.
50     return hash ~ iv ~ output;
51 }
52 
53 @trusted public bool validate (ubyte[] key, ubyte[] data)
54 in
55 {
56     assert(key.length == 32, "Encryption key must be 32 bytes in length.");
57 }
58 body
59 {
60     ubyte[] datahash = data[0..48];
61     ubyte[] computed = hmac(key, data[48..$]);
62 
63     return constantTimeEquality(datahash, computed);
64 }
65 
66 @trusted public ubyte[] decrypt (ubyte[] key, ubyte[] data)
67 in
68 {
69     assert(key.length == 32, "Encryption key must be 32 bytes in length.");
70 }
71 body
72 {
73     //Validate the data
74     if (!validate(key, data))
75         throw new CryptographicException("Cannot get an OpenSSL cipher context.");
76 
77     ubyte[] iv = data[48..64];
78     ubyte[] payload = data[64..$];
79     ubyte[] output = new ubyte[payload.length];
80 
81     //Get the OpenSSL cipher context
82     EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
83     if (ctx is null)
84         throw new CryptographicException("Cannot get an OpenSSL cipher context.");
85     scope(exit)
86         EVP_CIPHER_CTX_free(ctx);
87 
88     //Initialize the cipher context
89     if (EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), null, key.ptr, iv.ptr) != 1)
90         throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
91 
92     //Write data to the cipher context
93     int written = 0;
94     int len = 0;
95     if (EVP_DecryptUpdate(ctx, &output[written], &len, payload.ptr, cast(int)payload.length) != 1)
96         throw new CryptographicException("Unable to write bytes to cipher context.");
97     written += len;
98 
99     //Extract the complete plaintext
100     if (EVP_DecryptFinal_ex(ctx, &output[written-1], &len) != 1)
101         throw new CryptographicException("Unable to extract the plaintext from the cipher context.");
102     written += len;
103 
104     return output;
105 }
106 
107 unittest
108 {
109     import std.digest;
110     import std.stdio;
111 
112     writeln("Testing Encryption (No Additional Data)");
113 
114     ubyte[32] key = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
115                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
116 
117     string input = "The quick brown fox jumps over the lazy dog.";
118     writeln("Encryption Input: ", input);
119     ubyte[] enc = encrypt(key, cast(ubyte[])input);
120     writeln("Encryption Output: ", toHexString!(LetterCase.lower)(enc));
121 
122     write("Testing Validation (No Additional Data): ");
123     assert(validate(key, enc));
124     writeln("Success!");
125 
126     writeln("Testing Decryption (No Additional Data)");
127     ubyte[] dec = decrypt(key, enc);
128     writeln("Decryption Input: ", toHexString!(LetterCase.lower)(enc));
129     writeln("Decryption Output: ", cast(string)dec);
130 
131     assert((cast(string)dec) == input);
132 }