Skip to content

Commit

Permalink
10.0.1 (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmeyer2k committed Jul 1, 2019
1 parent 42aba37 commit e427000
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 121 deletions.
2 changes: 2 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
46 changes: 25 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<?php
// Decode the high entropy key
$key = "replace this with the output of: head -c 256 /dev/urandom | base64 -w 0 | xargs echo";

$encrypted = \Dcrypt\Aes256Gcm::encrypt("a secret", $key);
$encrypted = \Dcrypt\Aes256Gcm::encrypt('a secret', $key);

$plaintext = \Dcrypt\Aes256Gcm::decrypt($encrypted, $key);
```

**If in doubt, use this example and don't read any further!**

### Other AES-256 Modes

If you read to this point then you are an experienced cryptonaut, congrats :ok_hand: :metal:
If you read to this point then you are an experienced cryptonaut, congrats! :ok_hand: :metal:

Several AES-256 encryption modes are supported out of the box via hardcoded classes.

Expand All @@ -68,15 +72,15 @@ Several AES-256 encryption modes are supported out of the box via hardcoded clas
### Custom Encryption Suites

Dcrypt is compatible with _most_ OpenSSL ciphers and hashing algorithms supported by PHP.

Run `php examples/support.php` to view supported options.

#### Static Wrapper

Use any cipher/algo combination by calling the `OpensslStatic` class.

```php
<?php
$encrypted = \Dcrypt\OpensslStatic::encrypt("a secret", $key, 'des-ofb', 'md5');
$encrypted = \Dcrypt\OpensslStatic::encrypt('a secret', $key, 'des-ofb', 'md5');

$plaintext = \Dcrypt\OpensslStatic::decrypt($encrypted, $key, 'des-ofb', 'md5');
```
Expand All @@ -100,17 +104,17 @@ then...

```php
<?php
$encrypted = \BlowfishCrc::encrypt("a secret", $password);
$encrypted = \BlowfishCrc::encrypt('a secret', $password);

$plaintext = \BlowfishCrc::decrypt($encrypted, $password);
```

### Message Authenticity Checking
By default, a `\Dcrypt\Exceptions\InvalidChecksum` exception will be thrown before decryption is allowed to proceed when the supplied checksum is not valid.
By default, `\Dcrypt\Exceptions\InvalidChecksumException` exception will be raised before decryption is allowed to proceed when the supplied checksum is not valid.

```php
<?php
$encrypted = \Dcrypt\Aes256Gcm::encrypt("a secret", $key);
$encrypted = \Dcrypt\Aes256Gcm::encrypt('a secret', $key);

// Mangle the encrypted data by adding a single character
$encrypted = $encrypted . 'A';
Expand All @@ -135,7 +139,7 @@ The PBKDF2 cost can be defined in a custom class...
```php
<?php

class Aes256GcmWithCost extends \Dcrypt\OpensslBridge
class Aes256GcmWithCost extends \Dcrypt\Aes256Gcm
{
const COST = 1000000;
}
Expand All @@ -155,8 +159,8 @@ $plaintext = \Dcrypt\Aes256Gcm::decrypt($encrypted, $password, 10000);

Feeling especially paranoid?
Is the NSA monitoring your brainwaves?
Not sure which cipher method you can trust?
Why not try all of them?
Not sure which cipher methods and algos can be trusted?
Why not try all of them.

```php
<?php
Expand All @@ -167,7 +171,7 @@ $stack = (new \Dcrypt\OpensslStack($key))
->add('aes-256-ctr', 'sha384')
->add('aes-256-gcm', 'sha512');

$encrypted = $stack->encrypt("a secret");
$encrypted = $stack->encrypt('a secret');

$plaintext = $stack->decrypt($encrypted);
```
Expand All @@ -184,7 +188,7 @@ A fast symmetric stream cipher is quickly accessible with the `Otp` class.

```php
<?php
$encrypted = \Dcrypt\Otp::crypt("a secret", $key);
$encrypted = \Dcrypt\Otp::crypt('a secret', $key);

$plaintext = \Dcrypt\Otp::crypt($encrypted, $key);
```
Expand Down
2 changes: 1 addition & 1 deletion examples/Aes256Base64.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);

namespace Dcrypt\Examples;

Expand Down
2 changes: 1 addition & 1 deletion examples/TinyFish.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);

namespace Dcrypt\Examples;

Expand Down
74 changes: 54 additions & 20 deletions examples/support.php
Original file line number Diff line number Diff line change
@@ -1,25 +1,59 @@
<?php
<?php declare(strict_types=1);

error_reporting(0);

/**
* support.php
*
* Displays supported
*/

require __DIR__ . '/../vendor/autoload.php';

foreach (hash_algos() as $algo) {
foreach (openssl_get_cipher_methods() as $meth) {
echo str_pad("[$algo]", 20);
echo str_pad("[$meth]", 40);

try {
$e = \Dcrypt\OpensslStatic::encrypt('AAAA', 'BBBB', $meth, $algo);
$d = \Dcrypt\OpensslStatic::decrypt($e, 'BBBB', $meth, $algo);

echo " [pass] ";
} catch (\Exception $e) {
$m = $e->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";
}
}
6 changes: 5 additions & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
<directory>./tests/</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory>src</directory>
</whitelist>
</filter>
</phpunit>
9 changes: 8 additions & 1 deletion src/Exceptions/InvalidAlgoException.php
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
<?php
<?php declare(strict_types=1);

namespace Dcrypt\Exceptions;

class InvalidAlgoException extends \Exception
{

}
30 changes: 13 additions & 17 deletions src/OpensslKeyGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ final class OpensslKeyGenerator
/**
* OpensslKeyGenerator constructor.
*
* @param string $algo
* @param string $passkey
* @param string $cipher
* @param string $ivr
* @param int $cost
* @param string $algo Algo to use for PBKDF2 and HKDF
* @param string $passkey Password or key
* @param string $cipher Openssl cipher
* @param string $ivr Initialization vactor
* @param int $cost Cost value for PBKDF2
*/
public function __construct(string $algo, string $passkey, string $cipher, string $ivr, int $cost)
public function __construct(string $algo, string $passkey, string $cipher, string $ivr, int $cost = 0)
{
// When cost is 0 then we are in key mode
if ($cost === 0) {
Expand All @@ -66,15 +66,15 @@ public function __construct(string $algo, string $passkey, string $cipher, strin

// Make sure key was properly decoded and meets minimum required length
if (Str::strlen($passkey) < 256) {
throw new InvalidKeyException("Key must be at least 2048 bits long and base64 encoded.");
throw new InvalidKeyException("Key must be at least 256 bytes and base64 encoded.");
}

// Store the key as what was supplied
$this->key = $passkey;
} else {
// Make sure that the user is not attempting to use a key in password word mode
if (Str::strlen($passkey) >= 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
Expand Down Expand Up @@ -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));
}
}
22 changes: 11 additions & 11 deletions src/OpensslStatic.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
}
Expand All @@ -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
Expand All @@ -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;
}
}
Loading

0 comments on commit e427000

Please sign in to comment.