Skip to content

Commit

Permalink
Merge pull request #23 from paragonie/v3.1-dev
Browse files Browse the repository at this point in the history
Version 3.1.0
  • Loading branch information
paragonie-security authored Jun 2, 2021
2 parents 31de911 + e92e825 commit 1d6b1cd
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 37 deletions.
33 changes: 1 addition & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,13 @@ name: CI
on: [push]

jobs:
old:
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: ['ubuntu-16.04']
php-versions: ['7.0']
phpunit-versions: ['7.5.20']
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, intl
ini-values: post_max_size=256M, max_execution_time=180
tools: psalm, phpunit:${{ matrix.phpunit-versions }}

- name: Fix permissions
run: sudo chmod -R 0777 .

- name: Install dependencies
run: composer self-update --1; composer install

- name: PHPUnit tests
uses: php-actions/phpunit@v2
with:
memory_limit: 256M

moderate:
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: ['ubuntu-latest']
php-versions: ['7.1', '7.2', '7.3']
php-versions: ['7.3']
phpunit-versions: ['latest']
steps:
- name: Checkout
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# Version 3.1.0 (2021-06-02)

* Added `needsRehash()` method.
* Added support for `$hashOptions` in `hashAndEncrypt()` to support
custom bcrypt costs. (This can also be used to support custom Argon2id
parameters, should the default ever change in PHP.)

# Version 3.0.3 (2021-06-02)

* Support PHP 8.
* The previous tag (v3.0.2) was erroneous and erased.

# Version 3.0.1 (2016-05-20)

* Fixed `autoload.php`

# Version 3.0.0 (2016-05-18)

* Set minimum PHP version to 7.0
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ if (isset($_POST['password'])) {
}
```

### Determine if a re-hash is necessary

```php
use ParagonIE\PasswordLock\PasswordLock;
/**
* @var string $encryptedPwhash
* @var Defuse\Crypto\Key $key
*/

if (PasswordLock::needsRehash($encryptedPwhash, $key)) {
// Recalculate PasswordLock::hashAndEncrypt()
}
```

### Re-encrypt a hash with a different encryption key

```php
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
]
},
"require": {
"php": "^7|^8",
"php": "^7.3|^8",
"defuse/php-encryption": "^2",
"paragonie/constant_time_encoding": "^2"
},
Expand Down
78 changes: 74 additions & 4 deletions src/PasswordLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,47 @@
*/
class PasswordLock
{
/**
* @ref https://www.php.net/manual/en/function.password-hash.php
*/
const OPTIONS_DEFAULT_BCRYPT = ['cost' => 12];
const OPTIONS_DEFAULT_ARGON2ID = [
'memory_cost' => 65536,
'time_cost' => 4,
'threads' => 1
];

/**
* 1. Hash password using bcrypt-base64-SHA256
* 2. Encrypt-then-MAC the hash
*
* @param string $password
* @param Key $aesKey
* @param ?array $hashOptions
* @return string
*
* @throws EnvironmentIsBrokenException
* @throws \InvalidArgumentException
* @psalm-suppress InvalidArgument
*/
public static function hashAndEncrypt(string $password, Key $aesKey): string
{
public static function hashAndEncrypt(
string $password,
Key $aesKey,
?array $hashOptions = null
): string {
if (is_null($hashOptions)) {
$hashOptions = static::getDefaultOptions();
}
if (array_key_exists('salt', $hashOptions)) {
throw new \InvalidArgumentException('Explicit salts are unsupported.');
}
/** @var string $hash */
$hash = \password_hash(
Base64::encode(
\hash('sha384', $password, true)
),
PASSWORD_DEFAULT
PASSWORD_DEFAULT,
$hashOptions
);
if (!\is_string($hash)) {
throw new EnvironmentIsBrokenException("Unknown hashing error.");
Expand All @@ -53,7 +75,11 @@ public static function hashAndEncrypt(string $password, Key $aesKey): string
* @throws EnvironmentIsBrokenException
* @throws WrongKeyOrModifiedCiphertextException
*/
public static function decryptAndVerifyLegacy(string $password, string $ciphertext, string $aesKey): bool
public static function decryptAndVerifyLegacy(
string $password,
string $ciphertext,
string $aesKey
): bool
{
if (Binary::safeStrlen($aesKey) !== 16) {
throw new \InvalidArgumentException("Encryption keys must be 16 bytes long");
Expand Down Expand Up @@ -102,6 +128,50 @@ public static function decryptAndVerify(string $password, string $ciphertext, Ke
);
}

/**
* @return array<string, int>
*
* @psalm-suppress TypeDoesNotContainType
*/
protected static function getDefaultOptions(): array
{
// Future-proofing:
if (PASSWORD_DEFAULT === PASSWORD_ARGON2ID) {
return self::OPTIONS_DEFAULT_ARGON2ID;
}
return self::OPTIONS_DEFAULT_BCRYPT;
}

/**
* Decrypt the ciphertext and ascertain if the stored password needs to be rehashed?
*
* @param string $ciphertext
* @param Key $aesKey
* @param ?array $hashOptions
* @return bool
*
* @throws EnvironmentIsBrokenException
* @throws WrongKeyOrModifiedCiphertextException
*/
public static function needsRehash(
string $ciphertext,
Key $aesKey,
?array $hashOptions = null
): bool {
if (is_null($hashOptions)) {
$hashOptions = static::getDefaultOptions();
}
$hash = Crypto::decrypt(
$ciphertext,
$aesKey
);
if (!\is_string($hash)) {
throw new EnvironmentIsBrokenException("Unknown hashing error.");
}
/** @psalm-suppress InvalidArgument */
return password_needs_rehash($hash, PASSWORD_DEFAULT, $hashOptions);
}

/**
* Key rotation method -- decrypt with your old key then re-encrypt with your new key
*
Expand Down
9 changes: 9 additions & 0 deletions tests/PasswordLockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,13 @@ public function testBitflip()
}
$this->assertTrue($failed, 'Bitflips should break the decryption');
}

public function testNeedsRehash()
{
$lowCost = ['cost' => 8];
$key = Key::createNewRandomKey();
$password = PasswordLock::hashAndEncrypt('YELLOW SUBMARINE', $key, $lowCost);
$this->assertTrue(PasswordLock::needsRehash($password, $key));
$this->assertFalse(PasswordLock::needsRehash($password, $key, $lowCost));
}
}

0 comments on commit 1d6b1cd

Please sign in to comment.