Skip to content
This repository was archived by the owner on Jun 1, 2021. It is now read-only.

Commit

Permalink
As the ECB mode cannot use IVs, allow null IVs.
Browse files Browse the repository at this point in the history
  • Loading branch information
KiNgMaR authored and terrafrost committed Jan 13, 2018
1 parent 851b845 commit 034ee0e
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 70 deletions.
70 changes: 46 additions & 24 deletions lib/mcrypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,11 @@ function phpseclib_set_key(Base $td, $key)
*/
function phpseclib_set_iv(Base $td, $iv)
{
$length = $td->getBlockLength() >> 3;
$iv = str_pad(substr($iv, 0, $length), $length, "\0");
$td->setIV($iv);
if (phpseclib_mcrypt_module_is_iv_mode($td->mcrypt_mode)) {
$length = $td->getBlockLength() >> 3;
$iv = str_pad(substr($iv, 0, $length), $length, "\0");
$td->setIV($iv);
}
}

/**
Expand Down Expand Up @@ -317,6 +319,7 @@ function phpseclib_mcrypt_module_open($algorithm, $algorithm_directory, $mode, $
return false;
}

$cipher->mcrypt_mode = $mode;
$cipher->disablePadding();

return $cipher;
Expand Down Expand Up @@ -631,9 +634,7 @@ function phpseclib_mcrypt_enc_self_test(Base $td)
}

/**
* Returns the maximum supported keysize of the opened mode
*
* Gets the maximum supported key size of the algorithm in bytes.
* This function initializes all buffers needed for en/decryption.
*
* @param \phpseclib\Crypt\Base $td
* @param string $key
Expand All @@ -644,7 +645,7 @@ function phpseclib_mcrypt_enc_self_test(Base $td)
function phpseclib_mcrypt_generic_init(Base $td, $key, $iv)
{
$iv_size = phpseclib_mcrypt_enc_get_iv_size($td);
if (strlen($iv) != $iv_size) {
if (strlen($iv) != $iv_size && $td->mcrypt_mode != 'ecb') {
trigger_error('mcrypt_generic_init(): Iv size incorrect; supplied length: ' . strlen($iv) . ', needed: ' . $iv_size, E_USER_WARNING);
}
if (!strlen($key)) {
Expand Down Expand Up @@ -687,7 +688,7 @@ function phpseclib_mcrypt_generic_helper(Base $td, &$data, $op)
}

// phpseclib does not currently provide a way to retrieve the mode once it has been set via "public" methods
if ($td->mode == Base::MODE_CBC || $td->mode == Base::MODE_ECB) {
if (phpseclib_mcrypt_module_is_block_mode($td->mcrypt_mode)) {
$block_length = phpseclib_mcrypt_enc_get_iv_size($td);
$extra = strlen($data) % $block_length;
if ($extra) {
Expand Down Expand Up @@ -898,6 +899,23 @@ function phpseclib_mcrypt_module_is_block_mode($mode, $lib_dir = '')
return false;
}

/**
* Returns if the specified mode can use an IV or not
*
* @param string $mode
* @return bool
* @access private
*/
function phpseclib_mcrypt_module_is_iv_mode($mode)
{
switch ($mode) {
case 'ecb':
case 'stream':
return false;
}
return true;
}

