Skip to content

Commit

Permalink
Use HKDF info parameter instead of salt for randomness
Browse files Browse the repository at this point in the history
  • Loading branch information
paragonie-security committed Jan 19, 2022
1 parent 7d6cdc8 commit 1bbf8ac
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 83 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
group element, but that isn't necessarily a uniformly random bit string.
* **Security:** Halite v5 uses the [PAE](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Common.md#pae-definition)
strategy from PASETO to prevent canonicalization attacks.
* **Security:** Halite v5 appends the random salt to HKDF's `info` parameter instead of
the `salt` parameter. This allows us to meet the KDF Security Definition (which is
stronger than a mere Pseudo-Random Function).

## Version 4.8.0 (2021-04-18)

Expand Down
4 changes: 0 additions & 4 deletions src/Asymmetric/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* @property string|bool ENCODING
* @property string HASH_DOMAIN_SEPARATION
* @property bool HASH_SCALARMULT
*/
final class Config extends BaseConfig
{
Expand Down
9 changes: 8 additions & 1 deletion src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,24 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* @property string|bool ENCODING
*
* AsymmetricCrypto:
* @property string HASH_DOMAIN_SEPARATION
* @property bool HASH_SCALARMULT
*
* SymmetricCrypto:
* @property bool CHECKSUM_PUBKEY
* @property int BUFFER
* @property int HASH_LEN
* @property string|bool ENCODING
* @property int SHORTEST_CIPHERTEXT_LENGTH
* @property int NONCE_BYTES
* @property int HKDF_SALT_LEN
* @property string ENC_ALGO
* @property string MAC_ALGO
* @property int MAC_SIZE
* @property int PUBLICKEY_BYTES
* @property bool HKDF_USE_INFO
* @property string HKDF_SBOX
* @property string HKDF_AUTH
* @property bool USE_PAE
Expand Down
47 changes: 8 additions & 39 deletions src/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ protected static function encryptData(
// @codeCoverageIgnoreEnd

// Let's split our key
list ($encKey, $authKey) = self::splitKeys($key, $hkdfSalt, $config);
list ($encKey, $authKey) = Util::splitKeys($key, $hkdfSalt, $config);

// Write the header
$output->writeBytes(
Expand Down Expand Up @@ -814,7 +814,7 @@ protected static function decryptData(
$hkdfSalt = $input->readBytes((int) $config->HKDF_SALT_LEN);

// Split our keys, begin the HMAC instance
list ($encKey, $authKey) = self::splitKeys($key, $hkdfSalt, $config);
list ($encKey, $authKey) = Util::splitKeys($key, $hkdfSalt, $config);

// VERSION 2+ uses BMAC
$mac = sodium_crypto_generichash_init($authKey);
Expand Down Expand Up @@ -939,7 +939,7 @@ protected static function sealData(
* @var string $encKey
* @var string $authKey
*/
list ($encKey, $authKey) = self::splitKeys($sharedSecretKey, $hkdfSalt, $config);
list ($encKey, $authKey) = Util::splitKeys($sharedSecretKey, $hkdfSalt, $config);

// Write the header:
$output->writeBytes(
Expand Down Expand Up @@ -1083,7 +1083,7 @@ protected static function unsealData(
* @var string $encKey
* @var string $authKey
*/
list ($encKey, $authKey) = self::splitKeys($key, $hkdfSalt, $config);
list ($encKey, $authKey) = Util::splitKeys($key, $hkdfSalt, $config);
// We no longer need the original key after we split it
unset($key);

Expand Down Expand Up @@ -1274,6 +1274,7 @@ protected static function getConfigEncrypt(int $major, int $minor): array
'MAC_SIZE' => 32,
'ENC_ALGO' => 'XChaCha20',
'USE_PAE' => true,
'HKDF_USE_INFO' => true,
'HKDF_SBOX' => 'Halite|EncryptionKey',
'HKDF_AUTH' => 'AuthenticationKeyFor_|Halite'
];
Expand All @@ -1286,6 +1287,7 @@ protected static function getConfigEncrypt(int $major, int $minor): array
'MAC_SIZE' => 32,
'ENC_ALGO' => 'XSalsa20',
'USE_PAE' => false,
'HKDF_USE_INFO' => false,
'HKDF_SBOX' => 'Halite|EncryptionKey',
'HKDF_AUTH' => 'AuthenticationKeyFor_|Halite'
];
Expand Down Expand Up @@ -1319,6 +1321,7 @@ protected static function getConfigSeal(int $major, int $minor): array
'PUBLICKEY_BYTES' => SODIUM_CRYPTO_BOX_PUBLICKEYBYTES,
'ENC_ALGO' => 'XChaCha20',
'USE_PAE' => true,
'HKDF_USE_INFO' => true,
'HKDF_SBOX' => 'Halite|EncryptionKey',
'HKDF_AUTH' => 'AuthenticationKeyFor_|Halite'
];
Expand All @@ -1334,6 +1337,7 @@ protected static function getConfigSeal(int $major, int $minor): array
'PUBLICKEY_BYTES' => SODIUM_CRYPTO_BOX_PUBLICKEYBYTES,
'ENC_ALGO' => 'XSalsa20',
'USE_PAE' => false,
'HKDF_USE_INFO' => false,
'HKDF_SBOX' => 'Halite|EncryptionKey',
'HKDF_AUTH' => 'AuthenticationKeyFor_|Halite'
];
Expand Down Expand Up @@ -1373,41 +1377,6 @@ protected static function getConfigChecksum(int $major, int $minor): array
// @codeCoverageIgnoreEnd
}

/**
* Split a key using HKDF-BLAKE2b
*
* @param Key $master
* @param string $salt
* @param Config $config
* @return array<int, string>
*
* @throws InvalidDigestLength
* @throws CannotPerformOperation
* @throws SodiumException
* @throws TypeError
*/
protected static function splitKeys(
Key $master,
string $salt,
Config $config
): array {
$binary = $master->getRawKeyMaterial();
return [
Util::hkdfBlake2b(
$binary,
SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
(string) $config->HKDF_SBOX,
$salt
),
Util::hkdfBlake2b(
$binary,
SODIUM_CRYPTO_AUTH_KEYBYTES,
(string) $config->HKDF_AUTH,
$salt
)
];
}

/**
* Stream encryption - Do not call directly
*
Expand Down
3 changes: 3 additions & 0 deletions src/Symmetric/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public static function getConfigEncrypt(int $major, int $minor): array
'USE_PAE' => true,
'MAC_ALGO' => 'BLAKE2b',
'MAC_SIZE' => SODIUM_CRYPTO_GENERICHASH_BYTES_MAX,
'HKDF_USE_INFO' => true,
'HKDF_SBOX' => 'Halite|EncryptionKey',
'HKDF_AUTH' => 'AuthenticationKeyFor_|Halite'
];
Expand All @@ -111,6 +112,7 @@ public static function getConfigEncrypt(int $major, int $minor): array
'USE_PAE' => false,
'MAC_ALGO' => 'BLAKE2b',
'MAC_SIZE' => SODIUM_CRYPTO_GENERICHASH_BYTES_MAX,
'HKDF_USE_INFO' => false,
'HKDF_SBOX' => 'Halite|EncryptionKey',
'HKDF_AUTH' => 'AuthenticationKeyFor_|Halite'
];
Expand Down Expand Up @@ -142,6 +144,7 @@ public static function getConfigAuth(int $major, int $minor): array
'MAC_ALGO' => 'BLAKE2b',
'MAC_SIZE' => SODIUM_CRYPTO_GENERICHASH_BYTES_MAX,
'PUBLICKEY_BYTES' => SODIUM_CRYPTO_BOX_PUBLICKEYBYTES,
'HKDF_USE_INFO' => $major > 4,
'HKDF_SBOX' => 'Halite|EncryptionKey',
'HKDF_AUTH' => 'AuthenticationKeyFor_|Halite'
];
Expand Down
43 changes: 4 additions & 39 deletions src/Symmetric/Crypto.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
random_bytes,
sodium_crypto_generichash,
sodium_crypto_stream_xchacha20_xor,
sodium_crypto_stream_xor;
sodium_crypto_stream_xor,
str_repeat;

/**
* Class Crypto
Expand Down Expand Up @@ -203,7 +204,7 @@ public static function decryptWithAD(
* @var string $encKey
* @var string $authKey
*/
$split = self::splitKeys($secretKey, $salt, $config);
$split = Util::splitKeys($secretKey, $salt, $config);
$encKey = $split[0];
$authKey = $split[1];

Expand Down Expand Up @@ -327,7 +328,7 @@ public static function encryptWithAD(
This uses salted HKDF to split the keys, which is why we need the
salt in the first place. */
[$encKey, $authKey] = self::splitKeys($secretKey, $salt, $config);
[$encKey, $authKey] = Util::splitKeys($secretKey, $salt, $config);

// Encrypt our message with the encryption key:

Expand Down Expand Up @@ -383,42 +384,6 @@ public static function encryptWithAD(
return $message;
}

/**
* Split a key (using HKDF-BLAKE2b instead of HKDF-HMAC-*)
*
* @param EncryptionKey $master
* @param string $salt
* @param BaseConfig $config
*
* @return string[]
*
* @throws CannotPerformOperation
* @throws InvalidDigestLength
* @throws SodiumException
* @throws TypeError
*/
public static function splitKeys(
EncryptionKey $master,
string $salt,
BaseConfig $config
): array {
$binary = $master->getRawKeyMaterial();
return [
Util::hkdfBlake2b(
$binary,
SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
(string) $config->HKDF_SBOX,
$salt
),
Util::hkdfBlake2b(
$binary,
SODIUM_CRYPTO_AUTH_KEYBYTES,
(string) $config->HKDF_AUTH,
$salt
)
];
}

/**
* Unpack a message string into an array (assigned to variables via list()).
*
Expand Down
61 changes: 61 additions & 0 deletions src/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
InvalidDigestLength,
InvalidType
};
use ParagonIE\Halite\Symmetric\EncryptionKey;
use RangeException;
use SodiumException;
use Throwable;
Expand Down Expand Up @@ -331,6 +332,66 @@ public static function safeStrcpy(string $string): string
return $return;
}

/**
* Split a key (using HKDF-BLAKE2b instead of HKDF-HMAC-*)
*
* @param EncryptionKey $master
* @param string $salt
* @param Config $config
*
* @return string[]
*
* @throws CannotPerformOperation
* @throws InvalidDigestLength
* @throws SodiumException
* @throws TypeError
*/
public static function splitKeys(
EncryptionKey $master,
string $salt,
Config $config
): array {
$binary = $master->getRawKeyMaterial();

/*
* From Halite version 5, we use the HKDF info parameter instead of the salt.
* This does two things:
*
* 1. It allows us to use the HKDF security definition (which is stronger than a PRF)
* 2. It allows us to reuse the intermediary step and make key derivation faster.
*/
if ($config->HKDF_USE_INFO) {
$prk = self::raw_keyed_hash(
$binary,
str_repeat("\x00", SODIUM_CRYPTO_GENERICHASH_KEYBYTES)
);
$return = [
self::raw_keyed_hash(((string) $config->HKDF_SBOX) . $salt . "\x01", $prk),
self::raw_keyed_hash(((string) $config->HKDF_AUTH) . $salt . "\x01", $prk)
];
self::memzero($prk);
return $return;
}

/*
* Halite 4 and blow used this strategy:
*/
return [
Util::hkdfBlake2b(
$binary,
SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
(string) $config->HKDF_SBOX,
$salt
),
Util::hkdfBlake2b(
$binary,
SODIUM_CRYPTO_AUTH_KEYBYTES,
(string) $config->HKDF_AUTH,
$salt
)
];
}

/**
* Turn a string into an array of integers
*
Expand Down

0 comments on commit 1bbf8ac

Please sign in to comment.