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 }