blob: 54db2b42d64db08f8a568877f3179dd33cf74073 [file] [log] [blame]
Andrey Andreev912831f2014-02-04 17:21:37 +02001<?php
2
3class Encryption_test extends CI_TestCase {
4
5 public function set_up()
6 {
Andrey Andreev81e10642014-02-07 01:43:36 +02007 $this->encryption = new Mock_Libraries_Encryption();
8 }
9
10 /**
11 * __construct test
12 *
13 * Covers behavior with $config['encryption_key'] set or not
14 */
15 public function test___construct()
16 {
17 // Assume no configuration from set_up()
18 $this->assertNull($this->encryption->get_key());
19
20 // Try with an empty value
21 $this->ci_set_config('encryption_key');
22 $this->encrypt = new Mock_Libraries_Encryption();
23 $this->assertNull($this->encrypt->get_key());
24
25 $this->ci_set_config('encryption_key', str_repeat("\x0", 16));
26 $this->encrypt = new Mock_Libraries_Encryption();
27 $this->assertEquals(str_repeat("\x0", 16), $this->encrypt->get_key());
Andrey Andreev912831f2014-02-04 17:21:37 +020028 }
29
30 // --------------------------------------------------------------------
31
Andrey Andreev81e10642014-02-07 01:43:36 +020032 /**
33 * hkdf() test
34 *
35 * Applies test vectors described in Appendix A(1-3) RFC5869.
36 * Described vectors 4-7 SHA-1, which we don't support and are
37 * therefore excluded.
38 *
39 * Because our implementation is a single method instead of being
40 * split into hkdf_extract() and hkdf_expand(), we cannot test for
41 * the PRK value. As long as the OKM is correct though, it's fine.
42 *
43 * @link https://tools.ietf.org/rfc/rfc5869.txt
44 */
45 public function test_hkdf()
46 {
47 $vectors = array(
48 // A.1: Basic test case with SHA-256
49 array(
50 'digest' => 'sha256',
51 'ikm' => "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
52 'salt' => "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c",
53 'length' => 42,
54 'info' => "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9",
55 // 'prk' => "\x07\x77\x09\x36\x2c\x2e\x32\xdf\x0d\xdc\x3f\x0d\xc4\x7b\xba\x63\x90\xb6\xc7\x3b\xb5\x0f\x9c\x31\x22\xec\x84\x4a\xd7\xc2\xb3\xe5",
56 'okm' => "\x3c\xb2\x5f\x25\xfa\xac\xd5\x7a\x90\x43\x4f\x64\xd0\x36\x2f\x2a\x2d\x2d\x0a\x90\xcf\x1a\x5a\x4c\x5d\xb0\x2d\x56\xec\xc4\xc5\xbf\x34\x00\x72\x08\xd5\xb8\x87\x18\x58\x65"
57 ),
58 // A.2: Test with SHA-256 and longer inputs/outputs
59 array(
60 'digest' => 'sha256',
61 'ikm' => "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
62 'salt' => "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
63 'length' => 82,
64 'info' => "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
65 // 'prk' => "\x06\xa6\xb8\x8c\x58\x53\x36\x1a\x06\x10\x4c\x9c\xeb\x35\xb4\x5c\xef\x76\x00\x14\x90\x46\x71\x01\x4a\x19\x3f\x40\xc1\x5f\xc2\x44",
66 'okm' => "\xb1\x1e\x39\x8d\xc8\x03\x27\xa1\xc8\xe7\xf7\x8c\x59\x6a\x49\x34\x4f\x01\x2e\xda\x2d\x4e\xfa\xd8\xa0\x50\xcc\x4c\x19\xaf\xa9\x7c\x59\x04\x5a\x99\xca\xc7\x82\x72\x71\xcb\x41\xc6\x5e\x59\x0e\x09\xda\x32\x75\x60\x0c\x2f\x09\xb8\x36\x77\x93\xa9\xac\xa3\xdb\x71\xcc\x30\xc5\x81\x79\xec\x3e\x87\xc1\x4c\x01\xd5\xc1\xf3\x43\x4f\x1d\x87",
67 ),
68 // A.3: Test with SHA-256 and zero-length salt/info
69 array(
70 'digest' => 'sha256',
71 'ikm' => "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
72 'salt' => '',
73 'length' => 42,
74 'info' => '',
75 // 'prk' => "\x19\xef\x24\xa3\x2c\x71\x7b\x16\x7f\x33\xa9\x1d\x6f\x64\x8b\xdf\x96\x59\x67\x76\xaf\xdb\x63\x77\xac\x43\x4c\x1c\x29\x3c\xcb\x04",
76 'okm' => "\x8d\xa4\xe7\x75\xa5\x63\xc1\x8f\x71\x5f\x80\x2a\x06\x3c\x5a\x31\xb8\xa1\x1f\x5c\x5e\xe1\x87\x9e\xc3\x45\x4e\x5f\x3c\x73\x8d\x2d\x9d\x20\x13\x95\xfa\xa4\xb6\x1a\x96\xc8",
77 )
78 );
79
80 foreach ($vectors as $test)
81 {
82 $this->assertEquals(
83 $test['okm'],
84 $this->encryption->hkdf(
85 $test['ikm'],
86 $test['digest'],
87 $test['salt'],
88 $test['length'],
89 $test['info']
90 )
91 );
92 }
93
94 // Test default length, it must match the digest size
95 $this->assertEquals(64, strlen($this->encryption->hkdf('foobar', 'sha512')));
96
97 // Test maximum length (RFC5869 says that it must be up to 255 times the digest size)
98 $this->assertEquals(12240, strlen($this->encryption->hkdf('foobar', 'sha384', NULL, 48 * 255)));
99 $this->assertFalse($this->encryption->hkdf('foobar', 'sha224', NULL, 28 * 255 + 1));
100
101 // CI-specific test for an invalid digest
102 $this->assertFalse($this->encryption->hkdf('fobar', 'sha1'));
103 }
104
105 // --------------------------------------------------------------------
106
107 /**
108 * _get_params() test
109 *
110 * @uses Mock_Libraries_Encryption::__get_params()
111 */
112 public function test__get_params()
113 {
114 $key = str_repeat("\x0", 16);
115
116 // Invalid custom parameters
117 $params = array(
118 // No cipher, mode or key
119 array('cipher' => 'aes-128', 'mode' => 'cbc'),
120 array('cipher' => 'aes-128', 'key' => $key),
121 array('mode' => 'cbc', 'key' => $key),
122 // No HMAC key or not a valid digest
123 array('cipher' => 'aes-128', 'mode' => 'cbc', 'key' => $key),
124 array('cipher' => 'aes-128', 'mode' => 'cbc', 'key' => $key, 'hmac_digest' => 'sha1', 'hmac_key' => $key),
125 // Invalid mode
126 array('cipher' => 'aes-128', 'mode' => 'foo', 'key' => $key, 'hmac_digest' => 'sha256', 'hmac_key' => $key)
127 );
128
129 for ($i = 0, $c = count($params); $i < $c; $i++)
130 {
131 $this->assertFalse($this->encryption->__get_params($params[$i]));
132 }
133
134 // Valid parameters
135 $params = array(
136 'cipher' => 'aes-128',
137 'mode' => 'cbc',
138 'key' => str_repeat("\x0", 16),
139 'hmac_key' => str_repeat("\x0", 16)
140 );
141
142 $this->assertTrue(is_array($this->encryption->__get_params($params)));
143
144 $params['iv'] = NULL;
145 $params['base64'] = TRUE;
146 $params['hmac_digest'] = 'sha512';
147
148 // Including all parameters
149 $params = array(
150 'cipher' => 'aes-128',
151 'mode' => 'cbc',
152 'key' => str_repeat("\x0", 16),
153 'iv' => str_repeat("\x0", 16),
Andrey Andreev4b450652014-02-10 06:59:54 +0200154 'raw_data' => TRUE,
Andrey Andreev81e10642014-02-07 01:43:36 +0200155 'hmac_key' => str_repeat("\x0", 16),
156 'hmac_digest' => 'sha256'
157 );
158
159 $output = $this->encryption->__get_params($params);
Andrey Andreev4b450652014-02-10 06:59:54 +0200160 unset($output['handle'], $params['raw_data']);
161 $params['base64'] = FALSE;
Andrey Andreev81e10642014-02-07 01:43:36 +0200162 $this->assertEquals($params, $output);
163
164 // HMAC disabled
165 unset($params['hmac_key'], $params['hmac_digest']);
Andrey Andreev4b450652014-02-10 06:59:54 +0200166 $params['hmac'] = $params['raw_data'] = FALSE;
Andrey Andreev81e10642014-02-07 01:43:36 +0200167 $output = $this->encryption->__get_params($params);
Andrey Andreev4b450652014-02-10 06:59:54 +0200168 unset($output['handle'], $params['hmac'], $params['raw_data']);
169 $params['base64'] = TRUE;
Andrey Andreev81e10642014-02-07 01:43:36 +0200170 $params['hmac_digest'] = $params['hmac_key'] = NULL;
171 $this->assertEquals($params, $output);
172 }
173
174 // --------------------------------------------------------------------
175
176 /**
177 * initialize(), encrypt(), decrypt() test
178 *
179 * Testing the three methods separately is not realistic as they are
180 * designed to work together. A more thorough test for initialize()
181 * though is the OpenSSL/MCrypt compatibility test.
182 */
183 public function test_initialize_encrypt_decrypt()
184 {
185 $message = 'This is a plain-text message.';
186 $key = "\xd0\xc9\x08\xc4\xde\x52\x12\x6e\xf8\xcc\xdb\x03\xea\xa0\x3a\x5c";
187
188 // Default state (AES-128/Rijndael-128 in CBC mode)
189 $this->encryption->initialize(array('key' => $key));
190
191 // Was the key properly set?
192 $this->assertEquals($key, $this->encryption->get_key());
193
194 $this->assertEquals($message, $this->encryption->decrypt($this->encryption->encrypt($message)));
195
196 // Try DES in ECB mode, just for the sake of changing stuff
197 $this->encryption->initialize(array('cipher' => 'des', 'mode' => 'ecb'));
198 $this->assertEquals($message, $this->encryption->decrypt($this->encryption->encrypt($message)));
199 }
200
201 // --------------------------------------------------------------------
202
203 /**
204 * encrypt(), decrypt test with custom parameters
205 */
206 public function test_encrypt_decrypt_custom()
207 {
208 $message = 'Another plain-text message.';
209
210 // A random invalid parameter
211 $this->assertFalse($this->encryption->encrypt($message, array('foo')));
212 $this->assertFalse($this->encryption->decrypt($message, array('foo')));
213
214 // Custom IV (we'll check it), no HMAC, binary output
215 $params = array(
216 'cipher' => 'tripledes',
217 'mode' => 'cfb',
218 'key' => str_repeat("\x1", 16),
219 'iv' => str_repeat("\x2", 8),
220 'base64' => FALSE,
221 'hmac' => FALSE
222 );
223
224 $ciphertext = $this->encryption->encrypt($message, $params);
225 $this->assertEquals(0, strncmp($params['iv'], $ciphertext, 8));
226
227 // IV should be found in the cipher-text, no matter if it was supplied or not
228 $this->assertEquals($message, $this->encryption->decrypt($ciphertext, $params));
229 unset($params['iv']);
230 $this->assertEquals($message, $this->encryption->decrypt($ciphertext, $params));
231 }
232
233 // --------------------------------------------------------------------
234
235 /**
236 * _mcrypt_get_handle() test
237 */
238 public function test__mcrypt_get_handle()
239 {
240 if ($this->encryption->drivers['mcrypt'] === FALSE)
241 {
242 return $this->markTestAsSkipped('Cannot test MCrypt because it is not available.');
243 }
244
245 $this->assertTrue(is_resource($this->encryption->__driver_get_handle('mcrypt', 'rijndael-128', 'cbc')));
246 }
247
248 // --------------------------------------------------------------------
249
250 /**
251 * _openssl_get_handle() test
252 */
253 public function test__openssl_mcrypt_get_handle()
254 {
255 if ($this->encryption->drivers['openssl'] === FALSE)
256 {
257 return $this->markTestAsSkipped('Cannot test OpenSSL because it is not available.');
258 }
259
260 $this->assertEquals('aes-128-cbc', $this->encryption->__driver_get_handle('openssl', 'aes-128', 'cbc'));
261 $this->assertEquals('rc4-40', $this->encryption->__driver_get_handle('openssl', 'rc4-40', 'stream'));
262 }
263
264 // --------------------------------------------------------------------
265
266 /**
267 * OpenSSL/MCrypt portability test
268 *
269 * Amongst the obvious stuff, _cipher_alias() is also tested here.
270 */
Andrey Andreev50ccc382014-02-04 23:30:06 +0200271 public function test_portability()
272 {
273 if ( ! $this->encryption->drivers['mcrypt'] OR ! $this->encryption->drivers['openssl'])
274 {
275 $this->markTestAsSkipped('Both MCrypt and OpenSSL support are required for portability tests.');
276 return;
277 }
278
279 $message = 'This is a message encrypted via MCrypt and decrypted via OpenSSL, or vice-versa.';
280
Andrey Andreevf4017672014-02-05 18:51:15 +0200281 // Format is: <Cipher name>, <Cipher mode>, <Key size>
Andrey Andreev50ccc382014-02-04 23:30:06 +0200282 $portable = array(
Andrey Andreevf4017672014-02-05 18:51:15 +0200283 array('aes-128', 'cbc', 16),
284 array('aes-128', 'cfb', 16),
285 array('aes-128', 'cfb8', 16),
286 array('aes-128', 'ofb', 16),
287 array('aes-128', 'ecb', 16),
288 array('aes-128', 'ctr', 16),
289 array('aes-192', 'cbc', 24),
290 array('aes-192', 'cfb', 24),
291 array('aes-192', 'cfb8', 24),
292 array('aes-192', 'ofb', 24),
293 array('aes-192', 'ecb', 24),
294 array('aes-192', 'ctr', 24),
295 array('aes-256', 'cbc', 32),
296 array('aes-256', 'cfb', 32),
297 array('aes-256', 'cfb8', 32),
298 array('aes-256', 'ofb', 32),
299 array('aes-256', 'ecb', 32),
300 array('aes-256', 'ctr', 32),
301 array('des', 'cbc', 7),
302 array('des', 'cfb', 7),
303 array('des', 'cfb8', 7),
304 array('des', 'ofb', 7),
305 array('des', 'ecb', 7),
306 array('tripledes', 'cbc', 7),
307 array('tripledes', 'cfb', 7),
308 array('tripledes', 'cfb8', 7),
309 array('tripledes', 'ofb', 7),
310 array('tripledes', 'cbc', 14),
311 array('tripledes', 'cfb', 14),
312 array('tripledes', 'cfb8', 14),
313 array('tripledes', 'ofb', 14),
314 array('tripledes', 'cbc', 21),
315 array('tripledes', 'cfb', 21),
316 array('tripledes', 'cfb8', 21),
317 array('tripledes', 'ofb', 21),
318 array('blowfish', 'cbc', 16),
319 array('blowfish', 'cfb', 16),
320 array('blowfish', 'ofb', 16),
321 array('blowfish', 'ecb', 16),
322 array('blowfish', 'cbc', 56),
323 array('blowfish', 'cfb', 56),
324 array('blowfish', 'ofb', 56),
325 array('blowfish', 'ecb', 56),
Andrey Andreeve8088d62014-02-06 05:01:48 +0200326 array('cast5', 'cbc', 5),
327 array('cast5', 'cfb', 5),
328 array('cast5', 'ofb', 5),
329 array('cast5', 'ecb', 5),
330 array('cast5', 'cbc', 8),
331 array('cast5', 'cfb', 8),
332 array('cast5', 'ofb', 8),
333 array('cast5', 'ecb', 8),
334 array('cast5', 'cbc', 10),
335 array('cast5', 'cfb', 10),
336 array('cast5', 'ofb', 10),
337 array('cast5', 'ecb', 10),
338 array('cast5', 'cbc', 16),
339 array('cast5', 'cfb', 16),
340 array('cast5', 'ofb', 16),
341 array('cast5', 'ecb', 16),
342 array('rc4', 'stream', 5),
343 array('rc4', 'stream', 8),
344 array('rc4', 'stream', 16),
345 array('rc4', 'stream', 32),
346 array('rc4', 'stream', 64),
347 array('rc4', 'stream', 128),
348 array('rc4', 'stream', 256)
Andrey Andreev50ccc382014-02-04 23:30:06 +0200349 );
350 $driver_index = array('mcrypt', 'openssl');
351
352 foreach ($portable as &$test)
353 {
354 // Add some randomness to the selected driver
355 $driver = mt_rand(0,1);
356 $params = array(
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200357 'driver' => $driver_index[$driver],
Andrey Andreevf4017672014-02-05 18:51:15 +0200358 'cipher' => $test[0],
359 'mode' => $test[1],
Andrey Andreev50ccc382014-02-04 23:30:06 +0200360 'key' => openssl_random_pseudo_bytes($test[2])
361 );
362
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200363 $this->encryption->initialize($params);
364 $ciphertext = $this->encryption->encrypt($message);
Andrey Andreev50ccc382014-02-04 23:30:06 +0200365
366 $driver = (int) ! $driver;
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200367 $params['driver'] = $driver_index[$driver];
Andrey Andreev50ccc382014-02-04 23:30:06 +0200368
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200369 $this->encryption->initialize($params);
370 $this->assertEquals($message, $this->encryption->decrypt($ciphertext));
Andrey Andreev50ccc382014-02-04 23:30:06 +0200371 }
372 }
373
374 // --------------------------------------------------------------------
375
Andrey Andreev81e10642014-02-07 01:43:36 +0200376 /**
377 * __get() test
378 */
379 public function test_magic_get()
Andrey Andreev912831f2014-02-04 17:21:37 +0200380 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200381 $this->assertNull($this->encryption->foo);
382 $this->assertEquals(array('mcrypt', 'openssl'), array_keys($this->encryption->drivers));
Andrey Andreev912831f2014-02-04 17:21:37 +0200383
Andrey Andreev81e10642014-02-07 01:43:36 +0200384 // 'stream' mode is translated into an empty string for OpenSSL
385 $this->encryption->initialize(array('cipher' => 'rc4', 'mode' => 'stream'));
386 $this->assertEquals('stream', $this->encryption->mode);
Andrey Andreev912831f2014-02-04 17:21:37 +0200387 }
388
389}