/**
* This function runs a self test on the specified module
*
Expand Down Expand Up @@ -955,20 +973,24 @@ function phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, $op)
);
return false;
}
$iv_size = phpseclib_mcrypt_enc_get_iv_size($td);
if (!isset($iv) && $iv_size) {
trigger_error(
'mcrypt_' . $op . '(): Encryption mode requires an initialization vector of size ' . $iv_size,
E_USER_WARNING
);
return false;
}
if (strlen($iv) != $iv_size) {
trigger_error(
'mcrypt_' . $op . '(): Received initialization vector of size ' . strlen($iv) . ', but size ' . $iv_size . ' is required for this encryption mode',
E_USER_WARNING
);
return false;
if (phpseclib_mcrypt_module_is_iv_mode($mode)) {
$iv_size = phpseclib_mcrypt_enc_get_iv_size($td);
if (!isset($iv) && $iv_size) {
trigger_error(
'mcrypt_' . $op . '(): Encryption mode requires an initialization vector of size ' . $iv_size,
E_USER_WARNING
);
return false;
}
if (strlen($iv) != $iv_size) {
trigger_error(
'mcrypt_' . $op . '(): Received initialization vector of size ' . strlen($iv) . ', but size ' . $iv_size . ' is required for this encryption mode',
E_USER_WARNING
);
return false;
}
} else {
$iv = null;
}
phpseclib_mcrypt_generic_init($td, $key, $iv);
return $op == 'encrypt' ? phpseclib_mcrypt_generic($td, $data) : phpseclib_mdecrypt_generic($td, $data);
Expand Down Expand Up @@ -1067,7 +1089,7 @@ class phpseclib_mcrypt_filter extends php_user_filter
public function filter($in, $out, &$consumed, $closing)
{
$newlen = 0;
$block_mode = $this->cipher->mode == Base::MODE_CBC || $this->cipher->mode == Base::MODE_ECB;
$block_mode = phpseclib_mcrypt_module_is_block_mode($this->cipher->mcrypt_mode);
while ($bucket = stream_bucket_make_writeable($in)) {
if ($block_mode) {
$bucket->data = $this->buffer . $bucket->data;
Expand Down Expand Up @@ -1315,4 +1337,4 @@ function mcrypt_decrypt($cipher, $key, $data, $mode, $iv = null)
stream_filter_register('mcrypt.*', 'phpseclib_mcrypt_filter');
stream_filter_register('mdecrypt.*', 'phpseclib_mcrypt_filter');
//}
}
}
135 changes: 89 additions & 46 deletions tests/MCryptCompatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -773,78 +773,121 @@ public function test2DES()
$this->assertEquals(bin2hex($mcrypt), bin2hex($compat));
}

/**
* demonstrates how mcrypt deals with short IV's in stream mode
*/
public function testIVOnStream()
public function testMcryptGenericWithTwoParamsPHPPre71()
{
if (!extension_loaded('mcrypt')) {
$this->markTestSkipped('mcrypt must be demonstrate it\'s behaviors');
if (version_compare(PHP_VERSION, '7.1.0') >= 0) {
$this->markTestSkipped('PHPUnit_Framework_Error_Warning exception is thrown for legacy PHP versions only');
}

$this->setExpectedException('PHPUnit_Framework_Error_Warning');

$td = mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
mcrypt_generic_init($td, 'xxx', 'x');
$td = phpseclib_mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
phpseclib_mcrypt_generic_init($td, 'xxx');
}

/**
* demonstrates how phpseclib deals with short IV's in stream mode
*/
public function testIVOnStreamPHP()
public function testMcryptGenericWithTwoParamsPHPPost71()
{
$this->setExpectedException('PHPUnit_Framework_Error_Warning');
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
$this->markTestSkipped('ArgumentCountError exception is thrown for newer PHP versions only');
}

$this->setExpectedException('ArgumentCountError');

$td = phpseclib_mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
phpseclib_mcrypt_generic_init($td, 'xxx', 'x');
phpseclib_mcrypt_generic_init($td, 'xxx');
}

/**
* demonstrates how mcrypt deals with short IV's in ECB mode (eg. no warning thrown)
*
* @requires PHP 7.0
*/
public function testIVOnECB()
{
if (!extension_loaded('mcrypt')) {
$this->markTestSkipped('mcrypt must be demonstrate it\'s behaviors');
public function providerForIVSizeChecks()
{
$tests = [
[ '', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 8, '44448888', 8, false ],
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'generic', 8, '44448888', 8, false ],
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'generic', 0, '44448888', 8, 'Iv size incorrect; supplied length: 0, needed: 8' ],
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'generic', 4, '44448888', 8, 'Iv size incorrect; supplied length: 4, needed: 8' ],
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'generic', 0, '44448888', 0, false ],
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'generic', 4, '44448888', 0, 'Iv size incorrect; supplied length: 4, needed: 0' ],
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'generic', 8, '44448888', 0, 'Iv size incorrect; supplied length: 8, needed: 0' ],
[ '', MCRYPT_3DES, MCRYPT_MODE_ECB, 'decrypt', 0, '44448888', 8, false ],
[ '', MCRYPT_3DES, MCRYPT_MODE_ECB, 'decrypt', 4, '44448888', 8, false ],
[ '', MCRYPT_3DES, MCRYPT_MODE_ECB, 'decrypt', 8, '44448888', 8, false ],
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'decrypt', 8, '44448888', 8, false ],
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'decrypt', 0, '44448888', 0, false ],
// here comes a known, but acceptable difference between the ext and phpseclib:
[ 'compat', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 0, '44448888', 8, false ],
[ 'compat', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 4, '44448888', 8, false ],
[ 'ext', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 0, '44448888', 8, PHP_VERSION_ID >= 70000 ? false : 'Iv size incorrect; supplied length: 0, needed: 8' ],
[ 'ext', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 4, '44448888', 8, PHP_VERSION_ID >= 70000 ? false : 'Iv size incorrect; supplied length: 4, needed: 8' ],
];
if (PHP_VERSION_ID >= 56000) {
$tests+= [
// the following produce errors in older versions of PHP but stopped as of PHP 5.6+
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'decrypt', 4, '44448888', 0, false ],
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'decrypt', 8, '44448888', 0, false ],
// the following produced an error with a different message before PHP 5.6. mcrypt_compat uses the
// PHP 5.6+ error messages.
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'decrypt', 0, '44448888', 8, 'initialization vector of size 0, but size 8 is required' ],
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'decrypt', 4, '44448888', 8, 'initialization vector of size 4, but size 8 is required' ]
];
}

