You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
127 lines
4.4 KiB
127 lines
4.4 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace CodeHydrater\services\paragon_crypto;
|
|
|
|
/**
|
|
* @copyright Paragon Initiative Enterprises
|
|
* @license Creative Commons Attribution 4.0 International / ISC
|
|
* @link https://github.com/paragonie/pecl-libsodium-doc/blob/master/chapters/09-recipes.md
|
|
*/
|
|
|
|
class password_storage {
|
|
|
|
const SALT_SIZE_IN_BYTES = 16;
|
|
private $random_engine;
|
|
|
|
public function __construct() {
|
|
$this->random_engine = new \tts\random_engine();
|
|
}
|
|
|
|
public function generate_a_key(): string {
|
|
return sodium_bin2hex($this->random_engine->get_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES));
|
|
}
|
|
|
|
/**
|
|
* Hash then encrypt a password
|
|
*
|
|
* @param string $password - The user's password
|
|
* @param string $secret_key - The main_magic key for all passwords
|
|
* @return string
|
|
*/
|
|
public function hash(#[\SensitiveParameter] string $password, #[\SensitiveParameter] string $secret_key): string {
|
|
// First, let's calculate the hash
|
|
$hashed = sodium_crypto_pwhash_scryptsalsa208sha256_str(
|
|
$password,
|
|
SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE,
|
|
SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE
|
|
);
|
|
$salt = $this->random_engine->get_bytes(self::SALT_SIZE_IN_BYTES);
|
|
list ($enc_key, $auth_key) = $this->split_keys($secret_key, sodium_bin2hex($salt));
|
|
sodium_memzero($secret_key);
|
|
sodium_memzero($password);
|
|
$nonce = $this->random_engine->get_bytes(
|
|
SODIUM_CRYPTO_STREAM_NONCEBYTES
|
|
);
|
|
$cipher_text = sodium_crypto_stream_xor(
|
|
$hashed,
|
|
$nonce,
|
|
$enc_key
|
|
);
|
|
|
|
$mac = sodium_crypto_auth($nonce . $cipher_text, $auth_key);
|
|
sodium_memzero($enc_key);
|
|
sodium_memzero($auth_key);
|
|
|
|
return sodium_bin2hex($mac . $nonce . $salt . $cipher_text);
|
|
}
|
|
|
|
/**
|
|
* Decrypt then verify a password
|
|
*
|
|
* @param string $password - The user-provided password
|
|
* @param string $stored - The encrypted password hash
|
|
* @param string $secret_key - The main_magic key for all passwords
|
|
*/
|
|
public function verify(#[\SensitiveParameter] string $password, string $stored, #[\SensitiveParameter] string $secret_key): bool {
|
|
$data = sodium_hex2bin($stored);
|
|
$mac = mb_substr(
|
|
$data,
|
|
0,
|
|
SODIUM_CRYPTO_AUTH_BYTES,
|
|
'8bit'
|
|
);
|
|
$nonce = mb_substr(
|
|
$data,
|
|
SODIUM_CRYPTO_AUTH_BYTES,
|
|
SODIUM_CRYPTO_STREAM_NONCEBYTES,
|
|
'8bit'
|
|
);
|
|
$salt = mb_substr(
|
|
$data,
|
|
SODIUM_CRYPTO_AUTH_BYTES + SODIUM_CRYPTO_STREAM_NONCEBYTES,
|
|
self::SALT_SIZE_IN_BYTES,
|
|
'8bit'
|
|
);
|
|
$cipher_text = mb_substr(
|
|
$data,
|
|
SODIUM_CRYPTO_AUTH_BYTES + SODIUM_CRYPTO_STREAM_NONCEBYTES + self::SALT_SIZE_IN_BYTES,
|
|
null,
|
|
'8bit'
|
|
);
|
|
list ($enc_key, $auth_key) = $this->split_keys($secret_key, sodium_bin2hex($salt));
|
|
|
|
if (sodium_crypto_auth_verify($mac, $nonce . $cipher_text, $auth_key)) {
|
|
sodium_memzero($auth_key);
|
|
$hash_str = sodium_crypto_stream_xor($cipher_text, $nonce, $enc_key);
|
|
sodium_memzero($enc_key);
|
|
if ($hash_str !== false) {
|
|
$ret = sodium_crypto_pwhash_scryptsalsa208sha256_str_verify($hash_str, $password);
|
|
sodium_memzero($password);
|
|
return $ret;
|
|
}
|
|
} else {
|
|
sodium_memzero($auth_key);
|
|
sodium_memzero($enc_key);
|
|
}
|
|
throw new \Exception('Decryption failed.');
|
|
}
|
|
|
|
/**
|
|
* @return array(2) [encryption key, authentication key]
|
|
*/
|
|
private function split_keys(#[\SensitiveParameter] string $secret_key, #[\SensitiveParameter] string $salt): array {
|
|
$enc_key = hash_hkdf('sha256', $secret_key, SODIUM_CRYPTO_STREAM_KEYBYTES, 'encryption', $salt);
|
|
$auth_key = hash_hkdf('sha256', $secret_key, SODIUM_CRYPTO_AUTH_KEYBYTES, 'authentication', $salt);
|
|
return [$enc_key, $auth_key];
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
$c = new \tts\services\paragon_crypto\password_storage();
|
|
$k = $c->generate_a_key();
|
|
$h = $c->hash("HelpMe", $k);
|
|
var_dump( $c->verify("HelpMe", $h, $k) );
|
|
*/
|
|
|