1 module ddata.crypto.decrypt;
2 
3 import std.format: format;
4 import std.conv: to;
5 
6 import deimos.openssl.evp;
7 
8 import ddata.common;
9 import ddata.crypto.algorithm;
10 
11 /**
12     Decrypts the data using a key with the specified algorithm
13 
14     See_Also:
15         `ddata.crypto.encrypt`
16 */
17 public string decrypt(const ubyte[] data, const string key, Algorithm algorithm = Algorithm.aes128) @safe {
18     final switch (algorithm) {
19     case Algorithm.aes128:
20         return decrypt(data, cast(const ubyte[])key, () @trusted { return EVP_aes_128_cbc(); }() ).idup;
21     }
22 }
23 
24 /// Ditto
25 public string decrypt(const string data, const string key, Algorithm algorithm = Algorithm.aes128) @safe {
26     final switch (algorithm) {
27     case Algorithm.aes128:
28         return decrypt(cast(immutable ubyte[])data, cast(const ubyte[])key, () @trusted { return EVP_aes_128_cbc(); }() ).idup;
29     }
30 }
31 
32 private string decrypt(const ubyte[] data, const ubyte[] key, const(EVP_CIPHER)* cipher) @trusted {
33     const iv = data[0 .. 16];
34     const payload = data[16 .. $];
35 
36     auto ctx = EVP_CIPHER_CTX_new();
37     if (ctx is null) {
38         throw new Exception("Failed to create EVP cipher context - %s".format(getLastError));
39     }
40     scope(exit) EVP_CIPHER_CTX_free(ctx);
41 
42     if (!EVP_DecryptInit(ctx, cipher, key.ptr, cast(const(ubyte)*)iv.ptr)) {
43         throw new Exception("Failed to initialize evp context - %s".format(getLastError));
44     }
45 
46     ubyte[] buffer = new ubyte[payload.length];
47     int updateLength = void;
48     if (!EVP_DecryptUpdate(ctx, buffer.ptr, &updateLength, payload.ptr, cast(int)payload.length)) {
49         throw new Exception("Failed to update evp context - %s".format(getLastError));
50     }
51 
52     int finalLength = void;
53     if (!EVP_DecryptFinal(ctx, &buffer.ptr[updateLength], &finalLength)) {
54         throw new Exception("Failed to finalize evp context - %s".format(getLastError));
55     }
56 
57     return cast(string)buffer[0 .. updateLength + finalLength];
58 }
59 
60 @("decryption should handle failure gracefully")
61 unittest {
62     import std.exception: collectExceptionMsg;
63     import std.algorithm: canFind;
64     import ddata.crypto: encrypt;
65 
66     auto msg = (cast(ubyte[])"76d3beec63f0dc9204c3a102d2d3db86200d57cf")
67         .decrypt("some-key", Algorithm.aes128)
68         .collectExceptionMsg;
69     assert(msg.canFind("Failed to finalize evp contex"));
70 
71     const password = "some random password";
72     const message = "this is a call to all you people how is this even happening";
73     const encryptedMessage = encrypt(message, password);
74     const decryptedMessage = decrypt(encryptedMessage, password);
75     assert(message == decryptedMessage);
76 }
77 
78 
79 @("Should be able to base64 an encryption and then decrypt")
80 unittest {
81     import std.base64: Base64;
82     import ddata.crypto: encrypt;
83     const str = "0aecba4fe377338b94746e203b4718c4cfdb7629";
84     const password = "password";
85     const encrypted = str.encrypt(password, Algorithm.aes128);
86     const base64Encoded = Base64.encode(encrypted);
87     const base64Decoded = Base64.decode(base64Encoded);
88     const decrypted = base64Decoded.decrypt(password, Algorithm.aes128);
89     assert(str == decrypted);
90 }