Skip to content

Commit 034ee0e

Browse files
KiNgMaRterrafrost
authored andcommitted
As the ECB mode cannot use IVs, allow null IVs.
1 parent 851b845 commit 034ee0e

File tree

2 files changed

+135
-70
lines changed

2 files changed

+135
-70
lines changed

lib/mcrypt.php

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,11 @@ function phpseclib_set_key(Base $td, $key)
180180
*/
181181
function phpseclib_set_iv(Base $td, $iv)
182182
{
183-
$length = $td->getBlockLength() >> 3;
184-
$iv = str_pad(substr($iv, 0, $length), $length, "\0");
185-
$td->setIV($iv);
183+
if (phpseclib_mcrypt_module_is_iv_mode($td->mcrypt_mode)) {
184+
$length = $td->getBlockLength() >> 3;
185+
$iv = str_pad(substr($iv, 0, $length), $length, "\0");
186+
$td->setIV($iv);
187+
}
186188
}
187189

188190
/**
@@ -317,6 +319,7 @@ function phpseclib_mcrypt_module_open($algorithm, $algorithm_directory, $mode, $
317319
return false;
318320
}
319321

322+
$cipher->mcrypt_mode = $mode;
320323
$cipher->disablePadding();
321324

322325
return $cipher;
@@ -631,9 +634,7 @@ function phpseclib_mcrypt_enc_self_test(Base $td)
631634
}
632635

633636
/**
634-
* Returns the maximum supported keysize of the opened mode
635-
*
636-
* Gets the maximum supported key size of the algorithm in bytes.
637+
* This function initializes all buffers needed for en/decryption.
637638
*
638639
* @param \phpseclib\Crypt\Base $td
639640
* @param string $key
@@ -644,7 +645,7 @@ function phpseclib_mcrypt_enc_self_test(Base $td)
644645
function phpseclib_mcrypt_generic_init(Base $td, $key, $iv)
645646
{
646647
$iv_size = phpseclib_mcrypt_enc_get_iv_size($td);
647-
if (strlen($iv) != $iv_size) {
648+
if (strlen($iv) != $iv_size && $td->mcrypt_mode != 'ecb') {
648649
trigger_error('mcrypt_generic_init(): Iv size incorrect; supplied length: ' . strlen($iv) . ', needed: ' . $iv_size, E_USER_WARNING);
649650
}
650651
if (!strlen($key)) {
@@ -687,7 +688,7 @@ function phpseclib_mcrypt_generic_helper(Base $td, &$data, $op)
687688
}
688689

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

902+
/**
903+
* Returns if the specified mode can use an IV or not
904+
*
905+
* @param string $mode
906+
* @return bool
907+
* @access private
908+
*/
909+
function phpseclib_mcrypt_module_is_iv_mode($mode)
910+
{
911+
switch ($mode) {
912+
case 'ecb':
913+
case 'stream':
914+
return false;
915+
}
916+
return true;
917+
}
918+
901919
/**
902920
* This function runs a self test on the specified module
903921
*
@@ -955,20 +973,24 @@ function phpseclib_mcrypt_helper($cipher, $key, $data, $mode, $iv, $op)
955973
);
956974
return false;
957975
}
958-
$iv_size = phpseclib_mcrypt_enc_get_iv_size($td);
959-
if (!isset($iv) && $iv_size) {
960-
trigger_error(
961-
'mcrypt_' . $op . '(): Encryption mode requires an initialization vector of size ' . $iv_size,
962-
E_USER_WARNING
963-
);
964-
return false;
965-
}
966-
if (strlen($iv) != $iv_size) {
967-
trigger_error(
968-
'mcrypt_' . $op . '(): Received initialization vector of size ' . strlen($iv) . ', but size ' . $iv_size . ' is required for this encryption mode',
969-
E_USER_WARNING
970-
);
971-
return false;
976+
if (phpseclib_mcrypt_module_is_iv_mode($mode)) {
977+
$iv_size = phpseclib_mcrypt_enc_get_iv_size($td);
978+
if (!isset($iv) && $iv_size) {
979+
trigger_error(
980+
'mcrypt_' . $op . '(): Encryption mode requires an initialization vector of size ' . $iv_size,
981+
E_USER_WARNING
982+
);
983+
return false;
984+
}
985+
if (strlen($iv) != $iv_size) {
986+
trigger_error(
987+
'mcrypt_' . $op . '(): Received initialization vector of size ' . strlen($iv) . ', but size ' . $iv_size . ' is required for this encryption mode',
988+
E_USER_WARNING
989+
);
990+
return false;
991+
}
992+
} else {
993+
$iv = null;
972994
}
973995
phpseclib_mcrypt_generic_init($td, $key, $iv);
974996
return $op == 'encrypt' ? phpseclib_mcrypt_generic($td, $data) : phpseclib_mdecrypt_generic($td, $data);
@@ -1067,7 +1089,7 @@ class phpseclib_mcrypt_filter extends php_user_filter
10671089
public function filter($in, $out, &$consumed, $closing)
10681090
{
10691091
$newlen = 0;
1070-
$block_mode = $this->cipher->mode == Base::MODE_CBC || $this->cipher->mode == Base::MODE_ECB;
1092+
$block_mode = phpseclib_mcrypt_module_is_block_mode($this->cipher->mcrypt_mode);
10711093
while ($bucket = stream_bucket_make_writeable($in)) {
10721094
if ($block_mode) {
10731095
$bucket->data = $this->buffer . $bucket->data;
@@ -1315,4 +1337,4 @@ function mcrypt_decrypt($cipher, $key, $data, $mode, $iv = null)
13151337
stream_filter_register('mcrypt.*', 'phpseclib_mcrypt_filter');
13161338
stream_filter_register('mdecrypt.*', 'phpseclib_mcrypt_filter');
13171339
//}
1318-
}
1340+
}

tests/MCryptCompatTest.php

Lines changed: 89 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -773,78 +773,121 @@ public function test2DES()
773773
$this->assertEquals(bin2hex($mcrypt), bin2hex($compat));
774774
}
775775

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

785782
$this->setExpectedException('PHPUnit_Framework_Error_Warning');
786783

787-
$td = mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
788-
mcrypt_generic_init($td, 'xxx', 'x');
784+
$td = phpseclib_mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
785+
phpseclib_mcrypt_generic_init($td, 'xxx');
789786
}
790787

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

798796
$td = phpseclib_mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
799-
phpseclib_mcrypt_generic_init($td, 'xxx', 'x');
797+
phpseclib_mcrypt_generic_init($td, 'xxx');
800798
}
801799

802-
/**
803-
* demonstrates how mcrypt deals with short IV's in ECB mode (eg. no warning thrown)
804-
*
805-
* @requires PHP 7.0
806-
*/
807-
public function testIVOnECB()
808-
{
809-
if (!extension_loaded('mcrypt')) {
810-
$this->markTestSkipped('mcrypt must be demonstrate it\'s behaviors');
800+
public function providerForIVSizeChecks()
801+
{
802+
$tests = [
803+
[ '', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 8, '44448888', 8, false ],
804+
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'generic', 8, '44448888', 8, false ],
805+
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'generic', 0, '44448888', 8, 'Iv size incorrect; supplied length: 0, needed: 8' ],
806+
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'generic', 4, '44448888', 8, 'Iv size incorrect; supplied length: 4, needed: 8' ],
807+
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'generic', 0, '44448888', 0, false ],
808+
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'generic', 4, '44448888', 0, 'Iv size incorrect; supplied length: 4, needed: 0' ],
809+
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'generic', 8, '44448888', 0, 'Iv size incorrect; supplied length: 8, needed: 0' ],
810+
[ '', MCRYPT_3DES, MCRYPT_MODE_ECB, 'decrypt', 0, '44448888', 8, false ],
811+
[ '', MCRYPT_3DES, MCRYPT_MODE_ECB, 'decrypt', 4, '44448888', 8, false ],
812+
[ '', MCRYPT_3DES, MCRYPT_MODE_ECB, 'decrypt', 8, '44448888', 8, false ],
813+
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'decrypt', 8, '44448888', 8, false ],
814+
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'decrypt', 0, '44448888', 0, false ],
815+
// here comes a known, but acceptable difference between the ext and phpseclib:
816+
[ 'compat', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 0, '44448888', 8, false ],
817+
[ 'compat', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 4, '44448888', 8, false ],
818+
[ 'ext', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 0, '44448888', 8, PHP_VERSION_ID >= 70000 ? false : 'Iv size incorrect; supplied length: 0, needed: 8' ],
819+
[ 'ext', MCRYPT_3DES, MCRYPT_MODE_ECB, 'generic', 4, '44448888', 8, PHP_VERSION_ID >= 70000 ? false : 'Iv size incorrect; supplied length: 4, needed: 8' ],
820+
];
821+
if (PHP_VERSION_ID >= 56000) {
822+
$tests+= [
823+
// the following produce errors in older versions of PHP but stopped as of PHP 5.6+
824+
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'decrypt', 4, '44448888', 0, false ],
825+
[ '', MCRYPT_ARCFOUR, MCRYPT_MODE_STREAM, 'decrypt', 8, '44448888', 0, false ],
826+
// the following produced an error with a different message before PHP 5.6. mcrypt_compat uses the
827+
// PHP 5.6+ error messages.
828+
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'decrypt', 0, '44448888', 8, 'initialization vector of size 0, but size 8 is required' ],
829+
[ '', MCRYPT_3DES, MCRYPT_MODE_CBC, 'decrypt', 4, '44448888', 8, 'initialization vector of size 4, but size 8 is required' ]
830+
];
831+
}
832+
833+
$all_tests = [];
834+
foreach ($tests as $test) {
835+
if (empty($test[0])) {
836+
$test[0] = 'ext';
837+
$all_tests[] = $test;
838+
$test[0] = 'compat';
839+
$all_tests[] = $test;
840+
} else {
841+
$all_tests[] = $test;
842+
}
811843
}
812844

