| 
<?php
 namespace ParagonIE\CipherSweet;
 
 use ParagonIE\ConstantTime\Binary;
 use ParagonIE\CipherSweet\Backend\Key\SymmetricKey;
 use ParagonIE\CipherSweet\Exception\CryptoOperationException;
 use ParagonIE_Sodium_Core_Util as SodiumUtil;
 
 /**
 * Class Util
 * @package ParagonIE\CipherSweet
 */
 abstract class Util
 {
 /**
 * Userland polyfill for AES-256-CTR, using AES-256-ECB
 *
 * @param string $plaintext
 * @param string $key
 * @param string $nonce
 * @return string
 */
 public static function aes256ctr($plaintext, $key, $nonce)
 {
 if (empty($plaintext)) {
 return '';
 }
 $length = Binary::safeStrlen($plaintext);
 /** @var int $numBlocks */
 $numBlocks = (($length - 1) >> 4) + 1;
 $stream = '';
 for ($i = 0; $i < $numBlocks; ++$i) {
 $stream .= $nonce;
 $nonce = self::ctrNonceIncrease($nonce);
 }
 /** @var string $xor */
 $xor = \openssl_encrypt(
 $stream,
 'aes-256-ecb',
 $key,
 OPENSSL_RAW_DATA
 );
 return (string) (
 $plaintext ^ Binary::safeSubstr($xor, 0, $length)
 );
 }
 
 /**
 * @param string $input
 * @param int $bits
 * @param bool $bitwiseLeft
 * @return string
 *
 * @throws \SodiumException
 */
 public static function andMask($input, $bits, $bitwiseLeft = false)
 {
 $bytes = $bits >> 3;
 $length = Binary::safeStrlen($input);
 if ($bytes >= $length) {
 $input .= \str_repeat("\0", ($bytes - $length) + 1);
 }
 $string = Binary::safeSubstr($input, 0, $bytes);
 $leftOver = ($bits - ($bytes << 3));
 if ($leftOver > 0) {
 $mask = (1 << $leftOver) - 1;
 if (!$bitwiseLeft) {
 // https://stackoverflow.com/a/2602885
 $mask = ($mask & 0xF0) >> 4 | ($mask & 0x0F) << 4;
 $mask = ($mask & 0xCC) >> 2 | ($mask & 0x33) << 2;
 $mask = ($mask & 0xAA) >> 1 | ($mask & 0x55) << 1;
 }
 $int = SodiumUtil::chrToInt($input[$bytes]);
 $string .= SodiumUtil::intToChr($int & $mask);
 }
 return $string;
 }
 
 /**
 * Convert a nullable boolean to a string with a length of 1.
 *
 * @param bool|null $bool
 * @return string
 * @psalm-suppress RedundantConditionGivenDocblockType
 */
 public static function boolToChr($bool)
 {
 if (\is_null($bool)) {
 $int = 0;
 } elseif (\is_bool($bool)) {
 $int = $bool ? 2 : 1;
 } else {
 throw new \TypeError('Only TRUE, FALSE, or NULL allowed');
 }
 /** @var string $string */
 $string = \pack('C', $int);
 
 return $string;
 }
 
 /**
 * Convert a string with a length of 1 to a nullable boolean.
 *
 * @param string $string
 * @return bool|null
 */
 public static function chrToBool($string)
 {
 if (Binary::safeStrlen($string) !== 1) {
 throw new \OutOfRangeException(
 'String is not 1 length long'
 );
 }
 /** @var array<int, int> $unpacked */
 $unpacked = \unpack('C', $string);
 switch ($unpacked[1]) {
 case 0:
 return null;
 case 1:
 return false;
 case 2:
 return true;
 }
 throw new \InvalidArgumentException(
 'Internal integer is not 0, 1, or 2'
 );
 }
 
 /**
 * @param float $float
 * @return string
 *
 * @throws \SodiumException
 */
 public static function floatToString($float)
 {
 SodiumUtil::declareScalarType($float, 'float');
 /** @var bool|null $wrongEndian */
 static $wrongEndian = null;
 
 if (PHP_VERSION_ID >= 70015 && PHP_VERSION_ID !== 70100) {
 // PHP >= 7.0.15 or >= 7.1.1
 return (string) \pack('e', $float);
 } else {
 if (\is_null($wrongEndian)) {
 $wrongEndian = self::getWrongEndianness();
 }
 /** @var string $packed */
 $packed = (string) \pack('d', $float);
 if ($wrongEndian) {
 return \strrev($packed);
 }
 return $packed;
 }
 }
 
 /**
 * @param int $int
 * @return string
 */
 public static function intToString($int)
 {
 return SodiumUtil::store64_le($int);
 }
 
 /**
 * Increase a counter nonce, starting with the LSB (big-endian)
 *
 * @param string $nonce
 * @return string
 */
 public static function ctrNonceIncrease($nonce)
 {
 /** @var array<int, int> $pieces */
 $pieces = \unpack('C*', $nonce);
 $c = 0;
 ++$pieces[16];
 for ($i = 16; $i > 0; --$i) {
 $pieces[$i] += $c;
 $c = $pieces[$i] >> 8;
 $pieces[$i] &= 0xff;
 }
 \array_unshift($pieces, \str_repeat('C', 16));
 return (string) \call_user_func_array('pack', $pieces);
 }
 
 /**
 * @param SymmetricKey $key
 * @param string|null $salt
 * @param string $info
 * @param int $length
 * @param string $hash
 *
 * @return string
 * @throws CryptoOperationException
 */
 public static function HKDF(
 SymmetricKey $key,
 $salt = null,
 $info = '',
 $length = 32,
 $hash = 'sha384'
 ) {
 static $nativeHKDF = null;
 if ($nativeHKDF === null) {
 $nativeHKDF = \is_callable('\\hash_hkdf');
 }
 /** @var string $ikm */
 $ikm = $key->getRawKey();
 
 if ($nativeHKDF) {
 /**
 * @psalm-suppress UndefinedFunction
 * This is wrapped in an is_callable() check.
 */
 return (string) \hash_hkdf(
 $hash,
 $ikm,
 $length,
 $info,
 (string) $salt
 );
 }
 
 $digest_length = Binary::safeStrlen(
 \hash_hmac($hash, '', '', true)
 );
 
 // Sanity-check the desired output length.
 if (empty($length) || $length < 0 || $length > 255 * $digest_length) {
 throw new CryptoOperationException(
 'Bad output length requested of HKDF.'
 );
 }
 
 // "if [salt] not provided, is set to a string of HashLen zeroes."
 if (\is_null($salt)) {
 $salt = \str_repeat("\x00", $digest_length);
 }
 
 // HKDF-Extract:
 // PRK = HMAC-Hash(salt, IKM)
 // The salt is the HMAC key.
 $prk = \hash_hmac($hash, $ikm, $salt, true);
 
 // HKDF-Expand:
 // T(0) = ''
 $t          = '';
 $last_block = '';
 for ($blockIndex = 1; Binary::safeStrlen($t) < $length; ++$blockIndex) {
 // T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??)
 $last_block = \hash_hmac(
 $hash,
 $last_block . $info . \pack('C', $blockIndex),
 $prk,
 true
 );
 // T = T(1) | T(2) | T(3) | ... | T(N)
 $t .= $last_block;
 }
 
 // ORM = first L octets of T
 /** @var string $orm */
 $orm = Binary::safeSubstr($t, 0, $length);
 return (string) $orm;
 }
 
 /**
 * Used for packing [table, field, index] names together in a way that
 * resists and/or prevents collisions caused by operator error.
 *
 * @param array<int, string> $pieces
 * @return string
 */
 public static function pack(array $pieces)
 {
 $output = SodiumUtil::store32_le(\count($pieces));
 foreach ($pieces as $piece) {
 $output .= SodiumUtil::store64_le(
 Binary::safeStrlen($piece)
 );
 $output .= $piece;
 }
 return $output;
 }
 
 /**
 * @param string $string
 * @return int
 * @throws \SodiumException
 */
 public static function stringToInt($string)
 {
 return SodiumUtil::load64_le($string);
 }
 
 /**
 * @param string $string
 * @return float
 *
 * @throws \SodiumException
 */
 public static function stringToFloat($string)
 {
 SodiumUtil::declareScalarType($string, 'string');
 /** @var bool|null $wrongEndian */
 static $wrongEndian = null;
 
 if (PHP_VERSION_ID >= 70015 && PHP_VERSION_ID !== 70100) {
 // PHP >= 7.0.15 or >= 7.1.1
 /** @var array{1: float} $unpacked */
 $unpacked = \unpack('e', (string) $string);
 return (float) $unpacked[1];
 } else {
 if (\is_null($wrongEndian)) {
 $wrongEndian = self::getWrongEndianness();
 }
 if ($wrongEndian) {
 $string = \strrev((string) $string);
 }
 $unpacked = \unpack('d', (string) $string);
 return (float) $unpacked[1];
 }
 }
 
 /**
 * @return bool|null
 */
 private static final function getWrongEndianness()
 {
 $x = \pack('d', 1.618);
 if ($x === "\x17\xd9\xce\xf7\x53\xe3\xf9\x3f") {
 return false;
 } elseif ($x === "\x3f\xf9\xe3\x53\xf7\xce\xd9\x17") {
 return true;
 }
 return null;
 }
 }
 
 |