Skip to content

Commit

Permalink
Deal with character encoding better.
Browse files Browse the repository at this point in the history
  • Loading branch information
paragonie-security committed Jul 15, 2016
1 parent 8cf1557 commit 66bef16
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 20 deletions.
42 changes: 38 additions & 4 deletions src/Cookie.php
Expand Up @@ -2,10 +2,12 @@
declare(strict_types=1);
namespace ParagonIE\Halite;

use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\Halite\{
Alerts\InvalidMessage,
Symmetric\EncryptionKey,
Symmetric\Crypto
Symmetric\Config as SymmetricConfig,
Symmetric\Crypto,
Symmetric\EncryptionKey
};

/**
Expand Down Expand Up @@ -54,8 +56,13 @@ public function fetch(string $name)
return null;
}
try {
$decrypted = Crypto::decrypt($_COOKIE[$name], $this->key)
->getString();
$stored = new HiddenString($_COOKIE[$name]);
$config = self::getConfig($stored);
$decrypted = Crypto::decrypt(
$stored,
$this->key,
$config->ENCODING
)->getString();
if (empty($decrypted)) {
return null;
}
Expand All @@ -64,6 +71,33 @@ public function fetch(string $name)
return null;
}
}

/**
* Get the configuration for this version of halite
*
* @param HiddenString $stored A stored password hash
* @return SymmetricConfig
* @throws InvalidMessage
*/
protected static function getConfig(HiddenString $stored): SymmetricConfig
{
$stored = $stored->getString();
$length = Util::safeStrlen($stored);
// This doesn't even have a header.
if ($length < 8) {
throw new InvalidMessage(
'Encrypted password hash is way too short.'
);
}
if (\hash_equals(Util::safeSubstr($stored, 0, 5), Halite::VERSION_PREFIX)) {
return SymmetricConfig::getConfig(
Base64UrlSafe::decode($stored),
'encrypt'
);
}
$v = \Sodium\hex2bin(Util::safeSubstr($stored, 0, 8));
return SymmetricConfig::getConfig($v, 'encrypt');
}

/**
* Store a value in an encrypted cookie
Expand Down
29 changes: 14 additions & 15 deletions src/Password.php
Expand Up @@ -23,13 +23,13 @@ final class Password
* Hash then encrypt a password
*
* @param HiddenString $password The user's password
* @param EncryptionKey $secret_key The master key for all passwords
* @param EncryptionKey $secretKey The master key for all passwords
* @param string $level The security level for this password
* @return HiddenString An encrypted hash to store
*/
public static function hash(
HiddenString $password,
EncryptionKey $secret_key,
EncryptionKey $secretKey,
string $level = KeyFactory::INTERACTIVE
): HiddenString {
$kdfLimits = KeyFactory::getSecurityLevels($level);
Expand All @@ -43,37 +43,36 @@ public static function hash(
// Now let's encrypt the result
return Crypto::encrypt(
new HiddenString($hashed),
$secret_key
$secretKey
);
}

/**
* Is this password hash stale?
*
* @param HiddenString $stored Encrypted password hash
* @param EncryptionKey $secret_key The master key for all passwords
* @param EncryptionKey $secretKey The master key for all passwords
* @param string $level The security level for this password
* @return bool Do we need to regenerate the hash or
* ciphertext?
* @throws InvalidMessage
*/
public static function needsRehash(
HiddenString $stored,
EncryptionKey $secret_key,
EncryptionKey $secretKey,
string $level = KeyFactory::INTERACTIVE
): bool {
$config = self::getConfig($stored);
$v = \Sodium\hex2bin(Util::safeSubstr($stored->getString(), 0, 8));
if (!\hash_equals(Halite::HALITE_VERSION, $v)) {
// Outdated version of the library; Always rehash without decrypting
return true;
}
if (Util::safeStrlen($stored->getString()) < ($config->SHORTEST_CIPHERTEXT_LENGTH * 4 / 3)) {
throw new InvalidMessage('Encrypted password hash is too short.');
}

// First let's decrypt the hash
$hash_str = Crypto::decrypt($stored, $secret_key)->getString();
$hash_str = Crypto::decrypt(
$stored,
$secretKey,
$config->ENCODING
)->getString();

// Upon successful decryption, verify that we're using Argon2i
if (!\hash_equals(
Expand Down Expand Up @@ -122,7 +121,7 @@ protected static function getConfig(HiddenString $stored): SymmetricConfig
'Encrypted password hash is way too short.'
);
}
if (Util::safeSubstr($stored, 0, 5) === Halite::VERSION_PREFIX) {
if (\hash_equals(Util::safeSubstr($stored, 0, 5), Halite::VERSION_PREFIX)) {
return SymmetricConfig::getConfig(
Base64UrlSafe::decode($stored),
'encrypt'
Expand All @@ -137,22 +136,22 @@ protected static function getConfig(HiddenString $stored): SymmetricConfig
*
* @param HiddenString $password The user's password
* @param HiddenString $stored The encrypted password hash
* @param EncryptionKey $secret_key The master key for all passwords
* @param EncryptionKey $secretKey The master key for all passwords
* @return bool Is this password valid?
* @throws InvalidMessage
*/
public static function verify(
HiddenString $password,
HiddenString $stored,
EncryptionKey $secret_key
EncryptionKey $secretKey
): bool {
$config = self::getConfig($stored);
// Hex-encoded, so the minimum ciphertext length is double:
if (Util::safeStrlen($stored->getString()) < ($config->SHORTEST_CIPHERTEXT_LENGTH * 2)) {
throw new InvalidMessage('Encrypted password hash is too short.');
}
// First let's decrypt the hash
$hash_str = Crypto::decrypt($stored, $secret_key);
$hash_str = Crypto::decrypt($stored, $secretKey);
// Upon successful decryption, verify the password is correct
return \Sodium\crypto_pwhash_str_verify(
$hash_str->getString(),
Expand Down
2 changes: 2 additions & 0 deletions src/Symmetric/Config.php
Expand Up @@ -68,6 +68,7 @@ public static function getConfigEncrypt(int $major, int $minor): array
case 1:
case 0:
return [
'ENCODING' => Halite::ENCODE_HEX,
'SHORTEST_CIPHERTEXT_LENGTH' => 124,
'NONCE_BYTES' => \Sodium\CRYPTO_STREAM_NONCEBYTES,
'HKDF_SALT_LEN' => 32,
Expand All @@ -81,6 +82,7 @@ public static function getConfigEncrypt(int $major, int $minor): array
switch ($minor) {
case 0:
return [
'ENCODING' => Halite::ENCODE_BASE64URLSAFE,
'SHORTEST_CIPHERTEXT_LENGTH' => 124,
'NONCE_BYTES' => \Sodium\CRYPTO_STREAM_NONCEBYTES,
'HKDF_SALT_LEN' => 32,
Expand Down
2 changes: 1 addition & 1 deletion src/Symmetric/Crypto.php
Expand Up @@ -82,7 +82,7 @@ public static function decrypt(

// Split our keys
list($encKey, $authKey) = self::splitKeys($secretKey, $salt, $config);

// Check the MAC first
if (!self::verifyMAC(
$auth,
Expand Down

0 comments on commit 66bef16

Please sign in to comment.