$all_tests = [];
foreach ($tests as $test) {
if (empty($test[0])) {
$test[0] = 'ext';
$all_tests[] = $test;
$test[0] = 'compat';
$all_tests[] = $test;
} else {
$all_tests[] = $test;
}
}

$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
mcrypt_generic_init($td, 'x', 'x');
return $all_tests;
}

/**
* demonstrates how phpseclib deals with short IV's in ECB mode (eg. no warning thrown)
* @dataProvider providerForIVSizeChecks
*/
public function testIVOnECBPHP()
public function testCompareIVSizeChecks($ext_or_compat, $cipher, $mode, $api, $input_iv_size, $input, $expected_iv_size, $expected_warning)
{
$td = phpseclib_mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
phpseclib_mcrypt_generic_init($td, 'x', 'x');
}
$this->assertSame(mcrypt_get_key_size($cipher, $mode), phpseclib_mcrypt_get_key_size($cipher, $mode));
$this->assertSame($expected_iv_size, mcrypt_get_iv_size($cipher, $mode));
$this->assertSame($expected_iv_size, phpseclib_mcrypt_get_iv_size($cipher, $mode));

public function testMcryptGenericWithTwoParamsPHPPre71()
{
if (version_compare(PHP_VERSION, '7.1.0') >= 0) {
$this->markTestSkipped('PHPUnit_Framework_Error_Warning exception is thrown for legacy PHP versions only');
$key = str_repeat('X', phpseclib_mcrypt_get_key_size($cipher, $mode));
$iv = str_repeat('Y', $input_iv_size);

if ($ext_or_compat === 'ext' && !extension_loaded('mcrypt')) {
$this->markTestSkipped('mcrypt extension not loaded');
}

$this->setExpectedException('PHPUnit_Framework_Error_Warning');
if ($expected_warning) {
$this->setExpectedException(PHPUnit_Framework_Error_Warning::class, $expected_warning);
}

$td = phpseclib_mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
phpseclib_mcrypt_generic_init($td, 'xxx');
if ($api === 'generic' && $ext_or_compat === 'ext') {
$td = mcrypt_module_open($cipher, '', $mode, '');
mcrypt_generic_init($td, $key, $iv);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
} elseif ($api === 'generic') {
$td = phpseclib_mcrypt_module_open($cipher, '', $mode, '');
phpseclib_mcrypt_generic_init($td, $key, $iv);
phpseclib_mcrypt_generic_deinit($td);
phpseclib_mcrypt_module_close($td);
} elseif ($ext_or_compat === 'ext') {
mcrypt_encrypt($cipher, $key, $input, $mode, $iv);
} else {
phpseclib_mcrypt_encrypt($cipher, $key, $input, $mode, $iv);
}
}

public function testMcryptGenericWithTwoParamsPHPPost71()
public function testTripleDESECBParameters()
{
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
$this->markTestSkipped('ArgumentCountError exception is thrown for newer PHP versions only');
}

$this->setExpectedException('ArgumentCountError');

$td = phpseclib_mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
phpseclib_mcrypt_generic_init($td, 'xxx');
$key_size = phpseclib_mcrypt_get_key_size(MCRYPT_3DES, MCRYPT_MODE_ECB);
$this->assertSame(24, $key_size);
$iv_size = phpseclib_mcrypt_get_iv_size(MCRYPT_3DES, MCRYPT_MODE_ECB);
$this->assertSame(8, $iv_size);
}

public function mcryptModuleNameProvider()
Expand Down

0 comments on commit 034ee0e

Please sign in to comment.