From e427000e8bf636c2cb58323b259f70a8d57c1984 Mon Sep 17 00:00:00 2001 From: mmeyer2k Date: Mon, 1 Jul 2019 00:07:01 -0500 Subject: [PATCH] 10.0.1 (#20) --- .circleci/config.yml | 2 + README.md | 46 ++++++++------- examples/Aes256Base64.php | 2 +- examples/TinyFish.php | 2 +- examples/support.php | 74 ++++++++++++++++++------- phpunit.xml | 6 +- src/Exceptions/InvalidAlgoException.php | 9 ++- src/OpensslKeyGenerator.php | 30 +++++----- src/OpensslStatic.php | 22 ++++---- tests/OpensslKeyGeneratorTest.php | 25 +++++++++ tests/OpensslStackTest.php | 26 +-------- tests/helpers/AesBase.php | 47 ++++++++-------- 12 files changed, 170 insertions(+), 121 deletions(-) create mode 100644 tests/OpensslKeyGeneratorTest.php diff --git a/.circleci/config.yml b/.circleci/config.yml index d53b7cf5..f0d81f5d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,6 +9,7 @@ jobs: - run: mkdir -p coverage - run: php vendor/phpunit/phpunit/phpunit --coverage-html coverage - run: php vendor/infection/infection/bin/infection + - run: php examples/support.php - store_artifacts: path: coverage - store_artifacts: @@ -23,6 +24,7 @@ jobs: - run: composer require phpunit/phpunit infection/infection - run: php vendor/phpunit/phpunit/phpunit --coverage-html coverage - run: php vendor/infection/infection/bin/infection + - run: php examples/support.php - store_artifacts: path: coverage - store_artifacts: diff --git a/README.md b/README.md index 2b650482..3a3fe414 100644 --- a/README.md +++ b/README.md @@ -25,36 +25,40 @@ composer require "mmeyer2k/dcrypt=^10.0" ``` # Features + ## Block Ciphers -Dcrypt helps application developers avoid common mistakes in crypto implementations that leave data at risk while providing flexibility in its options. -Dcrypt strives to make correct usage simple, but it is possible to use dcrypt incorrectly. -__NOTE__: Dcrypt's default configurations assume the usage of a base64 encoded high entropy key with a minimum of 2048 bits. -Be sure to read the section on key hardening and pay close attention to the diffences between `$key` and `$password`. -To quickly generate a strong key execute this command line: +The dcrypt library helps application developers avoid common mistakes in crypto implementations that leave data at risk while still providing flexibility in its options for crypto enthusiasts. +Dcrypt strives to make correct usage simple, but it _is_ possible to use dcrypt incorrectly. + +__NOTE__: Dcrypt's default configurations assume the usage of a base64 encoded high entropy key with a minimum of 256 bytes. +Be sure to read the section on key hardening and pay close attention to the differences between `$key` and `$password`. + +To generate a strong new key execute this command line: + ```bash head -c 256 /dev/urandom | base64 -w 0 | xargs echo ``` ### AES-256 GCM Encryption -PHP 7.1 ships with support for new AEAD encryption modes, GCM being considered the safest of these. -An AEAD authentication tag combined with SHA-256 HMAC ensures encrypted messages can not be forged or altered. -**When in doubt, use this example and don't read any further!** +PHP 7.1 ships with support for new AEAD encryption modes, GCM being considered the safest of these. +Dcrypt will handle the 32 bit AEAD authentication tag, SHA-256 HMAC and initialization vector as a single string. ```php add('aes-256-ctr', 'sha384') ->add('aes-256-gcm', 'sha512'); -$encrypted = $stack->encrypt("a secret"); +$encrypted = $stack->encrypt('a secret'); $plaintext = $stack->decrypt($encrypted); ``` @@ -184,7 +188,7 @@ A fast symmetric stream cipher is quickly accessible with the `Otp` class. ```php getMessage(); - echo " [fail] [$m]"; - } catch (\Error $e) { - $m = $e->getMessage(); - echo " [fail] [$m]"; - } finally { - echo "\n"; - } +$key = \Dcrypt\OpensslKeyGenerator::newKey(); + +echo "\nCIPHERS ----------------------------------------------------------------------------------------------\n"; + +foreach (\openssl_get_cipher_methods() as $meth) { + // Only process the lower case names + if (\strtolower($meth) !== $meth) { + continue; + } + + echo \str_pad("[$meth]", 40); + + try { + $e = \Dcrypt\OpensslStatic::encrypt('AAAA', $key, $meth, 'sha256'); + $d = \Dcrypt\OpensslStatic::decrypt($e, $key, $meth, 'sha256'); + + echo " [pass] "; + } catch (\Exception|\Error $e) { + $m = $e->getMessage(); + echo " [fail] [$m]"; + } finally { + echo "\n"; + } +} + +echo "\nALGOS ------------------------------------------------------------------------------------------------\n"; + +foreach (\hash_algos() as $algo) { + // Only process the lower case names + if (\strtolower($algo) !== $algo) { + continue; + } + + echo \str_pad("[$algo]", 40); + + try { + $e = \Dcrypt\OpensslStatic::encrypt('AAAA', $key, 'aes-256-gcm', $algo); + $d = \Dcrypt\OpensslStatic::decrypt($e, $key, 'aes-256-gcm', $algo); + + echo " [pass] "; + } catch (\Exception|\Error $e) { + $m = $e->getMessage(); + echo " [fail] [$m]"; + } finally { + echo "\n"; } } diff --git a/phpunit.xml b/phpunit.xml index 831a5052..5e0cae25 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -14,5 +14,9 @@ ./tests/ - + + + src + + diff --git a/src/Exceptions/InvalidAlgoException.php b/src/Exceptions/InvalidAlgoException.php index 6fd199e6..9e4720b6 100644 --- a/src/Exceptions/InvalidAlgoException.php +++ b/src/Exceptions/InvalidAlgoException.php @@ -1 +1,8 @@ -= 256) { - throw new InvalidPasswordException("Passwords must be less than 2048 bits (256 bytes) long."); + throw new InvalidPasswordException("Passwords must be less than 256 bytes."); } // Derive the key from the password and store in object @@ -134,20 +134,16 @@ public function deriveKey(string $info): string /** * Generate a new key that meets requirements for dcrypt * - * @param int $size + * @param int $size Size of key in bytes * @return string * @throws Exceptions\InvalidKeyException */ - public static function newKey(int $size = 2048): string + public static function newKey(int $bytes = 256): string { - if ($size < 2048) { - throw new InvalidKeyException('Key must be at least 2048 bits long.'); + if ($bytes < 256) { + throw new InvalidKeyException('Key must be at least 256 bytes long.'); } - if ($size % 8 !== 0) { - throw new InvalidKeyException('Key must be divisible by 8.'); - } - - return \base64_encode(\random_bytes($size / 8)); + return \base64_encode(\random_bytes($bytes)); } } \ No newline at end of file diff --git a/src/OpensslStatic.php b/src/OpensslStatic.php index 6f95667c..e81d4ad2 100644 --- a/src/OpensslStatic.php +++ b/src/OpensslStatic.php @@ -47,25 +47,25 @@ public static function decrypt(string $data, string $passkey, string $cipher, st // Ask openssl for the IV size needed for specified cipher $isz = parent::ivSize($cipher); - // Find the IV at the beginning of the cypher text + // Get the IV at the beginning of the ciphertext $ivr = Str::substr($data, 0, $isz); - // Gather the checksum portion of the ciphertext + // Get the checksum after the IV $sum = Str::substr($data, $isz, $hsz); - // Gather the GCM/CCM authentication tag + // Get the AEAD authentication tag (if present) after the checksum $tag = Str::substr($data, $isz + $hsz, $tsz); - // Gather message portion of ciphertext after iv and checksum + // Get the encrypted message payload $msg = Str::substr($data, $isz + $hsz + $tsz); - // Create password derivation object + // Create a new password derivation object $key = new OpensslKeyGenerator($algo, $passkey, $cipher, $ivr, $cost); - // Calculate verification checksum + // Calculate checksum of message payload for verification $chk = \hash_hmac($algo, $msg, $key->authenticationKey(), true); - // Verify HMAC before decrypting + // Compare given checksum against computed checksum using a time-safe function if (!Str::equal($chk, $sum)) { throw new Exceptions\InvalidChecksumException('Decryption can not proceed due to invalid cyphertext checksum.'); } @@ -87,7 +87,7 @@ public static function decrypt(string $data, string $passkey, string $cipher, st */ public static function encrypt(string $data, string $passkey, string $cipher, string $algo, int $cost = 0): string { - // Generate IV of appropriate size. + // Generate IV of appropriate size $ivr = parent::ivGenerate($cipher); // Create password derivation object @@ -96,13 +96,13 @@ public static function encrypt(string $data, string $passkey, string $cipher, st // Create a placeholder for the authentication tag to be passed by reference $tag = ''; - // Encrypt the plaintext data + // Encrypt the plaintext $msg = parent::openssl_encrypt($data, $cipher, $key->encryptionKey(), $ivr, $tag); - // Generate the ciphertext checksum to prevent message forging + // Generate the ciphertext checksum $chk = \hash_hmac($algo, $msg, $key->authenticationKey(), true); - // Return iv + checksum + tag + cyphertext + // Return iv + checksum + tag + ciphertext return $ivr . $chk . $tag . $msg; } } diff --git a/tests/OpensslKeyGeneratorTest.php b/tests/OpensslKeyGeneratorTest.php new file mode 100644 index 00000000..635518e8 --- /dev/null +++ b/tests/OpensslKeyGeneratorTest.php @@ -0,0 +1,25 @@ +expectException(InvalidKeyException::class); + + \Dcrypt\OpensslKeyGenerator::newKey(128); + } + + public function testKeyWithCostException() + { + $key = \Dcrypt\OpensslKeyGenerator::newKey(256); + + $this->expectException(InvalidPasswordException::class); + + new \Dcrypt\OpensslKeyGenerator('sha256', $key, 'aes-256-gcm', \random_bytes(128), 10000); + } +} diff --git a/tests/OpensslStackTest.php b/tests/OpensslStackTest.php index 550d2153..d8c2a27f 100644 --- a/tests/OpensslStackTest.php +++ b/tests/OpensslStackTest.php @@ -4,7 +4,6 @@ class OpensslStackTest extends \PHPUnit\Framework\TestCase { public function testAes256StackWithPassword() { - // Test all AES 256 modes with sha512 a ton of times. DONT JUDGE ME =) $stack = (new \Dcrypt\OpensslStack('password', 10000)) ->add('rc4-40', 'md2') ->add('bf-cbc', 'sha256') @@ -19,27 +18,7 @@ public function testAes256StackWithPassword() ->add('aes-256-ctr', 'sha512') ->add('aes-256-cfb', 'sha512') ->add('aes-256-ofb', 'sha512') - ->add('aes-256-gcm', 'sha512'); # save best for last (outter-most) - - $encrypted = $stack->encrypt("a secret"); - - $plaintext = $stack->decrypt($encrypted); - - $this->assertEquals("a secret", $plaintext); - } - - /* - public function testEveryCombinationStackWithKey() - { - $key = \Dcrypt\OpensslKeyGenerator::newKey(); - - $stack = (new \Dcrypt\OpensslStack($key)); - - foreach (\Dcrypt\OpensslSupported::algos() as $algo) { - foreach (\Dcrypt\OpensslSupported::ciphers() as $cipher) { - $stack->add($cipher, $algo); - } - } + ->add('aes-256-gcm', 'sha512'); $encrypted = $stack->encrypt("a secret"); @@ -47,9 +26,8 @@ public function testEveryCombinationStackWithKey() $this->assertEquals("a secret", $plaintext); } - */ - public function testAes256StackWithKey() + public function testAes256StackWithKeyFromReadmeFile() { $key = \Dcrypt\OpensslKeyGenerator::newKey(); diff --git a/tests/helpers/AesBase.php b/tests/helpers/AesBase.php index 1bb78f79..f62fd27e 100644 --- a/tests/helpers/AesBase.php +++ b/tests/helpers/AesBase.php @@ -2,32 +2,27 @@ class AesBase extends \PHPUnit\Framework\TestCase { - public static $input = 'AAAAAAAA'; public static $password = 'BBBBBBBBCCCCCCCC'; - public static $key = ' - QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB - QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB - QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB - QUFBQQ== - '; - - public function testEngineWithPassword() + + public function testEngineInPasswordMode() { - $encrypted = static::$class::encrypt(self::$input, self::$password, 10000); + $encrypted = static::$class::encrypt('a secret', self::$password, 10000); $decrypted = static::$class::decrypt($encrypted, self::$password, 10000); - $this->assertEquals(self::$input, $decrypted); + $this->assertEquals('a secret', $decrypted); } - public function testEngineWithKey() + public function testEngineInKeyMode() { - $encrypted = static::$class::encrypt(self::$input, self::$key); - $decrypted = static::$class::decrypt($encrypted, self::$key); + $key = \Dcrypt\OpensslKeyGenerator::newKey(); + + $encrypted = static::$class::encrypt('a secret', $key); + $decrypted = static::$class::decrypt($encrypted, $key); - $this->assertEquals(self::$input, $decrypted); + $this->assertEquals('a secret', $decrypted); } - public function testEngineWithSomeRandomness() + public function testEngineWithSomeRandomnessWhileInKeyMode() { $input = \random_bytes(256); $key = \Dcrypt\OpensslKeyGenerator::newKey(); @@ -38,15 +33,17 @@ public function testEngineWithSomeRandomness() $this->assertEquals($input, $decrypted); } - public function testCorruptDataUsingKey() + public function testCorruptDataUsingKeyMode() { - $encrypted = static::$class::encrypt(self::$input, self::$key); + $key = \Dcrypt\OpensslKeyGenerator::newKey(); + + $encrypted = static::$class::encrypt('a secret', $key); - $this->assertEquals(self::$input, static::$class::decrypt($encrypted, self::$key)); + $this->assertEquals('a secret', static::$class::decrypt($encrypted, $key)); $this->expectException(\Dcrypt\Exceptions\InvalidChecksumException::class); - static::$class::decrypt($encrypted . 'A', self::$key); + static::$class::decrypt($encrypted . 'A', $key); } public function testInvalidKeyEncoding() @@ -55,17 +52,19 @@ public function testInvalidKeyEncoding() $crazyKey = str_repeat('?', 10000); - static::$class::encrypt(self::$input, $crazyKey); + static::$class::encrypt('a secret', $crazyKey); } public function testCorruptDataUsingPassword() { - $encrypted = static::$class::encrypt(self::$input, self::$key); + $key = \Dcrypt\OpensslKeyGenerator::newKey(); + + $encrypted = static::$class::encrypt('a secret', $key); - $this->assertEquals(self::$input, static::$class::decrypt($encrypted, self::$key)); + $this->assertEquals('a secret', static::$class::decrypt($encrypted, $key)); $this->expectException(\Dcrypt\Exceptions\InvalidChecksumException::class); - static::$class::decrypt($encrypted . 'A', self::$key); + static::$class::decrypt($encrypted . 'A', $key); } }