WordPress/wp-includes/sodium_compat/src/Crypto.php
desrosj 10effe89b5 Upgrade/Install: Update sodium_compat to v1.10.0.
This adds a `runtime_speed_test()` method for estimating if the 32-bit implementation is fast enough for expensive computations.

Merges [45344] to the 5.2 branch.

Props paragoninitiativeenterprises, tellyworth.
See #47186.
Built from https://develop.svn.wordpress.org/branches/5.2@45355


git-svn-id: http://core.svn.wordpress.org/branches/5.2@45166 1a063a9b-81f0-0310-95a4-ce76da25c4cd
2019-05-17 17:20:53 +00:00

1253 lines
38 KiB
PHP

<?php
if (class_exists('ParagonIE_Sodium_Crypto', false)) {
return;
}
/**
* Class ParagonIE_Sodium_Crypto
*
* ATTENTION!
*
* If you are using this library, you should be using
* ParagonIE_Sodium_Compat in your code, not this class.
*/
abstract class ParagonIE_Sodium_Crypto
{
const aead_chacha20poly1305_KEYBYTES = 32;
const aead_chacha20poly1305_NSECBYTES = 0;
const aead_chacha20poly1305_NPUBBYTES = 8;
const aead_chacha20poly1305_ABYTES = 16;
const aead_chacha20poly1305_IETF_KEYBYTES = 32;
const aead_chacha20poly1305_IETF_NSECBYTES = 0;
const aead_chacha20poly1305_IETF_NPUBBYTES = 12;
const aead_chacha20poly1305_IETF_ABYTES = 16;
const aead_xchacha20poly1305_IETF_KEYBYTES = 32;
const aead_xchacha20poly1305_IETF_NSECBYTES = 0;
const aead_xchacha20poly1305_IETF_NPUBBYTES = 24;
const aead_xchacha20poly1305_IETF_ABYTES = 16;
const box_curve25519xsalsa20poly1305_SEEDBYTES = 32;
const box_curve25519xsalsa20poly1305_PUBLICKEYBYTES = 32;
const box_curve25519xsalsa20poly1305_SECRETKEYBYTES = 32;
const box_curve25519xsalsa20poly1305_BEFORENMBYTES = 32;
const box_curve25519xsalsa20poly1305_NONCEBYTES = 24;
const box_curve25519xsalsa20poly1305_MACBYTES = 16;
const box_curve25519xsalsa20poly1305_BOXZEROBYTES = 16;
const box_curve25519xsalsa20poly1305_ZEROBYTES = 32;
const onetimeauth_poly1305_BYTES = 16;
const onetimeauth_poly1305_KEYBYTES = 32;
const secretbox_xsalsa20poly1305_KEYBYTES = 32;
const secretbox_xsalsa20poly1305_NONCEBYTES = 24;
const secretbox_xsalsa20poly1305_MACBYTES = 16;
const secretbox_xsalsa20poly1305_BOXZEROBYTES = 16;
const secretbox_xsalsa20poly1305_ZEROBYTES = 32;
const secretbox_xchacha20poly1305_KEYBYTES = 32;
const secretbox_xchacha20poly1305_NONCEBYTES = 24;
const secretbox_xchacha20poly1305_MACBYTES = 16;
const secretbox_xchacha20poly1305_BOXZEROBYTES = 16;
const secretbox_xchacha20poly1305_ZEROBYTES = 32;
const stream_salsa20_KEYBYTES = 32;
/**
* AEAD Decryption with ChaCha20-Poly1305
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $ad
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function aead_chacha20poly1305_decrypt(
$message = '',
$ad = '',
$nonce = '',
$key = ''
) {
/** @var int $len - Length of message (ciphertext + MAC) */
$len = ParagonIE_Sodium_Core_Util::strlen($message);
/** @var int $clen - Length of ciphertext */
$clen = $len - self::aead_chacha20poly1305_ABYTES;
/** @var int $adlen - Length of associated data */
$adlen = ParagonIE_Sodium_Core_Util::strlen($ad);
/** @var string $mac - Message authentication code */
$mac = ParagonIE_Sodium_Core_Util::substr(
$message,
$clen,
self::aead_chacha20poly1305_ABYTES
);
/** @var string $ciphertext - The encrypted message (sans MAC) */
$ciphertext = ParagonIE_Sodium_Core_Util::substr($message, 0, $clen);
/** @var string The first block of the chacha20 keystream, used as a poly1305 key */
$block0 = ParagonIE_Sodium_Core_ChaCha20::stream(
32,
$nonce,
$key
);
/* Recalculate the Poly1305 authentication tag (MAC): */
$state = new ParagonIE_Sodium_Core_Poly1305_State($block0);
try {
ParagonIE_Sodium_Compat::memzero($block0);
} catch (SodiumException $ex) {
$block0 = null;
}
$state->update($ad);
$state->update(ParagonIE_Sodium_Core_Util::store64_le($adlen));
$state->update($ciphertext);
$state->update(ParagonIE_Sodium_Core_Util::store64_le($clen));
$computed_mac = $state->finish();
/* Compare the given MAC with the recalculated MAC: */
if (!ParagonIE_Sodium_Core_Util::verify_16($computed_mac, $mac)) {
throw new SodiumException('Invalid MAC');
}
// Here, we know that the MAC is valid, so we decrypt and return the plaintext
return ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
$ciphertext,
$nonce,
$key,
ParagonIE_Sodium_Core_Util::store64_le(1)
);
}
/**
* AEAD Encryption with ChaCha20-Poly1305
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $ad
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function aead_chacha20poly1305_encrypt(
$message = '',
$ad = '',
$nonce = '',
$key = ''
) {
/** @var int $len - Length of the plaintext message */
$len = ParagonIE_Sodium_Core_Util::strlen($message);
/** @var int $adlen - Length of the associated data */
$adlen = ParagonIE_Sodium_Core_Util::strlen($ad);
/** @var string The first block of the chacha20 keystream, used as a poly1305 key */
$block0 = ParagonIE_Sodium_Core_ChaCha20::stream(
32,
$nonce,
$key
);
$state = new ParagonIE_Sodium_Core_Poly1305_State($block0);
try {
ParagonIE_Sodium_Compat::memzero($block0);
} catch (SodiumException $ex) {
$block0 = null;
}
/** @var string $ciphertext - Raw encrypted data */
$ciphertext = ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
$message,
$nonce,
$key,
ParagonIE_Sodium_Core_Util::store64_le(1)
);
$state->update($ad);
$state->update(ParagonIE_Sodium_Core_Util::store64_le($adlen));
$state->update($ciphertext);
$state->update(ParagonIE_Sodium_Core_Util::store64_le($len));
return $ciphertext . $state->finish();
}
/**
* AEAD Decryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $ad
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function aead_chacha20poly1305_ietf_decrypt(
$message = '',
$ad = '',
$nonce = '',
$key = ''
) {
/** @var int $adlen - Length of associated data */
$adlen = ParagonIE_Sodium_Core_Util::strlen($ad);
/** @var int $len - Length of message (ciphertext + MAC) */
$len = ParagonIE_Sodium_Core_Util::strlen($message);
/** @var int $clen - Length of ciphertext */
$clen = $len - self::aead_chacha20poly1305_IETF_ABYTES;
/** @var string The first block of the chacha20 keystream, used as a poly1305 key */
$block0 = ParagonIE_Sodium_Core_ChaCha20::ietfStream(
32,
$nonce,
$key
);
/** @var string $mac - Message authentication code */
$mac = ParagonIE_Sodium_Core_Util::substr(
$message,
$len - self::aead_chacha20poly1305_IETF_ABYTES,
self::aead_chacha20poly1305_IETF_ABYTES
);
/** @var string $ciphertext - The encrypted message (sans MAC) */
$ciphertext = ParagonIE_Sodium_Core_Util::substr(
$message,
0,
$len - self::aead_chacha20poly1305_IETF_ABYTES
);
/* Recalculate the Poly1305 authentication tag (MAC): */
$state = new ParagonIE_Sodium_Core_Poly1305_State($block0);
try {
ParagonIE_Sodium_Compat::memzero($block0);
} catch (SodiumException $ex) {
$block0 = null;
}
$state->update($ad);
$state->update(str_repeat("\x00", ((0x10 - $adlen) & 0xf)));
$state->update($ciphertext);
$state->update(str_repeat("\x00", (0x10 - $clen) & 0xf));
$state->update(ParagonIE_Sodium_Core_Util::store64_le($adlen));
$state->update(ParagonIE_Sodium_Core_Util::store64_le($clen));
$computed_mac = $state->finish();
/* Compare the given MAC with the recalculated MAC: */
if (!ParagonIE_Sodium_Core_Util::verify_16($computed_mac, $mac)) {
throw new SodiumException('Invalid MAC');
}
// Here, we know that the MAC is valid, so we decrypt and return the plaintext
return ParagonIE_Sodium_Core_ChaCha20::ietfStreamXorIc(
$ciphertext,
$nonce,
$key,
ParagonIE_Sodium_Core_Util::store64_le(1)
);
}
/**
* AEAD Encryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $ad
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function aead_chacha20poly1305_ietf_encrypt(
$message = '',
$ad = '',
$nonce = '',
$key = ''
) {
/** @var int $len - Length of the plaintext message */
$len = ParagonIE_Sodium_Core_Util::strlen($message);
/** @var int $adlen - Length of the associated data */
$adlen = ParagonIE_Sodium_Core_Util::strlen($ad);
/** @var string The first block of the chacha20 keystream, used as a poly1305 key */
$block0 = ParagonIE_Sodium_Core_ChaCha20::ietfStream(
32,
$nonce,
$key
);
$state = new ParagonIE_Sodium_Core_Poly1305_State($block0);
try {
ParagonIE_Sodium_Compat::memzero($block0);
} catch (SodiumException $ex) {
$block0 = null;
}
/** @var string $ciphertext - Raw encrypted data */
$ciphertext = ParagonIE_Sodium_Core_ChaCha20::ietfStreamXorIc(
$message,
$nonce,
$key,
ParagonIE_Sodium_Core_Util::store64_le(1)
);
$state->update($ad);
$state->update(str_repeat("\x00", ((0x10 - $adlen) & 0xf)));
$state->update($ciphertext);
$state->update(str_repeat("\x00", ((0x10 - $len) & 0xf)));
$state->update(ParagonIE_Sodium_Core_Util::store64_le($adlen));
$state->update(ParagonIE_Sodium_Core_Util::store64_le($len));
return $ciphertext . $state->finish();
}
/**
* AEAD Decryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $ad
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function aead_xchacha20poly1305_ietf_decrypt(
$message = '',
$ad = '',
$nonce = '',
$key = ''
) {
$subkey = ParagonIE_Sodium_Core_HChaCha20::hChaCha20(
ParagonIE_Sodium_Core_Util::substr($nonce, 0, 16),
$key
);
$nonceLast = "\x00\x00\x00\x00" .
ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
return self::aead_chacha20poly1305_ietf_decrypt($message, $ad, $nonceLast, $subkey);
}
/**
* AEAD Encryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $ad
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function aead_xchacha20poly1305_ietf_encrypt(
$message = '',
$ad = '',
$nonce = '',
$key = ''
) {
$subkey = ParagonIE_Sodium_Core_HChaCha20::hChaCha20(
ParagonIE_Sodium_Core_Util::substr($nonce, 0, 16),
$key
);
$nonceLast = "\x00\x00\x00\x00" .
ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
return self::aead_chacha20poly1305_ietf_encrypt($message, $ad, $nonceLast, $subkey);
}
/**
* HMAC-SHA-512-256 (a.k.a. the leftmost 256 bits of HMAC-SHA-512)
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $key
* @return string
* @throws TypeError
*/
public static function auth($message, $key)
{
return ParagonIE_Sodium_Core_Util::substr(
hash_hmac('sha512', $message, $key, true),
0,
32
);
}
/**
* HMAC-SHA-512-256 validation. Constant-time via hash_equals().
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $mac
* @param string $message
* @param string $key
* @return bool
* @throws SodiumException
* @throws TypeError
*/
public static function auth_verify($mac, $message, $key)
{
return ParagonIE_Sodium_Core_Util::hashEquals(
$mac,
self::auth($message, $key)
);
}
/**
* X25519 key exchange followed by XSalsa20Poly1305 symmetric encryption
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $plaintext
* @param string $nonce
* @param string $keypair
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function box($plaintext, $nonce, $keypair)
{
$c = self::secretbox(
$plaintext,
$nonce,
self::box_beforenm(
self::box_secretkey($keypair),
self::box_publickey($keypair)
)
);
return $c;
}
/**
* X25519-XSalsa20-Poly1305 with one ephemeral X25519 keypair.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $publicKey
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function box_seal($message, $publicKey)
{
/** @var string $ephemeralKeypair */
$ephemeralKeypair = self::box_keypair();
/** @var string $ephemeralSK */
$ephemeralSK = self::box_secretkey($ephemeralKeypair);
/** @var string $ephemeralPK */
$ephemeralPK = self::box_publickey($ephemeralKeypair);
/** @var string $nonce */
$nonce = self::generichash(
$ephemeralPK . $publicKey,
'',
24
);
/** @var string $keypair - The combined keypair used in crypto_box() */
$keypair = self::box_keypair_from_secretkey_and_publickey($ephemeralSK, $publicKey);
/** @var string $ciphertext Ciphertext + MAC from crypto_box */
$ciphertext = self::box($message, $nonce, $keypair);
try {
ParagonIE_Sodium_Compat::memzero($ephemeralKeypair);
ParagonIE_Sodium_Compat::memzero($ephemeralSK);
ParagonIE_Sodium_Compat::memzero($nonce);
} catch (SodiumException $ex) {
$ephemeralKeypair = null;
$ephemeralSK = null;
$nonce = null;
}
return $ephemeralPK . $ciphertext;
}
/**
* Opens a message encrypted via box_seal().
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $keypair
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function box_seal_open($message, $keypair)
{
/** @var string $ephemeralPK */
$ephemeralPK = ParagonIE_Sodium_Core_Util::substr($message, 0, 32);
/** @var string $ciphertext (ciphertext + MAC) */
$ciphertext = ParagonIE_Sodium_Core_Util::substr($message, 32);
/** @var string $secretKey */
$secretKey = self::box_secretkey($keypair);
/** @var string $publicKey */
$publicKey = self::box_publickey($keypair);
/** @var string $nonce */
$nonce = self::generichash(
$ephemeralPK . $publicKey,
'',
24
);
/** @var string $keypair */
$keypair = self::box_keypair_from_secretkey_and_publickey($secretKey, $ephemeralPK);
/** @var string $m */
$m = self::box_open($ciphertext, $nonce, $keypair);
try {
ParagonIE_Sodium_Compat::memzero($secretKey);
ParagonIE_Sodium_Compat::memzero($ephemeralPK);
ParagonIE_Sodium_Compat::memzero($nonce);
} catch (SodiumException $ex) {
$secretKey = null;
$ephemeralPK = null;
$nonce = null;
}
return $m;
}
/**
* Used by crypto_box() to get the crypto_secretbox() key.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $sk
* @param string $pk
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function box_beforenm($sk, $pk)
{
return ParagonIE_Sodium_Core_HSalsa20::hsalsa20(
str_repeat("\x00", 16),
self::scalarmult($sk, $pk)
);
}
/**
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @return string
* @throws Exception
* @throws SodiumException
* @throws TypeError
*/
public static function box_keypair()
{
$sKey = random_bytes(32);
$pKey = self::scalarmult_base($sKey);
return $sKey . $pKey;
}
/**
* @param string $seed
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function box_seed_keypair($seed)
{
$sKey = ParagonIE_Sodium_Core_Util::substr(
hash('sha512', $seed, true),
0,
32
);
$pKey = self::scalarmult_base($sKey);
return $sKey . $pKey;
}
/**
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $sKey
* @param string $pKey
* @return string
* @throws TypeError
*/
public static function box_keypair_from_secretkey_and_publickey($sKey, $pKey)
{
return ParagonIE_Sodium_Core_Util::substr($sKey, 0, 32) .
ParagonIE_Sodium_Core_Util::substr($pKey, 0, 32);
}
/**
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $keypair
* @return string
* @throws RangeException
* @throws TypeError
*/
public static function box_secretkey($keypair)
{
if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== 64) {
throw new RangeException(
'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES bytes long.'
);
}
return ParagonIE_Sodium_Core_Util::substr($keypair, 0, 32);
}
/**
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $keypair
* @return string
* @throws RangeException
* @throws TypeError
*/
public static function box_publickey($keypair)
{
if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
throw new RangeException(
'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES bytes long.'
);
}
return ParagonIE_Sodium_Core_Util::substr($keypair, 32, 32);
}
/**
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $sKey
* @return string
* @throws RangeException
* @throws SodiumException
* @throws TypeError
*/
public static function box_publickey_from_secretkey($sKey)
{
if (ParagonIE_Sodium_Core_Util::strlen($sKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_SECRETKEYBYTES) {
throw new RangeException(
'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_SECRETKEYBYTES bytes long.'
);
}
return self::scalarmult_base($sKey);
}
/**
* Decrypt a message encrypted with box().
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $ciphertext
* @param string $nonce
* @param string $keypair
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function box_open($ciphertext, $nonce, $keypair)
{
return self::secretbox_open(
$ciphertext,
$nonce,
self::box_beforenm(
self::box_secretkey($keypair),
self::box_publickey($keypair)
)
);
}
/**
* Calculate a BLAKE2b hash.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string|null $key
* @param int $outlen
* @return string
* @throws RangeException
* @throws SodiumException
* @throws TypeError
*/
public static function generichash($message, $key = '', $outlen = 32)
{
// This ensures that ParagonIE_Sodium_Core_BLAKE2b::$iv is initialized
ParagonIE_Sodium_Core_BLAKE2b::pseudoConstructor();
$k = null;
if (!empty($key)) {
/** @var SplFixedArray $k */
$k = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($key);
if ($k->count() > ParagonIE_Sodium_Core_BLAKE2b::KEYBYTES) {
throw new RangeException('Invalid key size');
}
}
/** @var SplFixedArray $in */
$in = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($message);
/** @var SplFixedArray $ctx */
$ctx = ParagonIE_Sodium_Core_BLAKE2b::init($k, $outlen);
ParagonIE_Sodium_Core_BLAKE2b::update($ctx, $in, $in->count());
/** @var SplFixedArray $out */
$out = new SplFixedArray($outlen);
$out = ParagonIE_Sodium_Core_BLAKE2b::finish($ctx, $out);
/** @var array<int, int> */
$outArray = $out->toArray();
return ParagonIE_Sodium_Core_Util::intArrayToString($outArray);
}
/**
* Finalize a BLAKE2b hashing context, returning the hash.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $ctx
* @param int $outlen
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function generichash_final($ctx, $outlen = 32)
{
if (!is_string($ctx)) {
throw new TypeError('Context must be a string');
}
$out = new SplFixedArray($outlen);
/** @var SplFixedArray $context */
$context = ParagonIE_Sodium_Core_BLAKE2b::stringToContext($ctx);
/** @var SplFixedArray $out */
$out = ParagonIE_Sodium_Core_BLAKE2b::finish($context, $out);
/** @var array<int, int> */
$outArray = $out->toArray();
return ParagonIE_Sodium_Core_Util::intArrayToString($outArray);
}
/**
* Initialize a hashing context for BLAKE2b.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $key
* @param int $outputLength
* @return string
* @throws RangeException
* @throws SodiumException
* @throws TypeError
*/
public static function generichash_init($key = '', $outputLength = 32)
{
// This ensures that ParagonIE_Sodium_Core_BLAKE2b::$iv is initialized
ParagonIE_Sodium_Core_BLAKE2b::pseudoConstructor();
$k = null;
if (!empty($key)) {
$k = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($key);
if ($k->count() > ParagonIE_Sodium_Core_BLAKE2b::KEYBYTES) {
throw new RangeException('Invalid key size');
}
}
/** @var SplFixedArray $ctx */
$ctx = ParagonIE_Sodium_Core_BLAKE2b::init($k, $outputLength);
return ParagonIE_Sodium_Core_BLAKE2b::contextToString($ctx);
}
/**
* Update a hashing context for BLAKE2b with $message
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $ctx
* @param string $message
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function generichash_update($ctx, $message)
{
// This ensures that ParagonIE_Sodium_Core_BLAKE2b::$iv is initialized
ParagonIE_Sodium_Core_BLAKE2b::pseudoConstructor();
/** @var SplFixedArray $context */
$context = ParagonIE_Sodium_Core_BLAKE2b::stringToContext($ctx);
/** @var SplFixedArray $in */
$in = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($message);
ParagonIE_Sodium_Core_BLAKE2b::update($context, $in, $in->count());
return ParagonIE_Sodium_Core_BLAKE2b::contextToString($context);
}
/**
* Libsodium's crypto_kx().
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $my_sk
* @param string $their_pk
* @param string $client_pk
* @param string $server_pk
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function keyExchange($my_sk, $their_pk, $client_pk, $server_pk)
{
return ParagonIE_Sodium_Compat::crypto_generichash(
ParagonIE_Sodium_Compat::crypto_scalarmult($my_sk, $their_pk) .
$client_pk .
$server_pk
);
}
/**
* ECDH over Curve25519
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $sKey
* @param string $pKey
* @return string
*
* @throws SodiumException
* @throws TypeError
*/
public static function scalarmult($sKey, $pKey)
{
$q = ParagonIE_Sodium_Core_X25519::crypto_scalarmult_curve25519_ref10($sKey, $pKey);
self::scalarmult_throw_if_zero($q);
return $q;
}
/**
* ECDH over Curve25519, using the basepoint.
* Used to get a secret key from a public key.
*
* @param string $secret
* @return string
*
* @throws SodiumException
* @throws TypeError
*/
public static function scalarmult_base($secret)
{
$q = ParagonIE_Sodium_Core_X25519::crypto_scalarmult_curve25519_ref10_base($secret);
self::scalarmult_throw_if_zero($q);
return $q;
}
/**
* This throws an Error if a zero public key was passed to the function.
*
* @param string $q
* @return void
* @throws SodiumException
* @throws TypeError
*/
protected static function scalarmult_throw_if_zero($q)
{
$d = 0;
for ($i = 0; $i < self::box_curve25519xsalsa20poly1305_SECRETKEYBYTES; ++$i) {
$d |= ParagonIE_Sodium_Core_Util::chrToInt($q[$i]);
}
/* branch-free variant of === 0 */
if (-(1 & (($d - 1) >> 8))) {
throw new SodiumException('Zero public key is not allowed');
}
}
/**
* XSalsa20-Poly1305 authenticated symmetric-key encryption.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $plaintext
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function secretbox($plaintext, $nonce, $key)
{
/** @var string $subkey */
$subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
/** @var string $block0 */
$block0 = str_repeat("\x00", 32);
/** @var int $mlen - Length of the plaintext message */
$mlen = ParagonIE_Sodium_Core_Util::strlen($plaintext);
$mlen0 = $mlen;
if ($mlen0 > 64 - self::secretbox_xsalsa20poly1305_ZEROBYTES) {
$mlen0 = 64 - self::secretbox_xsalsa20poly1305_ZEROBYTES;
}
$block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
/** @var string $block0 */
$block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
$block0,
ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
$subkey
);
/** @var string $c */
$c = ParagonIE_Sodium_Core_Util::substr(
$block0,
self::secretbox_xsalsa20poly1305_ZEROBYTES
);
if ($mlen > $mlen0) {
$c .= ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
ParagonIE_Sodium_Core_Util::substr(
$plaintext,
self::secretbox_xsalsa20poly1305_ZEROBYTES
),
ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
1,
$subkey
);
}
$state = new ParagonIE_Sodium_Core_Poly1305_State(
ParagonIE_Sodium_Core_Util::substr(
$block0,
0,
self::onetimeauth_poly1305_KEYBYTES
)
);
try {
ParagonIE_Sodium_Compat::memzero($block0);
ParagonIE_Sodium_Compat::memzero($subkey);
} catch (SodiumException $ex) {
$block0 = null;
$subkey = null;
}
$state->update($c);
/** @var string $c - MAC || ciphertext */
$c = $state->finish() . $c;
unset($state);
return $c;
}
/**
* Decrypt a ciphertext generated via secretbox().
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $ciphertext
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function secretbox_open($ciphertext, $nonce, $key)
{
/** @var string $mac */
$mac = ParagonIE_Sodium_Core_Util::substr(
$ciphertext,
0,
self::secretbox_xsalsa20poly1305_MACBYTES
);
/** @var string $c */
$c = ParagonIE_Sodium_Core_Util::substr(
$ciphertext,
self::secretbox_xsalsa20poly1305_MACBYTES
);
/** @var int $clen */
$clen = ParagonIE_Sodium_Core_Util::strlen($c);
/** @var string $subkey */
$subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
/** @var string $block0 */
$block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
64,
ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
$subkey
);
$verified = ParagonIE_Sodium_Core_Poly1305::onetimeauth_verify(
$mac,
$c,
ParagonIE_Sodium_Core_Util::substr($block0, 0, 32)
);
if (!$verified) {
try {
ParagonIE_Sodium_Compat::memzero($subkey);
} catch (SodiumException $ex) {
$subkey = null;
}
throw new SodiumException('Invalid MAC');
}
/** @var string $m - Decrypted message */
$m = ParagonIE_Sodium_Core_Util::xorStrings(
ParagonIE_Sodium_Core_Util::substr($block0, self::secretbox_xsalsa20poly1305_ZEROBYTES),
ParagonIE_Sodium_Core_Util::substr($c, 0, self::secretbox_xsalsa20poly1305_ZEROBYTES)
);
if ($clen > self::secretbox_xsalsa20poly1305_ZEROBYTES) {
// We had more than 1 block, so let's continue to decrypt the rest.
$m .= ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
ParagonIE_Sodium_Core_Util::substr(
$c,
self::secretbox_xsalsa20poly1305_ZEROBYTES
),
ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
1,
(string) $subkey
);
}
return $m;
}
/**
* XChaCha20-Poly1305 authenticated symmetric-key encryption.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $plaintext
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function secretbox_xchacha20poly1305($plaintext, $nonce, $key)
{
/** @var string $subkey */
$subkey = ParagonIE_Sodium_Core_HChaCha20::hChaCha20(
ParagonIE_Sodium_Core_Util::substr($nonce, 0, 16),
$key
);
$nonceLast = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
/** @var string $block0 */
$block0 = str_repeat("\x00", 32);
/** @var int $mlen - Length of the plaintext message */
$mlen = ParagonIE_Sodium_Core_Util::strlen($plaintext);
$mlen0 = $mlen;
if ($mlen0 > 64 - self::secretbox_xchacha20poly1305_ZEROBYTES) {
$mlen0 = 64 - self::secretbox_xchacha20poly1305_ZEROBYTES;
}
$block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
/** @var string $block0 */
$block0 = ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
$block0,
$nonceLast,
$subkey
);
/** @var string $c */
$c = ParagonIE_Sodium_Core_Util::substr(
$block0,
self::secretbox_xchacha20poly1305_ZEROBYTES
);
if ($mlen > $mlen0) {
$c .= ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
ParagonIE_Sodium_Core_Util::substr(
$plaintext,
self::secretbox_xchacha20poly1305_ZEROBYTES
),
$nonceLast,
$subkey,
ParagonIE_Sodium_Core_Util::store64_le(1)
);
}
$state = new ParagonIE_Sodium_Core_Poly1305_State(
ParagonIE_Sodium_Core_Util::substr(
$block0,
0,
self::onetimeauth_poly1305_KEYBYTES
)
);
try {
ParagonIE_Sodium_Compat::memzero($block0);
ParagonIE_Sodium_Compat::memzero($subkey);
} catch (SodiumException $ex) {
$block0 = null;
$subkey = null;
}
$state->update($c);
/** @var string $c - MAC || ciphertext */
$c = $state->finish() . $c;
unset($state);
return $c;
}
/**
* Decrypt a ciphertext generated via secretbox_xchacha20poly1305().
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $ciphertext
* @param string $nonce
* @param string $key
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key)
{
/** @var string $mac */
$mac = ParagonIE_Sodium_Core_Util::substr(
$ciphertext,
0,
self::secretbox_xchacha20poly1305_MACBYTES
);
/** @var string $c */
$c = ParagonIE_Sodium_Core_Util::substr(
$ciphertext,
self::secretbox_xchacha20poly1305_MACBYTES
);
/** @var int $clen */
$clen = ParagonIE_Sodium_Core_Util::strlen($c);
/** @var string $subkey */
$subkey = ParagonIE_Sodium_Core_HChaCha20::hchacha20($nonce, $key);
/** @var string $block0 */
$block0 = ParagonIE_Sodium_Core_ChaCha20::stream(
64,
ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
$subkey
);
$verified = ParagonIE_Sodium_Core_Poly1305::onetimeauth_verify(
$mac,
$c,
ParagonIE_Sodium_Core_Util::substr($block0, 0, 32)
);
if (!$verified) {
try {
ParagonIE_Sodium_Compat::memzero($subkey);
} catch (SodiumException $ex) {
$subkey = null;
}
throw new SodiumException('Invalid MAC');
}
/** @var string $m - Decrypted message */
$m = ParagonIE_Sodium_Core_Util::xorStrings(
ParagonIE_Sodium_Core_Util::substr($block0, self::secretbox_xchacha20poly1305_ZEROBYTES),
ParagonIE_Sodium_Core_Util::substr($c, 0, self::secretbox_xchacha20poly1305_ZEROBYTES)
);
if ($clen > self::secretbox_xchacha20poly1305_ZEROBYTES) {
// We had more than 1 block, so let's continue to decrypt the rest.
$m .= ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
ParagonIE_Sodium_Core_Util::substr(
$c,
self::secretbox_xchacha20poly1305_ZEROBYTES
),
ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
(string) $subkey,
ParagonIE_Sodium_Core_Util::store64_le(1)
);
}
return $m;
}
/**
* Detached Ed25519 signature.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $sk
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function sign_detached($message, $sk)
{
return ParagonIE_Sodium_Core_Ed25519::sign_detached($message, $sk);
}
/**
* Attached Ed25519 signature. (Returns a signed message.)
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $message
* @param string $sk
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function sign($message, $sk)
{
return ParagonIE_Sodium_Core_Ed25519::sign($message, $sk);
}
/**
* Opens a signed message. If valid, returns the message.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $signedMessage
* @param string $pk
* @return string
* @throws SodiumException
* @throws TypeError
*/
public static function sign_open($signedMessage, $pk)
{
return ParagonIE_Sodium_Core_Ed25519::sign_open($signedMessage, $pk);
}
/**
* Verify a detached signature of a given message and public key.
*
* @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
*
* @param string $signature
* @param string $message
* @param string $pk
* @return bool
* @throws SodiumException
* @throws TypeError
*/
public static function sign_verify_detached($signature, $message, $pk)
{
return ParagonIE_Sodium_Core_Ed25519::verify_detached($signature, $message, $pk);
}
}