1 module chloride.secretbox;
2 
3 import chloride.core;
4 import chloride.random : randomArray;
5 
6 import std.array;
7 
8 import deimos.sodium.crypto_secretbox;
9 
10 ///
11 unittest {
12     import std..string : representation;
13     immutable ubyte[] message = representation("hello");
14     immutable key = makeSecretKey();
15     auto box = secretBox(message, key);
16     assert(openSecretBox(box, key) == message);
17 }
18 
19 alias SecretKey = ubyte[crypto_secretbox_KEYBYTES];
20 alias Nonce = ubyte[crypto_secretbox_NONCEBYTES];
21 alias SecretMacLength = crypto_secretbox_MACBYTES;
22 
23 /**
24  * A struct containing both the encrypted cipher (using secret key encryption)
25  * and the nonce used to encrypt it.
26  */
27 struct SecretBox {
28     /// The encrypted message
29     ubyte[] ciphertext;
30     /// The nonce used to encrypt it (and needed for decryption)
31     Nonce nonce;
32 }
33 
34 /**
35  * Wrapper around `crypto_secretbox_easy`. Encrypts a message using a nonce and key
36  * and returns the cipher with MAC as a prefix.
37  */
38 ubyte[] encryptSecretBox(in ubyte[] message,
39                          in Nonce nonce,
40                          in SecretKey key) {
41     auto cipher = uninitializedArray!(ubyte[])(message.length + SecretMacLength);
42     auto result = crypto_secretbox_easy(cipher.ptr, message.ptr, message.length,
43                           nonce.ptr, key.ptr);
44     enforceSodium(result == 0);
45     return cipher;
46 }
47 
48 
49 /**
50  * Wrapper around `crypto_secretbox_open_easy`. Decrypts a cipher using a nonce and key
51  * and returns the decrypted message.
52  * If decryption fails, null is returned.
53  */
54 ubyte[] decryptSecretBox(in ubyte[] cipher,
55                          in Nonce nonce,
56                          in SecretKey key) in {
57     assert(cipher.length > SecretMacLength);
58 } body {
59     auto message = uninitializedArray!(ubyte[])(cipher.length - SecretMacLength);
60     auto result = crypto_secretbox_open_easy(message.ptr, cipher.ptr, cipher.length,
61                                              nonce.ptr, key.ptr);
62     if (result == 0) {
63         return message;
64     } else {
65         return null;
66     }
67 }
68 /**
69  * Wrapper around `crypto_secretbox_easy` that encrypts in place.
70  * `buffer` is changed in place from the message to the cipher. Note
71  * that it will be reallocated to be large enough to include the MAC, which
72  * may cause the array to be copied.
73  */
74 void encryptSecretBoxInPlace(ref ubyte[] buffer,
75                              in Nonce nonce,
76                              in SecretKey key) {
77     auto messageLen = buffer.length;
78     buffer.length += SecretMacLength; // make sure we have enough space for the MAC
79     auto result = crypto_secretbox_easy(buffer.ptr, buffer.ptr, messageLen, nonce.ptr, key.ptr);
80     enforceSodium(result == 0);
81 }
82 
83 /**
84  * Wrapper around `crypto_secretbox_open_easy` that decrypts in place.
85  * `buffer` is changed in place from the cipher to the decrypted message.
86  * If decryption is successful, true is returned and `buffer` is updated with
87  * the length of the decrypted message, which will be shorter than the cipher.
88  * Otherwise true will be returned.
89  */
90 bool decryptSecretBoxInPlace(ref ubyte[] buffer,
91                              in Nonce nonce,
92                              in SecretKey key) in {
93     assert(buffer.length > SecretMacLength);
94 } body {
95     auto result = crypto_secretbox_open_easy(buffer.ptr, buffer.ptr, buffer.length,
96                                              nonce.ptr, key.ptr);
97     if (result == 0) {
98         buffer = buffer[0 .. $ - SecretMacLength];
99         return true;
100     } else {
101         return false;
102     }
103 }
104 
105 /**
106  * Encrypt `message` using `key` and a newly generated nonce. Return a `SecreBox` with
107  * the encrypted ciphertext and the generated nonce.
108  */
109 SecretBox secretBox(in ubyte[] message, in SecretKey key) {
110     SecretBox result;
111     result.nonce = makeNonce();
112     result.ciphertext = encryptSecretBox(message, result.nonce, key);
113     return result;
114 }
115 
116 /**
117  * Decrypt `box` which contains the ciphertext and the nonce used to generate it.
118  * If decryption fails it returns null.
119  */
120 ubyte[] openSecretBox(in SecretBox box, in SecretKey key) {
121     return decryptSecretBox(box.ciphertext, box.nonce, key);
122 }
123 
124 /**
125  * Generate a nonce suitable for secret key encryption.
126  */
127 alias makeNonce = randomArray!Nonce;
128 
129 /**
130  * Generate a key suitable for secret key encryption.
131  */
132 alias makeSecretKey = randomArray!SecretKey;