blob: cb9326d1164e0481868ea1ff271511bd581d198f [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
Andrey Andreevf30b14a2014-02-20 16:18:37 +020010 // --------------------------------------------------------------------
11
Andrey Andreev81e10642014-02-07 01:43:36 +020012 /**
13 * __construct test
14 *
15 * Covers behavior with $config['encryption_key'] set or not
16 */
17 public function test___construct()
18 {
19 // Assume no configuration from set_up()
20 $this->assertNull($this->encryption->get_key());
21
22 // Try with an empty value
23 $this->ci_set_config('encryption_key');
24 $this->encrypt = new Mock_Libraries_Encryption();
25 $this->assertNull($this->encrypt->get_key());
26
27 $this->ci_set_config('encryption_key', str_repeat("\x0", 16));
28 $this->encrypt = new Mock_Libraries_Encryption();
29 $this->assertEquals(str_repeat("\x0", 16), $this->encrypt->get_key());
Andrey Andreev912831f2014-02-04 17:21:37 +020030 }
31
32 // --------------------------------------------------------------------
33
Andrey Andreev81e10642014-02-07 01:43:36 +020034 /**
35 * hkdf() test
36 *
37 * Applies test vectors described in Appendix A(1-3) RFC5869.
38 * Described vectors 4-7 SHA-1, which we don't support and are
39 * therefore excluded.
40 *
41 * Because our implementation is a single method instead of being
42 * split into hkdf_extract() and hkdf_expand(), we cannot test for
43 * the PRK value. As long as the OKM is correct though, it's fine.
44 *
45 * @link https://tools.ietf.org/rfc/rfc5869.txt
46 */
47 public function test_hkdf()
48 {
49 $vectors = array(
50 // A.1: Basic test case with SHA-256
51 array(
52 'digest' => 'sha256',
53 'ikm' => "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
54 'salt' => "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c",
55 'length' => 42,
56 'info' => "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9",
57 // '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",
58 '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"
59 ),
60 // A.2: Test with SHA-256 and longer inputs/outputs
61 array(
62 'digest' => 'sha256',
63 '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",
64 '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",
65 'length' => 82,
66 '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",
67 // '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",
68 '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",
69 ),
70 // A.3: Test with SHA-256 and zero-length salt/info
71 array(
72 'digest' => 'sha256',
73 'ikm' => "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
74 'salt' => '',
75 'length' => 42,
76 'info' => '',
77 // '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",
78 '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",
79 )
80 );
81
82 foreach ($vectors as $test)
83 {
84 $this->assertEquals(
85 $test['okm'],
86 $this->encryption->hkdf(
87 $test['ikm'],
88 $test['digest'],
89 $test['salt'],
90 $test['length'],
91 $test['info']
92 )
93 );
94 }
95
96 // Test default length, it must match the digest size
97 $this->assertEquals(64, strlen($this->encryption->hkdf('foobar', 'sha512')));
98
99 // Test maximum length (RFC5869 says that it must be up to 255 times the digest size)
100 $this->assertEquals(12240, strlen($this->encryption->hkdf('foobar', 'sha384', NULL, 48 * 255)));
101 $this->assertFalse($this->encryption->hkdf('foobar', 'sha224', NULL, 28 * 255 + 1));
102
103 // CI-specific test for an invalid digest
104 $this->assertFalse($this->encryption->hkdf('fobar', 'sha1'));
105 }
106
107 // --------------------------------------------------------------------
108
109 /**
110 * _get_params() test
111 *
112 * @uses Mock_Libraries_Encryption::__get_params()
113 */
114 public function test__get_params()
115 {
116 $key = str_repeat("\x0", 16);
117
118 // Invalid custom parameters
119 $params = array(
120 // No cipher, mode or key
121 array('cipher' => 'aes-128', 'mode' => 'cbc'),
122 array('cipher' => 'aes-128', 'key' => $key),
123 array('mode' => 'cbc', 'key' => $key),
124 // No HMAC key or not a valid digest
125 array('cipher' => 'aes-128', 'mode' => 'cbc', 'key' => $key),
126 array('cipher' => 'aes-128', 'mode' => 'cbc', 'key' => $key, 'hmac_digest' => 'sha1', 'hmac_key' => $key),
127 // Invalid mode
128 array('cipher' => 'aes-128', 'mode' => 'foo', 'key' => $key, 'hmac_digest' => 'sha256', 'hmac_key' => $key)
129 );
130
131 for ($i = 0, $c = count($params); $i < $c; $i++)
132 {
133 $this->assertFalse($this->encryption->__get_params($params[$i]));
134 }
135
136 // Valid parameters
137 $params = array(
138 'cipher' => 'aes-128',
139 'mode' => 'cbc',
140 'key' => str_repeat("\x0", 16),
141 'hmac_key' => str_repeat("\x0", 16)
142 );
143
144 $this->assertTrue(is_array($this->encryption->__get_params($params)));
145
146 $params['iv'] = NULL;
147 $params['base64'] = TRUE;
148 $params['hmac_digest'] = 'sha512';
149
150 // Including all parameters
151 $params = array(
152 'cipher' => 'aes-128',
153 'mode' => 'cbc',
154 'key' => str_repeat("\x0", 16),
155 'iv' => str_repeat("\x0", 16),
Andrey Andreev4b450652014-02-10 06:59:54 +0200156 'raw_data' => TRUE,
Andrey Andreev81e10642014-02-07 01:43:36 +0200157 'hmac_key' => str_repeat("\x0", 16),
158 'hmac_digest' => 'sha256'
159 );
160
161 $output = $this->encryption->__get_params($params);
Andrey Andreev4b450652014-02-10 06:59:54 +0200162 unset($output['handle'], $params['raw_data']);
163 $params['base64'] = FALSE;
Andrey Andreev81e10642014-02-07 01:43:36 +0200164 $this->assertEquals($params, $output);
165
166 // HMAC disabled
167 unset($params['hmac_key'], $params['hmac_digest']);
Andrey Andreev4b450652014-02-10 06:59:54 +0200168 $params['hmac'] = $params['raw_data'] = FALSE;
Andrey Andreev81e10642014-02-07 01:43:36 +0200169 $output = $this->encryption->__get_params($params);
Andrey Andreev4b450652014-02-10 06:59:54 +0200170 unset($output['handle'], $params['hmac'], $params['raw_data']);
171 $params['base64'] = TRUE;
Andrey Andreev81e10642014-02-07 01:43:36 +0200172 $params['hmac_digest'] = $params['hmac_key'] = NULL;
173 $this->assertEquals($params, $output);
174 }
175
176 // --------------------------------------------------------------------
177
178 /**
179 * initialize(), encrypt(), decrypt() test
180 *
181 * Testing the three methods separately is not realistic as they are
182 * designed to work together. A more thorough test for initialize()
183 * though is the OpenSSL/MCrypt compatibility test.
Andrey Andreev1b4e5e12014-02-13 01:14:28 +0200184 *
185 * @depends test_hkdf
186 * @depends test__get_params
Andrey Andreev81e10642014-02-07 01:43:36 +0200187 */
188 public function test_initialize_encrypt_decrypt()
189 {
190 $message = 'This is a plain-text message.';
191 $key = "\xd0\xc9\x08\xc4\xde\x52\x12\x6e\xf8\xcc\xdb\x03\xea\xa0\x3a\x5c";
192
193 // Default state (AES-128/Rijndael-128 in CBC mode)
194 $this->encryption->initialize(array('key' => $key));
195
196 // Was the key properly set?
197 $this->assertEquals($key, $this->encryption->get_key());
198
199 $this->assertEquals($message, $this->encryption->decrypt($this->encryption->encrypt($message)));
200
201 // Try DES in ECB mode, just for the sake of changing stuff
202 $this->encryption->initialize(array('cipher' => 'des', 'mode' => 'ecb'));
203 $this->assertEquals($message, $this->encryption->decrypt($this->encryption->encrypt($message)));
204 }
205
206 // --------------------------------------------------------------------
207
208 /**
209 * encrypt(), decrypt test with custom parameters
Andrey Andreev1b4e5e12014-02-13 01:14:28 +0200210 *
211 * @depends test___get_params
Andrey Andreev81e10642014-02-07 01:43:36 +0200212 */
213 public function test_encrypt_decrypt_custom()
214 {
215 $message = 'Another plain-text message.';
216
217 // A random invalid parameter
218 $this->assertFalse($this->encryption->encrypt($message, array('foo')));
219 $this->assertFalse($this->encryption->decrypt($message, array('foo')));
220
221 // Custom IV (we'll check it), no HMAC, binary output
222 $params = array(
223 'cipher' => 'tripledes',
224 'mode' => 'cfb',
225 'key' => str_repeat("\x1", 16),
226 'iv' => str_repeat("\x2", 8),
227 'base64' => FALSE,
228 'hmac' => FALSE
229 );
230
231 $ciphertext = $this->encryption->encrypt($message, $params);
232 $this->assertEquals(0, strncmp($params['iv'], $ciphertext, 8));
233
234 // IV should be found in the cipher-text, no matter if it was supplied or not
235 $this->assertEquals($message, $this->encryption->decrypt($ciphertext, $params));
236 unset($params['iv']);
237 $this->assertEquals($message, $this->encryption->decrypt($ciphertext, $params));
238 }
239
240 // --------------------------------------------------------------------
241
242 /**
243 * _mcrypt_get_handle() test
244 */
245 public function test__mcrypt_get_handle()
246 {
247 if ($this->encryption->drivers['mcrypt'] === FALSE)
248 {
Andrey Andreev1b4e5e12014-02-13 01:14:28 +0200249 return $this->markTestSkipped('Cannot test MCrypt because it is not available.');
Andrey Andreev81e10642014-02-07 01:43:36 +0200250 }
251
252 $this->assertTrue(is_resource($this->encryption->__driver_get_handle('mcrypt', 'rijndael-128', 'cbc')));
253 }
254
255 // --------------------------------------------------------------------
256
257 /**
258 * _openssl_get_handle() test
259 */
260 public function test__openssl_mcrypt_get_handle()
261 {
262 if ($this->encryption->drivers['openssl'] === FALSE)
263 {
Andrey Andreev1b4e5e12014-02-13 01:14:28 +0200264 return $this->markTestSkipped('Cannot test OpenSSL because it is not available.');
Andrey Andreev81e10642014-02-07 01:43:36 +0200265 }
266
267 $this->assertEquals('aes-128-cbc', $this->encryption->__driver_get_handle('openssl', 'aes-128', 'cbc'));
268 $this->assertEquals('rc4-40', $this->encryption->__driver_get_handle('openssl', 'rc4-40', 'stream'));
269 }
270
271 // --------------------------------------------------------------------
272
273 /**
274 * OpenSSL/MCrypt portability test
275 *
276 * Amongst the obvious stuff, _cipher_alias() is also tested here.
277 */
Andrey Andreev50ccc382014-02-04 23:30:06 +0200278 public function test_portability()
279 {
280 if ( ! $this->encryption->drivers['mcrypt'] OR ! $this->encryption->drivers['openssl'])
281 {
Andrey Andreev1b4e5e12014-02-13 01:14:28 +0200282 $this->markTestSkipped('Both MCrypt and OpenSSL support are required for portability tests.');
Andrey Andreev50ccc382014-02-04 23:30:06 +0200283 return;
284 }
285
286 $message = 'This is a message encrypted via MCrypt and decrypted via OpenSSL, or vice-versa.';
287
Andrey Andreevf4017672014-02-05 18:51:15 +0200288 // Format is: <Cipher name>, <Cipher mode>, <Key size>
Andrey Andreev50ccc382014-02-04 23:30:06 +0200289 $portable = array(
Andrey Andreevf4017672014-02-05 18:51:15 +0200290 array('aes-128', 'cbc', 16),
291 array('aes-128', 'cfb', 16),
292 array('aes-128', 'cfb8', 16),
293 array('aes-128', 'ofb', 16),
294 array('aes-128', 'ecb', 16),
295 array('aes-128', 'ctr', 16),
296 array('aes-192', 'cbc', 24),
297 array('aes-192', 'cfb', 24),
298 array('aes-192', 'cfb8', 24),
299 array('aes-192', 'ofb', 24),
300 array('aes-192', 'ecb', 24),
301 array('aes-192', 'ctr', 24),
302 array('aes-256', 'cbc', 32),
303 array('aes-256', 'cfb', 32),
304 array('aes-256', 'cfb8', 32),
305 array('aes-256', 'ofb', 32),
306 array('aes-256', 'ecb', 32),
307 array('aes-256', 'ctr', 32),
308 array('des', 'cbc', 7),
309 array('des', 'cfb', 7),
310 array('des', 'cfb8', 7),
311 array('des', 'ofb', 7),
312 array('des', 'ecb', 7),
313 array('tripledes', 'cbc', 7),
314 array('tripledes', 'cfb', 7),
315 array('tripledes', 'cfb8', 7),
316 array('tripledes', 'ofb', 7),
317 array('tripledes', 'cbc', 14),
318 array('tripledes', 'cfb', 14),
319 array('tripledes', 'cfb8', 14),
320 array('tripledes', 'ofb', 14),
321 array('tripledes', 'cbc', 21),
322 array('tripledes', 'cfb', 21),
323 array('tripledes', 'cfb8', 21),
324 array('tripledes', 'ofb', 21),
325 array('blowfish', 'cbc', 16),
326 array('blowfish', 'cfb', 16),
327 array('blowfish', 'ofb', 16),
328 array('blowfish', 'ecb', 16),
329 array('blowfish', 'cbc', 56),
330 array('blowfish', 'cfb', 56),
331 array('blowfish', 'ofb', 56),
332 array('blowfish', 'ecb', 56),
Andrey Andreev18767e32014-03-04 22:21:35 +0200333 array('cast5', 'cbc', 11),
334 array('cast5', 'cfb', 11),
335 array('cast5', 'ofb', 11),
336 array('cast5', 'ecb', 11),
Andrey Andreeve8088d62014-02-06 05:01:48 +0200337 array('cast5', 'cbc', 16),
338 array('cast5', 'cfb', 16),
339 array('cast5', 'ofb', 16),
340 array('cast5', 'ecb', 16),
341 array('rc4', 'stream', 5),
342 array('rc4', 'stream', 8),
343 array('rc4', 'stream', 16),
344 array('rc4', 'stream', 32),
345 array('rc4', 'stream', 64),
346 array('rc4', 'stream', 128),
347 array('rc4', 'stream', 256)
Andrey Andreev50ccc382014-02-04 23:30:06 +0200348 );
349 $driver_index = array('mcrypt', 'openssl');
350
351 foreach ($portable as &$test)
352 {
353 // Add some randomness to the selected driver
354 $driver = mt_rand(0,1);
355 $params = array(
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200356 'driver' => $driver_index[$driver],
Andrey Andreevf4017672014-02-05 18:51:15 +0200357 'cipher' => $test[0],
358 'mode' => $test[1],
Andrey Andreev50ccc382014-02-04 23:30:06 +0200359 'key' => openssl_random_pseudo_bytes($test[2])
360 );
361
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200362 $this->encryption->initialize($params);
363 $ciphertext = $this->encryption->encrypt($message);
Andrey Andreev50ccc382014-02-04 23:30:06 +0200364
365 $driver = (int) ! $driver;
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200366 $params['driver'] = $driver_index[$driver];
Andrey Andreev50ccc382014-02-04 23:30:06 +0200367
Andrey Andreev8d33a9a2014-02-05 23:11:23 +0200368 $this->encryption->initialize($params);
369 $this->assertEquals($message, $this->encryption->decrypt($ciphertext));
Andrey Andreev50ccc382014-02-04 23:30:06 +0200370 }
371 }
372
373 // --------------------------------------------------------------------
374
Andrey Andreev81e10642014-02-07 01:43:36 +0200375 /**
376 * __get() test
377 */
378 public function test_magic_get()
Andrey Andreev912831f2014-02-04 17:21:37 +0200379 {
Andrey Andreev81e10642014-02-07 01:43:36 +0200380 $this->assertNull($this->encryption->foo);
381 $this->assertEquals(array('mcrypt', 'openssl'), array_keys($this->encryption->drivers));
Andrey Andreev912831f2014-02-04 17:21:37 +0200382
Andrey Andreev81e10642014-02-07 01:43:36 +0200383 // 'stream' mode is translated into an empty string for OpenSSL
384 $this->encryption->initialize(array('cipher' => 'rc4', 'mode' => 'stream'));
385 $this->assertEquals('stream', $this->encryption->mode);
Andrey Andreev912831f2014-02-04 17:21:37 +0200386 }
387
388}