1 /**
2 * Cryptobox Message Routines
3 * 
4 * Copyright:
5 * (C) 2009 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.constructs.cryptobox;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_SHA2_64 && BOTAN_HAS_PBKDF2 && BOTAN_HAS_SERPENT && BOTAN_HAS_CTR_BE):
15 
16 import botan.rng.rng;
17 import botan.algo_base.symkey;
18 import botan.filters.filters;
19 import botan.filters.pipe;
20 import botan.libstate.lookup;
21 import botan.hash.sha2_64;
22 import botan.mac.hmac;
23 import botan.pbkdf.pbkdf2;
24 import botan.codec.pem;
25 import botan.utils.get_byte;
26 import botan.utils.mem_ops;
27 import botan.filters.data_src;
28 
29 /**
30 * This namespace holds various high-level crypto functions
31 */
32 struct CryptoBox {
33 
34     /**
35     * Encrypt a message using a passphrase
36     * Params:
37     *  input = the input data
38     *  input_len = the length of input in bytes
39     *  passphrase = the passphrase used to encrypt the message
40     *  rng = a ref to a random number generator, such as AutoSeededRNG
41     */
42     static string encrypt(const(ubyte)* input, size_t input_len,
43                           in string passphrase,
44                           RandomNumberGenerator rng)
45     {
46         SecureVector!ubyte pbkdf_salt = SecureVector!ubyte(PBKDF_SALT_LEN);
47         rng.randomize(pbkdf_salt.ptr, pbkdf_salt.length);
48 
49         auto pbkdf = scoped!PKCS5_PBKDF2(new HMAC(new SHA512));
50         
51         OctetString master_key = pbkdf.deriveKey(PBKDF_OUTPUT_LEN, passphrase, pbkdf_salt.ptr, pbkdf_salt.length, PBKDF_ITERATIONS);
52         
53         const(const(ubyte)*) mk = master_key.ptr;
54         
55         SymmetricKey cipher_key = SymmetricKey(mk, CIPHER_KEY_LEN);
56         SymmetricKey mac_key = SymmetricKey(&mk[CIPHER_KEY_LEN], MAC_KEY_LEN);
57         InitializationVector iv = InitializationVector(&mk[CIPHER_KEY_LEN + MAC_KEY_LEN], CIPHER_IV_LEN);
58         
59         Pipe pipe = Pipe(getCipher("Serpent/CTR-BE", cipher_key, iv, ENCRYPTION),
60                          new Fork(null,
61                                   new MACFilter(new HMAC(new SHA512),
62                                                 mac_key, MAC_OUTPUT_LEN)));
63         
64         pipe.processMsg(input, input_len);
65         
66         /*
67         Output format is:
68             version # (4 bytes)
69             salt (10 bytes)
70             mac (20 bytes)
71             ciphertext
72         */
73         const size_t ciphertext_len = pipe.remaining(0);
74         
75         SecureVector!ubyte out_buf = SecureVector!ubyte(VERSION_CODE_LEN + PBKDF_SALT_LEN + MAC_OUTPUT_LEN + ciphertext_len);
76         
77         foreach (size_t i; 0 .. VERSION_CODE_LEN)
78             out_buf[i] = get_byte(i, CRYPTOBOX_VERSION_CODE);
79         
80         copyMem(&out_buf[VERSION_CODE_LEN], pbkdf_salt.ptr,  PBKDF_SALT_LEN);
81         
82         pipe.read(&out_buf[VERSION_CODE_LEN + PBKDF_SALT_LEN], MAC_OUTPUT_LEN, 1);
83         pipe.read(&out_buf[VERSION_CODE_LEN + PBKDF_SALT_LEN + MAC_OUTPUT_LEN],
84         ciphertext_len, 0);
85         
86         return PEM.encode(out_buf, "BOTAN CRYPTOBOX MESSAGE");
87     }
88 
89     /**
90     * Decrypt a message encrypted with CryptoBox::encrypt
91     * Params:
92     *  input = the input data
93     *  input_len = the length of input in bytes
94     *  passphrase = the passphrase used to encrypt the message
95     */
96     static string decrypt(const(ubyte)* input, size_t input_len, in string passphrase)
97     {
98         auto input_src = DataSourceMemory(input, input_len);
99         SecureVector!ubyte ciphertext = PEM.decodeCheckLabel(cast(DataSource)input_src, "BOTAN CRYPTOBOX MESSAGE");
100         
101         if (ciphertext.length < (VERSION_CODE_LEN + PBKDF_SALT_LEN + MAC_OUTPUT_LEN))
102             throw new DecodingError("Invalid CryptoBox input");
103         
104         foreach (size_t i; 0 .. VERSION_CODE_LEN)
105             if (ciphertext[i] != get_byte(i, CRYPTOBOX_VERSION_CODE))
106                 throw new DecodingError("Bad CryptoBox version");
107         
108         const(ubyte)* pbkdf_salt = &ciphertext[VERSION_CODE_LEN];
109         
110         auto pbkdf = scoped!PKCS5_PBKDF2(new HMAC(new SHA512));
111         
112         OctetString master_key = pbkdf.deriveKey(PBKDF_OUTPUT_LEN,
113                                                  passphrase,
114                                                  pbkdf_salt,
115                                                  PBKDF_SALT_LEN,
116                                                  PBKDF_ITERATIONS);
117         
118         const(ubyte)* mk = master_key.ptr;
119         
120         SymmetricKey cipher_key = SymmetricKey(mk, CIPHER_KEY_LEN);
121         SymmetricKey mac_key = SymmetricKey(&mk[CIPHER_KEY_LEN], MAC_KEY_LEN);
122         InitializationVector iv = InitializationVector(&mk[CIPHER_KEY_LEN + MAC_KEY_LEN], CIPHER_IV_LEN);
123 
124         Pipe pipe = Pipe(new Fork(getCipher("Serpent/CTR-BE", cipher_key, iv, DECRYPTION),
125                                   new MACFilter(new HMAC(new SHA512), mac_key, MAC_OUTPUT_LEN)));
126         
127         const size_t ciphertext_offset = VERSION_CODE_LEN + PBKDF_SALT_LEN + MAC_OUTPUT_LEN;
128         
129         pipe.processMsg(&ciphertext[ciphertext_offset],
130                             ciphertext.length - ciphertext_offset);
131 
132         ubyte[MAC_OUTPUT_LEN] computed_mac;
133         pipe.read(computed_mac.ptr, MAC_OUTPUT_LEN, 1);
134         
135         if (!sameMem(computed_mac.ptr, &ciphertext[VERSION_CODE_LEN + PBKDF_SALT_LEN], MAC_OUTPUT_LEN))
136             throw new DecodingError("CryptoBox integrity failure");
137         
138         return pipe.toString(0);
139     }
140 
141 
142     /**
143     * Decrypt a message encrypted with CryptoBox::encrypt
144     * Params:
145     *  input = the input data
146     *  passphrase = the passphrase used to encrypt the message
147     */
148     static string decrypt(in string input, in string passphrase)
149     {
150         return decrypt(cast(const(ubyte)*)(input.ptr), input.length, passphrase);
151     }
152 
153 
154 }
155 
156 private:
157 /*
158 First 24 bits of SHA-256("Botan Cryptobox"), followed by 8 0 bits
159 for later use as flags, etc if needed
160 */
161 __gshared immutable uint CRYPTOBOX_VERSION_CODE = 0xEFC22400;
162 
163 __gshared immutable size_t VERSION_CODE_LEN = 4;
164 __gshared immutable size_t CIPHER_KEY_LEN = 32;
165 __gshared immutable size_t CIPHER_IV_LEN = 16;
166 __gshared immutable size_t MAC_KEY_LEN = 32;
167 __gshared immutable size_t MAC_OUTPUT_LEN = 20;
168 __gshared immutable size_t PBKDF_SALT_LEN = 10;
169 __gshared immutable size_t PBKDF_ITERATIONS = 8 * 1024;
170 
171 __gshared immutable size_t PBKDF_OUTPUT_LEN = CIPHER_KEY_LEN + CIPHER_IV_LEN + MAC_KEY_LEN;
172 
173 import botan.constants;
174 static if (BOTAN_TEST):
175 
176 import botan.test;
177 import botan.rng.auto_rng;
178 
179 static if (BOTAN_HAS_TESTS && !SKIP_CRYPTOBOX_TEST) unittest
180 {
181     logDebug("Testing cryptobox.d ...");
182     import botan.libstate.global_state;
183     auto state = globalState(); // ensure initialized
184     size_t fails = 0;
185     
186 	Unique!AutoSeededRNG rng = new AutoSeededRNG;
187     
188     __gshared immutable ubyte[3] msg = [ 0xAA, 0xBB, 0xCC ];
189     string ciphertext = CryptoBox.encrypt(msg.ptr, msg.length, "secret password", *rng);
190     
191     try
192     {
193         string plaintext = CryptoBox.decrypt(ciphertext, "secret password");
194         
195         if (plaintext.length != msg.length || !sameMem(cast(const(ubyte)*)(plaintext.ptr), msg.ptr, msg.length))
196             ++fails;
197         
198     }
199     catch(Exception e)
200     {
201         logTrace("Error during Cryptobox test " ~ e.msg);
202         ++fails;
203     }
204     
205     testReport("Cryptobox", 2, fails);
206 }