1 /**
2 * PKCS #5 v2.0 PBE
3 *
4 * Copyright:
5 * (C) 1999-2007,2014 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.pbes2;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_PBE_PKCS_V20):
15 
16 import botan.utils.types;
17 import botan.algo_base.transform;
18 import std.datetime;
19 import botan.pbkdf.pbkdf;
20 import botan.asn1.ber_dec;
21 import botan.asn1.der_enc;
22 import botan.asn1.alg_id;
23 import botan.asn1.oids;
24 import botan.rng.rng;
25 import botan.utils.parsing;
26 import botan.stream.stream_cipher;
27 import botan.modes.cipher_mode;
28 import botan.modes.cbc;
29 import botan.modes.aead.gcm;
30 import botan.utils.mem_ops;
31 import botan.libstate.libstate;
32 
33 
34 /**
35 * Encrypt with PBES2 from PKCS #5 v2.0
36 *  key_bits = the passphrase to use for encryption
37 *  msec = how many milliseconds to run PBKDF2
38 *  cipher = specifies the block cipher to use to encrypt
39 *  digest = specifies the PRF to use with PBKDF2 (eg "HMAC(SHA-1)")
40 *  rng = a random number generator
41 */
42 Pair!(AlgorithmIdentifier, Array!ubyte)
43 	pbes2Encrypt()(auto const ref SecureVector!ubyte key_bits,
44                    const string passphrase,
45                    Duration msec,
46                    const string cipher,
47                    const string digest,
48                    RandomNumberGenerator rng,
49                    AlgorithmFactory af = null)
50 {
51 	if (!af) af = globalState().algorithmFactory();
52 	const string prf = "HMAC(" ~ digest ~ ")";
53 	
54 	const Vector!string cipher_spec = splitter(cipher, '/');
55 	if(cipher_spec.length != 2)
56 		throw new DecodingError("PBE-PKCS5 v2.0: Invalid cipher spec " ~ cipher);
57 	
58 	const SecureVector!ubyte salt = rng.randomVec(12);
59 	
60 	if(cipher_spec[1] != "CBC" && cipher_spec[1] != "GCM")
61 		throw new DecodingError("PBE-PKCS5 v2.0: Don't know param format for " ~ cipher);
62 
63 	Unique!KeyedTransform enc;
64 	static if (BOTAN_HAS_AEAD_GCM) {
65 		if(cipher_spec[1] == "GCM")
66 			enc = new GCMEncryption(af.makeBlockCipher(cipher_spec[0]));
67 		else if(cipher_spec[1] == "CBC")
68 			enc = new CBCEncryption(af.makeBlockCipher(cipher_spec[0]), new PKCS7Padding);
69 		else
70 			throw new DecodingError("PBE-PKCS5 v2.0: Don't know param format for " ~ cipher);
71 	} else {	
72 		if(cipher_spec[1] == "CBC")
73 			enc = new CBCEncryption(af.makeBlockCipher(cipher_spec[0]), new PKCS7Padding);
74 		else
75 			throw new DecodingError("PBE-PKCS5 v2.0: Don't know param format for " ~ cipher);
76 	}
77 	if (enc.isEmpty())
78 		throw new DecodingError("PBE-PKCS5: Cannot decrypt, no cipher " ~ cipher);
79 	Unique!PBKDF pbkdf = getPbkdf("PBKDF2(" ~ prf ~ ")");
80 	
81 	const size_t key_length = enc.keySpec().maximumKeylength();
82 	size_t iterations = 0;
83 	
84 	SecureVector!ubyte iv = rng.randomVec(enc.defaultNonceLength());
85 
86 	auto key = pbkdf.deriveKey(key_length, passphrase, salt.ptr, salt.length,
87 		msec, iterations).bitsOf();
88 	enc.setKey(key.ptr, key.length);
89 	
90 	enc.start(iv);
91 	SecureVector!ubyte buf = key_bits.ptr[0 .. key_bits.length];
92 	enc.finish(buf);
93 	
94 	AlgorithmIdentifier id = AlgorithmIdentifier(
95 		OIDS.lookup("PBE-PKCS5v20"),
96 		encodePbes2Params(cipher, prf, salt, iv, iterations, key_length));
97 	
98 	return makePair(id, unlock(buf).cloneToRef);
99 	
100 }
101 /*
102 * Encode PKCS#5 PBES2 parameters
103 */
104 Vector!ubyte encodePbes2Params(const string cipher,
105                                const string prf,
106                                const ref SecureVector!ubyte salt,
107                                const ref SecureVector!ubyte iv,
108                                size_t iterations,
109                                size_t key_length)
110 {
111 	return DEREncoder()
112 		.startCons(ASN1Tag.SEQUENCE)
113 			.encode(AlgorithmIdentifier("PKCS5.PBKDF2", 
114 					DEREncoder()
115 					.startCons(ASN1Tag.SEQUENCE)
116 					.encode(salt, ASN1Tag.OCTET_STRING)
117 					.encode(iterations)
118 					.encode(key_length)
119 					.encodeIf(prf != "HMAC(SHA-160)",
120 						AlgorithmIdentifier(prf, AlgorithmIdentifierImpl.USE_NULL_PARAM))
121 					.endCons()
122 					.getContentsUnlocked()
123 					)
124 				)
125 			.encode(
126 				AlgorithmIdentifier(cipher,
127 					DEREncoder().encode(iv, ASN1Tag.OCTET_STRING).getContentsUnlocked()
128 					)
129 				)
130 			.endCons()
131 			.getContentsUnlocked();
132 }
133 
134 
135 /**
136 * Decrypt a PKCS #5 v2.0 encrypted stream
137 *  key_bits = the input
138 *  passphrase = the passphrase to use for decryption
139 *  params = the PBES2 parameters
140 */
141 SecureVector!ubyte
142 	pbes2Decrypt()(const ref SecureVector!ubyte key_bits,
143 				   const string passphrase,
144 				   auto const ref Vector!ubyte params,
145                    AlgorithmFactory af = null)
146 {
147 	if (!af) af = globalState().algorithmFactory();
148 	AlgorithmIdentifier kdf_algo, enc_algo;
149 	
150 	BERDecoder(params)
151 		.startCons(ASN1Tag.SEQUENCE)
152 			.decode(kdf_algo)
153 			.decode(enc_algo)
154 			.verifyEnd()
155 			.endCons();
156 	
157 	AlgorithmIdentifier prf_algo;
158 	
159 	if(kdf_algo.oid != OIDS.lookup("PKCS5.PBKDF2"))
160 		throw new DecodingError("PBE-PKCS5 v2.0: Unknown KDF algorithm " ~ kdf_algo.oid.toString());
161 	
162 	SecureVector!ubyte salt;
163 	size_t iterations, key_length;
164 	
165 	BERDecoder(kdf_algo.parameters)
166 		.startCons(ASN1Tag.SEQUENCE)
167 			.decode(salt, ASN1Tag.OCTET_STRING)
168 			.decode(iterations)
169 			.decodeOptional(key_length, ASN1Tag.INTEGER, ASN1Tag.UNIVERSAL)
170 			.decodeOptional(prf_algo, ASN1Tag.SEQUENCE, ASN1Tag.CONSTRUCTED,
171 				AlgorithmIdentifier("HMAC(SHA-160)",
172 					AlgorithmIdentifierImpl.USE_NULL_PARAM))
173 			.verifyEnd()
174 			.endCons();
175 	
176 	const string cipher = OIDS.lookup(enc_algo.oid);
177 	const Vector!string cipher_spec = splitter(cipher, '/');
178 	if(cipher_spec.length != 2)
179 		throw new DecodingError("PBE-PKCS5 v2.0: Invalid cipher spec " ~ cipher);
180 	if(cipher_spec[1] != "CBC" && cipher_spec[1] != "GCM")
181 		throw new DecodingError("PBE-PKCS5 v2.0: Don't know param format for " ~ cipher);
182 	
183 	if(salt.length < 8)
184 		throw new DecodingError("PBE-PKCS5 v2.0: Encoded salt is too small");
185 	
186 	SecureVector!ubyte iv;
187 	BERDecoder(enc_algo.parameters).decode(iv, ASN1Tag.OCTET_STRING).verifyEnd();
188 	
189 	const string prf = OIDS.lookup(prf_algo.oid);
190 	
191 	Unique!PBKDF pbkdf = getPbkdf("PBKDF2(" ~ prf ~ ")");
192 
193 	Unique!KeyedTransform dec;
194 	static if (BOTAN_HAS_AEAD_GCM) {
195 		if (cipher_spec[1] == "GCM")
196 			dec = new GCMDecryption(af.makeBlockCipher(cipher_spec[0]));
197 		else if (cipher_spec[1] == "CBC")
198 			dec = new CBCDecryption(af.makeBlockCipher(cipher_spec[0]), new PKCS7Padding);
199 		else
200 			throw new DecodingError("PBE-PKCS5 v2.0: Don't know param format for " ~ cipher);
201 	} else {	
202 		if (cipher_spec[1] == "CBC")
203 			dec = new CBCDecryption(af.makeBlockCipher(cipher_spec[0]), new PKCS7Padding);
204 		else
205 			throw new DecodingError("PBE-PKCS5 v2.0: Don't know param format for " ~ cipher);
206 	}
207 
208 	if(key_length == 0)
209 		key_length = dec.keySpec().maximumKeylength();
210 	
211 	dec.setKey(pbkdf.deriveKey(key_length, passphrase, salt.ptr, salt.length, iterations));
212 	
213 	dec.start(iv);
214 	
215 	SecureVector!ubyte buf = key_bits.ptr[0 .. key_bits.length];
216 	dec.finish(buf);
217 	
218 	return buf.move();
219 }