Skip to content

Commit 79f065b

Browse files
committed
account for a nuanced difference between phpseclib / mcrypt w.r.t. AES
1 parent 4a31f43 commit 79f065b

File tree

2 files changed

+118
-3
lines changed

2 files changed

+118
-3
lines changed

lib/mcrypt.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,55 @@
108108
}
109109

110110
if (!function_exists('phpseclib_mcrypt_list_algorithms')) {
111+
/**
112+
* mcrypt-compatible Rijndael wrapper
113+
*
114+
* With mcrypt you have algorithms like rijndael-128, rijndael-192 and rijndael-256.
115+
* The numbers at the end refer to the block size. In Rijndael you can have a variable block size.
116+
* In AES you cannot. So, technically, rijndael-128 is AES, the rest are not.
117+
* Now, in both AES and Rijndael, you can have a variable key size. Rijndael supports
118+
* 128, 160, 192, 224 and 256 bit keys but mcrypt only supports 128, 192 and 256 bit keys.
119+
* Those are the same key sizes that AES supports but AES doesn't support variable block sizes so
120+
* we need a wrapper that restricts the key lengths as AES restricts them but doesn't restrict
121+
* the block size as AES restricts it.
122+
*
123+
* @package mcrypt_compat
124+
* @author Jim Wigginton <terrafrost@php.net>
125+
* @access public
126+
*/
127+
class phpseclib_mcrypt_rijndael extends Rijndael
128+
{
129+
/**
130+
* Sets the key.
131+
*
132+
* Rijndael supports five different key lengths, AES only supports three.
133+
*
134+
* @see Crypt_Rijndael:setKey()
135+
* @see setKeyLength()
136+
* @access public
137+
* @param string $key
138+
*/
139+
function setKey($key)
140+
{
141+
parent::setKey($key);
142+
143+
if (!$this->explicit_key_length) {
144+
$length = strlen($key);
145+
switch (true) {
146+
case $length <= 16:
147+
$this->key_length = 16;
148+
break;
149+
case $length <= 24:
150+
$this->key_length = 24;
151+
break;
152+
default:
153+
$this->key_length = 32;
154+
}
155+
$this->_setEngine();
156+
}
157+
}
158+
}
159+
111160
/**
112161
* Gets an array of all supported ciphers.
113162
*
@@ -206,14 +255,14 @@ function phpseclib_mcrypt_module_open($algorithm, $algorithm_directory, $mode, $
206255
}
207256
switch ($algorithm) {
208257
case 'rijndael-128':
209-
$cipher = new Rijndael($modeMap[$mode]);
258+
$cipher = new phpseclib_mcrypt_rijndael($modeMap[$mode]);
210259
$cipher->setBlockLength(128);
211260
break;
212261
case 'twofish':
213262
$cipher = new Twofish($modeMap[$mode]);
214263
break;
215264
case 'rijndael-192':
216-
$cipher = new Rijndael($modeMap[$mode]);
265+
$cipher = new phpseclib_mcrypt_rijndael($modeMap[$mode]);
217266
$cipher->setBlockLength(192);
218267
break;
219268
case 'blowfish-compat':
@@ -223,7 +272,7 @@ function phpseclib_mcrypt_module_open($algorithm, $algorithm_directory, $mode, $
223272
$cipher = new DES($modeMap[$mode]);
224273
break;
225274
case 'rijndael-256':
226-
$cipher = new Rijndael($modeMap[$mode]);
275+
$cipher = new phpseclib_mcrypt_rijndael($modeMap[$mode]);
227276
$cipher->setBlockLength(256);
228277
break;
229278
case 'blowfish':

tests/MCryptCompatTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ public function testAESBasicSuccess()
3333
$this->assertEquals($plaintext, $decrypted);
3434
}
3535

36+
public function testAESDiffKeyLength()
37+
{
38+
$key = str_repeat('z', 24);
39+
$iv = str_repeat('z', 16);
40+
41+
// a plaintext / ciphertext of length 1 is of an insufficient length for cbc mode
42+
$plaintext = str_repeat('a', 16);
43+
44+
$mcrypt = mcrypt_encrypt('rijndael-128', $key, $plaintext, 'cbc', $iv);
45+
$compat = phpseclib_mcrypt_encrypt('rijndael-128', $key, $plaintext, 'cbc', $iv);
46+
$this->assertEquals(bin2hex($mcrypt), bin2hex($compat));
47+
}
48+
3649
/**
3750
* @expectedException PHPUnit_Framework_Error_Warning
3851
*/
@@ -106,6 +119,59 @@ public function testNullPadding()
106119
$this->assertEquals($mcrypt, $compat);
107120
}
108121

122+
/**
123+
* valid AES key lengths are 128, 192 and 256-bit. if you pass in, say, a 160-bit key (20 bytes)
124+
* both phpseclib 1.0/2.0 and mcrypt will null pad 192-bits. at least with mcrypt_generic().
125+
*/
126+
public function testMiddleKey()
127+
{
128+
$key = str_repeat('z', 20);
129+
$iv = str_repeat('z', 16);
130+
131+
$plaintext = 'a';
132+
133+
$td = mcrypt_module_open('rijndael-128', '', 'cbc', '');
134+
mcrypt_generic_init($td, $key, $iv);
135+
$mcrypt = bin2hex(mcrypt_generic($td, 'This is very important data'));
136+
137+
$td = phpseclib_mcrypt_module_open('rijndael-128', '', 'cbc', '');
138+
phpseclib_mcrypt_generic_init($td, $key, $iv);
139+
$phpseclib = bin2hex(phpseclib_mcrypt_generic($td, 'This is very important data'));
140+
141+
$this->assertEquals($mcrypt, $phpseclib);
142+
}
143+
144+
/**
145+
* although mcrypt_generic() null pads keys mcrypt_encrypt() does not
146+
*
147+
* @requires PHP 5.6
148+
* @expectedException PHPUnit_Framework_Error_Warning
149+
*/
150+
public function testMiddleKey2()
151+
{
152+
$key = str_repeat('z', 20);
153+
$iv = str_repeat('z', 16);
154+
155+
$plaintext = 'a';
156+
157+
mcrypt_encrypt('rijndael-128', $key, $plaintext, 'cbc', $iv);
158+
}
159+
160+
/**
161+
* phpseclib_mcrypt_generic() behaves in the same way
162+
*
163+
* @expectedException PHPUnit_Framework_Error_Warning
164+
*/
165+
public function testMiddleKey3()
166+
{
167+
$key = str_repeat('z', 20);
168+
$iv = str_repeat('z', 16);
169+
170+
$plaintext = 'a';
171+
172+
phpseclib_mcrypt_encrypt('rijndael-128', $key, $plaintext, 'cbc', $iv);
173+
}
174+
109175
/**
110176
* adapted from the example at http://php.net/manual/en/filters.encryption.php
111177
*/

0 commit comments

Comments
 (0)