813-
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
814-
mcrypt_generic_init($td, 'x', 'x');
845+
return $all_tests;
815846
}
816847

817848
/**
818-
* demonstrates how phpseclib deals with short IV's in ECB mode (eg. no warning thrown)
849+
* @dataProvider providerForIVSizeChecks
819850
*/
820-
public function testIVOnECBPHP()
851+
public function testCompareIVSizeChecks($ext_or_compat, $cipher, $mode, $api, $input_iv_size, $input, $expected_iv_size, $expected_warning)
821852
{
822-
$td = phpseclib_mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
823-
phpseclib_mcrypt_generic_init($td, 'x', 'x');
824-
}
853+
$this->assertSame(mcrypt_get_key_size($cipher, $mode), phpseclib_mcrypt_get_key_size($cipher, $mode));
854+
$this->assertSame($expected_iv_size, mcrypt_get_iv_size($cipher, $mode));
855+
$this->assertSame($expected_iv_size, phpseclib_mcrypt_get_iv_size($cipher, $mode));
825856

826-
public function testMcryptGenericWithTwoParamsPHPPre71()
827-
{
828-
if (version_compare(PHP_VERSION, '7.1.0') >= 0) {
829-
$this->markTestSkipped('PHPUnit_Framework_Error_Warning exception is thrown for legacy PHP versions only');
857+
$key = str_repeat('X', phpseclib_mcrypt_get_key_size($cipher, $mode));
858+
$iv = str_repeat('Y', $input_iv_size);
859+
860+
if ($ext_or_compat === 'ext' && !extension_loaded('mcrypt')) {
861+
$this->markTestSkipped('mcrypt extension not loaded');
830862
}
831863

832-
$this->setExpectedException('PHPUnit_Framework_Error_Warning');
864+
if ($expected_warning) {
865+
$this->setExpectedException(PHPUnit_Framework_Error_Warning::class, $expected_warning);
866+
}
833867

834-
$td = phpseclib_mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
835-
phpseclib_mcrypt_generic_init($td, 'xxx');
868+
if ($api === 'generic' && $ext_or_compat === 'ext') {
869+
$td = mcrypt_module_open($cipher, '', $mode, '');
870+
mcrypt_generic_init($td, $key, $iv);
871+
mcrypt_generic_deinit($td);
872+
mcrypt_module_close($td);
873+
} elseif ($api === 'generic') {
874+
$td = phpseclib_mcrypt_module_open($cipher, '', $mode, '');
875+
phpseclib_mcrypt_generic_init($td, $key, $iv);
876+
phpseclib_mcrypt_generic_deinit($td);
877+
phpseclib_mcrypt_module_close($td);
878+
} elseif ($ext_or_compat === 'ext') {
879+
mcrypt_encrypt($cipher, $key, $input, $mode, $iv);
880+
} else {
881+
phpseclib_mcrypt_encrypt($cipher, $key, $input, $mode, $iv);
882+
}
836883
}
837884

838-
public function testMcryptGenericWithTwoParamsPHPPost71()
885+
public function testTripleDESECBParameters()
839886
{
840-
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
841-
$this->markTestSkipped('ArgumentCountError exception is thrown for newer PHP versions only');
842-
}
843-
844-
$this->setExpectedException('ArgumentCountError');
845-
846-
$td = phpseclib_mcrypt_module_open(MCRYPT_ARCFOUR, '', MCRYPT_MODE_STREAM, '');
847-
phpseclib_mcrypt_generic_init($td, 'xxx');
887+
$key_size = phpseclib_mcrypt_get_key_size(MCRYPT_3DES, MCRYPT_MODE_ECB);
888+
$this->assertSame(24, $key_size);
889+
$iv_size = phpseclib_mcrypt_get_iv_size(MCRYPT_3DES, MCRYPT_MODE_ECB);
890+
$this->assertSame(8, $iv_size);
848891
}
849892

850893
public function mcryptModuleNameProvider()

0 commit comments

Comments
 (0)