/** * Calculate the Merkle root, taking care to distinguish between * leaves and branches (0x01 for the nodes, 0x00 for the branches) * to protect against second-preimage attacks * * @return string */ protected function calculateRoot() : string { $size = \count($this->nodes); $order = self::getSizeRoundedUp($size); $hash = []; $debug = []; // Population (Use self::MERKLE_LEAF as a prefix) for ($i = 0; $i < $order; ++$i) { if ($i >= $size) { $hash[$i] = self::MERKLE_LEAF . $this->nodes[$size - 1]->getHash(true); $debug[$i] = \Sodium\bin2hex($hash[$i]); } else { $hash[$i] = self::MERKLE_LEAF . $this->nodes[$i]->getHash(true); $debug[$i] = \Sodium\bin2hex($hash[$i]); } } // Calculation (Use self::MERKLE_BRANCH as a prefix) do { $tmp = []; $j = 0; for ($i = 0; $i < $order; $i += 2) { if (empty($hash[$i + 1])) { $tmp[$j] = \Sodium\crypto_generichash(self::MERKLE_BRANCH . $hash[$i] . $hash[$i]); } else { $tmp[$j] = \Sodium\crypto_generichash(self::MERKLE_BRANCH . $hash[$i] . $hash[$i + 1]); } ++$j; } $hash = $tmp; $order >>= 1; } while ($order > 1); // We should only have one value left:t return \array_shift($hash); }
/** * Use a derivative of HKDF to derive multiple keys from one. * http://tools.ietf.org/html/rfc5869 * * This is a variant from hash_hkdf() and instead uses BLAKE2b provided by * libsodium. * * Important: instead of a true HKDF (from HMAC) construct, this uses the * \Sodium\crypto_generichash() key parameter. This is *probably* okay. * * @param string $ikm Initial Keying Material * @param int $length How many bytes? * @param string $info What sort of key are we deriving? * @param string $salt * @return string * @throws \ParagonIE\Halite\Alerts\InvalidDigestLength * @throws \ParagonIE\Halite\Alerts\CannotPerformOperation */ public static function hkdfBlake2b($ikm, $length, $info = '', $salt = null) { // Sanity-check the desired output length. if (empty($length) || !\is_int($length) || $length < 0 || $length > 255 * \Sodium\CRYPTO_GENERICHASH_KEYBYTES) { throw new \ParagonIE\Halite\Alerts\InvalidDigestLength('Bad HKDF Digest Length'); } // "If [salt] not provided, is set to a string of HashLen zeroes." if (\is_null($salt)) { $salt = \str_repeat("", \Sodium\CRYPTO_GENERICHASH_KEYBYTES); } // HKDF-Extract: // PRK = HMAC-Hash(salt, IKM) // The salt is the HMAC key. $prk = \Sodium\crypto_generichash($ikm, $salt); // HKDF-Expand: // This check is useless, but it serves as a reminder to the spec. if (self::safeStrlen($prk) < \Sodium\CRYPTO_GENERICHASH_KEYBYTES) { throw new \ParagonIE\Halite\Alerts\CannotPerformOperation('An unknown error has occurred'); } // T(0) = '' $t = ''; $last_block = ''; for ($block_index = 1; self::safeStrlen($t) < $length; ++$block_index) { // T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??) $last_block = \Sodium\crypto_generichash($last_block . $info . \chr($block_index), $prk); // T = T(1) | T(2) | T(3) | ... | T(N) $t .= $last_block; } // ORM = first L octets of T $orm = self::safeSubstr($t, 0, $length); if ($orm === false) { throw new \ParagonIE\Halite\Alerts\CannotPerformOperation('An unknown error has occurred'); } return $orm; }
public function testHash() { $stringData = \random_bytes(32); $hash = \Sodium\crypto_generichash($stringData); $node = new Node($stringData); $this->assertSame($stringData, $node->getData()); $this->assertSame($hash, $node->getHash(true)); }
/** * * @return string */ protected function calculateHash() : string { $numTrees = \count($this->trees); $hash = $this->startHash; for ($i = 0; $i < $numTrees; ++$i) { $hash = \Sodium\crypto_generichash($this->trees[$i]->getRoot(true), $hash); } return $hash; }
public function testChecksum() { $csum = File::checksumFile(__DIR__ . '/tmp/paragon_avatar.png'); $this->assertEquals($csum, "09f9f74a0e742d057ca08394db4c2e444be88c0c94fe9a914c3d3758c7eccafb" . "8dd286e3d6bc37f353e76c0c5aa2036d978ca28ffaccfa59f5dc1f076c5517a0"); $data = \Sodium\randombytes_buf(32); \file_put_contents(__DIR__ . '/tmp/garbage.dat', $data); $hash = \Sodium\crypto_generichash($data, null, 64); $file = File::checksumFile(__DIR__ . '/tmp/garbage.dat', null, true); $this->assertEquals($hash, $file); }
/** * Hash a message and return a string. * * @param string $msg The message to be hashed. * @param string $key The key for the message to be hashed against. * @param int $length The length of the hash digest. * @return string * @throws Exceptions\InvalidTypeException * @throws Exceptions\OutOfRangeException */ public static function hash($msg, $key = '', $length = Constants::GENERICHASH_BYTES) { # Test the length for validity. Helpers::rangeCheck($length, Constants::GENERICHASH_BYTES_MAX, Constants::GENERICHASH_BYTES_MIN, 'Hash', 'hash'); # Test the message and key for string validity. Helpers::isString($msg, 'Hash', 'hash'); Helpers::isString($key, 'Hash', 'hash'); # Return the hash to the client. return \Sodium\crypto_generichash($msg, $key, $length); }
public function testChain() { $trees = [new MerkleTree(new Node('418 i am a little teapot'), new Node('yellow submarine'), new Node('paragon initiative enterprises'), new Node('application security')), new MerkleTree(new Node('cryptography'), new Node('engineering'), new Node('for complete noobcakes')), new MerkleTree(new Node('418 i am a little teapot'), new Node('yellow submarine'), new Node('paragon initiative enterprises'), new Node('application security'), new Node('cryptography'), new Node('engineering'), new Node('for complete noobcakes'))]; $begin = \Sodium\crypto_generichash('GENESIS BLOCK'); $bc1 = new BlockChain($begin, $trees[0]); $bc2 = new BlockChain($bc1->getHash(true), $trees[1]); $bc3 = new BlockChain($begin, $trees[0], $trees[1]); $bc4 = new BlockChain($begin, $trees[2]); $treeTest = $trees[0]->getExpandedTree(new Node('cryptography'), new Node('engineering'), new Node('for complete noobcakes')); $bc5 = new BlockChain($begin, $treeTest); // The output of one block feeding into the next should match: $this->assertEquals($bc2->getHash(), $bc3->getHash()); // We shouldn't allow blocks to be appeneded: $this->assertNotEquals($bc3->getHash(), $bc4->getHash()); // However, if we create a new block, it should be OK: $this->assertEquals($bc4->getHash(), $bc5->getHash()); }
/** * Retrieve a token array for unit testing endpoints * * @param string $lockTo - Only get tokens locked to a particular form * * @return string */ public function getTokenString(string $lockTo = '') : string { if (!isset($_SESSION[$this->sessionIndex])) { $_SESSION[$this->sessionIndex] = []; } if (empty($lockTo)) { $lockTo = $_SERVER['REQUEST_URI'] ?? '/'; } if (\preg_match('#/$#', $lockTo)) { $lockTo = Util::subString($lockTo, 0, Util::stringLength($lockTo) - 1); } list($index, $token) = $this->generateToken($lockTo); if ($this->hmacIP) { // Use a keyed BLAKE2b hash to only allow this particular IP to send this request $token = Base64UrlSafe::encode(\Sodium\crypto_generichash($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1', Base64UrlSafe::decode($token), \Sodium\CRYPTO_GENERICHASH_BYTES)); } return $index . ':' . $token; }
/** * HMAC-BLAKE2b * * @param string $message * @param string $key */ public function hashBlake2b($message, $key) { if (self::safeStrlen($key) > \Sodium\CRYPTO_GENERICHASH_KEYBYTES) { $key = \Sodium\crypto_generichash($key); } elseif (self::safeStrlen($key) < \Sodium\CRYPTO_GENERICHASH_BYTES) { $key = \str_pad($key, \Sodium\CRYPTO_GENERICHASH_KEYBYTES, "", STR_PAD_RIGHT); } $opad = ''; $ipad = ''; for ($i = 0; $o < \Sodium\CRYPTO_GENERICHASH_KEYBYTES; ++$i) { $opad .= \chr(0x5c ^ \ord($key[$i])); $ipad .= \chr(0x36 ^ \ord($key[$i])); } return \Sodium\crypto_generichash($opad . \Sodium\crypto_generichash($ipad . $message)); }
exit(0); } $twigLoader = new \Twig_Loader_Filesystem(ROOT . '/Installer/skins'); $twigEnv = new \Twig_Environment($twigLoader); // Expose PHP's built-in functions as a filter $twigEnv->addFilter(new Twig_SimpleFilter('addslashes', 'addslashes')); $twigEnv->addFilter(new Twig_SimpleFilter('preg_quote', 'preg_quote')); $twigEnv->addFilter(new Twig_SimpleFilter('ceil', 'ceil')); $twigEnv->addFilter(new Twig_SimpleFilter('floor', 'floor')); $twigEnv->addFilter(new Twig_SimpleFilter('cachebust', function ($relative_path) { if ($relative_path[0] !== '/') { $relative_path = '/' . $relative_path; } $absolute = $_SERVER['DOCUMENT_ROOT'] . $relative_path; if (\is_readable($absolute)) { return $relative_path . '?' . Base64UrlSafe::encode(\Sodium\crypto_generichash(\file_get_contents($absolute) . \filemtime($absolute))); } return $relative_path . '?404NotFound'; })); $twigEnv->addFunction(new Twig_SimpleFunction('form_token', function ($lockTo = '') { static $csrf = null; if ($csrf === null) { $csrf = new \Airship\Engine\Security\CSRF(); } return $csrf->insertToken($lockTo); })); $twigEnv->addFunction(new Twig_SimpleFunction('cabin_url', function () { return '/'; })); $twigEnv->addFunction(new Twig_SimpleFunction('__', function (string $str = '') { // Not translating here.
/** * Wrapper around \Sodium\crypto_generichash() * * Expects a key (binary string). * Returns raw binary. * * @param string $input * @param string $key * @param int $length * @return string * @throws CannotPerformOperation */ public static function raw_keyed_hash(string $input, string $key, int $length = \Sodium\CRYPTO_GENERICHASH_BYTES) : string { if ($length < \Sodium\CRYPTO_GENERICHASH_BYTES_MIN) { throw new CannotPerformOperation(\sprintf('Output length must be at least %d bytes.', \Sodium\CRYPTO_GENERICHASH_BYTES_MIN)); } if ($length > \Sodium\CRYPTO_GENERICHASH_BYTES_MAX) { throw new CannotPerformOperation(\sprintf('Output length must be at most %d bytes.', \Sodium\CRYPTO_GENERICHASH_BYTES_MAX)); } return \Sodium\crypto_generichash($input, $key, $length); }
public function __construct(string $data) { $this->data = $data; $this->hash = \Sodium\crypto_generichash($data); }
/** * Check if a password is known by the knownpassword.org API. * * @param string $password The password to check. * @param string $passwordFormat The format of the given password (Blake2b, Sha512, Cleartext) [Default: Blake2b]. * @return mixed Exception on error, true if the password is known and false if the password is unknown. * @access public */ public function checkPassword($password, $passwordFormat = "Blake2b") { $apiData = array(); switch ($passwordFormat) { case "Blake2b": $apiData = array("Blake2b" => $password); break; case "Sha512": $apiData = array("Sha512" => $password); break; case "Cleartext": $apiData = array("Cleartext" => $password); break; default: throw new \Exception("Unknown passwordFormat."); } $nonce = \Sodium\randombytes_buf(24); $signature = \Sodium\crypto_sign_detached($nonce, $this->_privatekey); $clearJson = json_encode($apiData); $encryptionNonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_BOX_NONCEBYTES); $encryptionKeyPair = \Sodium\crypto_box_keypair(); $encryptionSecretkey = \Sodium\crypto_box_secretkey($encryptionKeyPair); $encryptionPublickey = \Sodium\crypto_box_publickey($encryptionKeyPair); $encryptionKeyPair = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($encryptionSecretkey, $this->_serverEncryptionPublicKey); $ciphertext = \Sodium\crypto_box($clearJson, $encryptionNonce, $encryptionKeyPair); $encryptedApiData = array("PublicKey" => \Sodium\bin2hex($encryptionPublickey), "Nonce" => \Sodium\bin2hex($encryptionNonce), "Ciphertext" => \Sodium\bin2hex($ciphertext)); $data_string = json_encode($encryptedApiData); $ch = curl_init($this->_apiurl . "/checkpassword"); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Content-Length: ' . strlen($data_string), 'User-Agent: ' . 'Laravel 5', 'X-Public: ' . \Sodium\bin2hex($this->_publickey), 'X-Nonce: ' . \Sodium\bin2hex($nonce), 'X-Signature: ' . \Sodium\bin2hex($signature))); if (!($result = curl_exec($ch))) { throw new \Exception("Request failed"); } $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header = substr($result, 0, $header_size); $headers = $this->get_headers_from_curl_response($header); if (array_key_exists("http_code", $headers[0]) && array_key_exists("X-Powered-By", $headers[0]) && array_key_exists("X-Signature", $headers[0])) { $httpCode = $headers[0]["http_code"]; $responsePowered = $headers[0]["X-Powered-By"]; $responseSignature = $headers[0]["X-Signature"]; $responseNonce = $headers[0]["X-Nonce"]; if ($httpCode === "HTTP/1.1 200 OK" || $httpCode === "HTTP/2.0 200 OK") { if ($responsePowered === "bitbeans") { // validate the response signature if (!\Sodium\crypto_sign_verify_detached(\Sodium\hex2bin($responseSignature), \Sodium\crypto_generichash(\Sodium\hex2bin($responseNonce), null, 64), $this->_serverSignaturePublicKey)) { throw new \Exception("Invalid signature"); } } else { throw new \Exception("Invalid server"); } } else { throw new \Exception("Invalid response code"); } } else { throw new \Exception("Invalid header"); } $result = substr($result, $header_size); curl_close($ch); $resultJson = json_decode($result); $decryptionKeyPair = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($encryptionSecretkey, \Sodium\hex2bin($resultJson->{'publicKey'})); $plaintext = \Sodium\crypto_box_open(\Sodium\hex2bin($resultJson->{'ciphertext'}), \Sodium\hex2bin($resultJson->{'nonce'}), $decryptionKeyPair); if ($plaintext === FALSE) { throw new \Exception("Malformed message or invalid MAC"); } $plaintextJson = json_decode($plaintext); return !$plaintextJson->{'FoundPassword'}; }
/** * Unseal the contents of a file. * * @param ReadOnlyFile $input * @param MutableFile $output * @param EncryptionSecretKey $secretKey * @return bool * @throws CannotPerformOperation * @throws InvalidMessage */ protected static function unsealData(ReadOnlyFile $input, MutableFile $output, EncryptionSecretKey $secretKey) : bool { $publicKey = $secretKey->derivePublicKey(); // Is the file at least as long as a header? if ($input->getSize() < Halite::VERSION_TAG_LEN) { throw new InvalidMessage("Input file is too small to have been encrypted by Halite."); } // Parse the header, ensuring we get 4 bytes $header = $input->readBytes(Halite::VERSION_TAG_LEN); // Load the config $config = self::getConfig($header, 'seal'); if ($input->getSize() < $config->SHORTEST_CIPHERTEXT_LENGTH) { throw new InvalidMessage("Input file is too small to have been encrypted by Halite."); } // Let's grab the public key and salt $ephPublic = $input->readBytes($config->PUBLICKEY_BYTES); $hkdfSalt = $input->readBytes($config->HKDF_SALT_LEN); // Generate the same nonce, as per sealData() $nonce = \Sodium\crypto_generichash($ephPublic . $publicKey->getRawKeyMaterial(), '', \Sodium\CRYPTO_STREAM_NONCEBYTES); // Create a key object out of the public key: $ephemeral = new EncryptionPublicKey(new HiddenString($ephPublic)); $key = AsymmetricCrypto::getSharedSecret($secretKey, $ephemeral, true); unset($ephemeral); list($encKey, $authKey) = self::splitKeys($key, $hkdfSalt, $config); // We no longer need the original key after we split it unset($key); $mac = \Sodium\crypto_generichash_init($authKey); \Sodium\crypto_generichash_update($mac, $header); \Sodium\crypto_generichash_update($mac, $ephPublic); \Sodium\crypto_generichash_update($mac, $hkdfSalt); $oldMACs = self::streamVerify($input, Util::safeStrcpy($mac), $config); // We no longer need this: \Sodium\memzero($hkdfSalt); $ret = self::streamDecrypt($input, $output, new EncryptionKey(new HiddenString($encKey)), $nonce, $mac, $config, $oldMACs); unset($encKey); unset($authKey); unset($nonce); unset($mac); unset($config); unset($oldMACs); return $ret; }
/** * Save a key to a file * * @param string $filePath * @param string $keyData * @return int|bool */ protected static function saveKeyFile($filePath, $keyData) { return \file_put_contents($filePath, \Sodium\bin2hex(Halite::HALITE_VERSION_KEYS . $keyData . \Sodium\crypto_generichash(Halite::HALITE_VERSION_KEYS . $keyData, null, \Sodium\CRYPTO_GENERICHASH_BYTES_MAX))); }
/** * Unseal a (file handle) * * @param ReadOnlyFile $input * @param MutableFile $output * @param EncryptionSecretKey $secretkey * * @return bool * @throws CryptoException\CannotPerformOperation * @throws CryptoException\InvalidMessage */ protected static function unsealData(ReadOnlyFile $input, MutableFile $output, EncryptionSecretKey $secretkey) : bool { $secret_key = $secretkey->getRawKeyMaterial(); $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key); if ($input->getSize() < Halite::VERSION_TAG_LEN) { throw new CryptoException\InvalidMessage("File is too small to have been encrypted by Halite."); } // Parse the header, ensuring we get 4 bytes $header = $input->readBytes(Halite::VERSION_TAG_LEN); // Load the config $config = self::getConfig($header, 'seal'); if ($input->getSize() < $config->SHORTEST_CIPHERTEXT_LENGTH) { throw new CryptoException\InvalidMessage("File is too small to have been encrypted by Halite."); } // Let's grab the public key and salt $eph_public = $input->readBytes($config->PUBLICKEY_BYTES); $hkdfsalt = $input->readBytes($config->HKDF_SALT_LEN); $nonce = \Sodium\crypto_generichash($eph_public . $public_key, '', \Sodium\CRYPTO_STREAM_NONCEBYTES); $ephemeral = new EncryptionPublicKey($eph_public); $key = AsymmetricCrypto::getSharedSecret($secretkey, $ephemeral, true); list($encKey, $authKey) = self::splitKeys($key, $hkdfsalt, $config); // We no longer need the original key after we split it unset($key); if ($config->USE_BLAKE2B) { $mac = \Sodium\crypto_generichash_init($authKey); \Sodium\crypto_generichash_update($mac, $header); \Sodium\crypto_generichash_update($mac, $eph_public); \Sodium\crypto_generichash_update($mac, $hkdfsalt); $old_macs = self::streamVerify($input, '' . $mac, $config); } else { $mac = \hash_init('sha256', HASH_HMAC, $authKey); \hash_update($mac, $header); \hash_update($mac, $eph_public); \hash_update($mac, $hkdfsalt); // This will throw an exception if it fails. $old_macs = self::streamVerify($input, \hash_copy($mac), $config); } $ret = self::streamDecrypt($input, $output, new EncryptionKey($encKey), $nonce, $mac, $config, $old_macs); unset($encKey); unset($authKey); unset($nonce); unset($mac); unset($config); unset($old_macs); return $ret; }
/** * Verify a Message Authentication Code (MAC) of a message, with a shared * key. * * @param string $mac Message Authentication Code * @param string $message The message to verify * @param string $authKey Authentication key (symmetric) * @param SymmetricConfig $config Configuration object * @return bool * @throws InvalidMessage * @throws InvalidSignature */ protected static function verifyMAC(string $mac, string $message, string $authKey, SymmetricConfig $config) : bool { if (CryptoUtil::safeStrlen($mac) !== $config->MAC_SIZE) { throw new InvalidSignature('Argument 1: Message Authentication Code is not the correct length; is it encoded?'); } if ($config->MAC_ALGO === 'BLAKE2b') { $calc = \Sodium\crypto_generichash($message, $authKey, $config->MAC_SIZE); $res = \hash_equals($mac, $calc); \Sodium\memzero($calc); return $res; } throw new InvalidMessage('Invalid Halite version'); }
/** * Save a key to a file * * @param string $filePath * @param string $keyData * @return int|bool */ protected static function saveKeyFile(string $filePath, string $keyData) : bool { return false !== \file_put_contents($filePath, \Sodium\bin2hex(Halite::HALITE_VERSION_KEYS . $keyData . \Sodium\crypto_generichash(Halite::HALITE_VERSION_KEYS . $keyData, '', \Sodium\CRYPTO_GENERICHASH_BYTES_MAX))); }
/** * Unseal a (file handle) * * @param $input * @param $output * @param \ParagonIE\Halite\Contract\CryptoKeyInterface $secretkey */ public static function unsealResource($input, $output, \ParagonIE\Halite\Contract\CryptoKeyInterface $secretkey) { // Input validation if (!\is_resource($input)) { throw new \ParagonIE\Halite\Alerts\InvalidType('Expected input handle to be a resource'); } if (!\is_resource($output)) { throw new \ParagonIE\Halite\Alerts\InvalidType('Expected output handle to be a resource'); } if (!$secretkey->isSecretKey()) { throw new CryptoAlert\InvalidKey('Expected a secret key'); } if (!$secretkey->isAsymmetricKey()) { throw new CryptoAlert\InvalidKey('Expected a key intended for asymmetric-key cryptography'); } $secret_key = $secretkey->get(); $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key); // Parse the header, ensuring we get 4 bytes $header = self::readBytes($input, Halite::VERSION_TAG_LEN); // Load the config $config = self::getConfig($header, 'seal'); // Let's grab the public key and salt $eph_public = self::readBytes($input, $config['PUBLICKEY_BYTES']); $hkdfsalt = self::readBytes($input, $config['HKDF_SALT_LEN']); $nonce = \Sodium\crypto_generichash($eph_public . $public_key, null, \Sodium\CRYPTO_STREAM_NONCEBYTES); $ephemeral = new Key($eph_public, true, false, true); $key = Asymmetric::getSharedSecret($secretkey, $ephemeral, true); list($encKey, $authKey) = self::splitKeys($key, $hkdfsalt); // We no longer need the original key after we split it unset($key); $mac = \hash_init('sha256', HASH_HMAC, $authKey); \hash_update($mac, $header); \hash_update($mac, $eph_public); \hash_update($mac, $hkdfsalt); // This will throw an exception if it fails. $old_macs = self::streamVerify($input, \hash_copy($mac), $config); $ret = self::streamDecrypt($input, $output, new Key($encKey), $nonce, $mac, $config, $old_macs); unset($encKey); unset($authKey); unset($nonce); unset($mac); unset($config); unset($old_macs); return $ret; }
/** * Unseal a (file handle) * * @param ReadOnlyFile $input * @param MutableFile $output * @param EncryptionSecretKey $secretkey */ public static function unsealStream(ReadOnlyFile $input, MutableFile $output, EncryptionSecretKey $secretkey) { if (!$secretkey instanceof EncryptionSecretKey) { throw new \ParagonIE\Halite\Alerts\InvalidKey('Argument 3: Expected an instance of EncryptionSecretKey'); } $secret_key = $secretkey->get(); $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key); // Parse the header, ensuring we get 4 bytes $header = $input->readBytes(Halite::VERSION_TAG_LEN); // Load the config $config = self::getConfig($header, 'seal'); // Let's grab the public key and salt $eph_public = $input->readBytes($config->PUBLICKEY_BYTES); $hkdfsalt = $input->readBytes($config->HKDF_SALT_LEN); $nonce = \Sodium\crypto_generichash($eph_public . $public_key, null, \Sodium\CRYPTO_STREAM_NONCEBYTES); $ephemeral = new EncryptionPublicKey($eph_public); $key = AsymmetricCrypto::getSharedSecret($secretkey, $ephemeral, true); list($encKey, $authKey) = self::splitKeys($key, $hkdfsalt, $config); // We no longer need the original key after we split it unset($key); $mac = \hash_init('sha256', HASH_HMAC, $authKey); \hash_update($mac, $header); \hash_update($mac, $eph_public); \hash_update($mac, $hkdfsalt); // This will throw an exception if it fails. $old_macs = self::streamVerify($input, \hash_copy($mac), $config); $ret = self::streamDecrypt($input, $output, new EncryptionKey($encKey), $nonce, $mac, $config, $old_macs); unset($encKey); unset($authKey); unset($nonce); unset($mac); unset($config); unset($old_macs); return $ret; }
/** * Decrypt a sealed message with our private key * * @param string $source Encrypted message (string or resource for a file) * @param Contract\CryptoKeyInterface $privateKey * @param boolean $raw Don't hex decode the input? * * @return string */ public static function unseal($source, Contract\CryptoKeyInterface $privateKey, $raw = false) { if (!$raw) { $source = \Sodium\hex2bin($source); } if ($privateKey->isSecretKey()) { if (function_exists('\\Sodium\\crypto_box_seal_open')) { // Get a box keypair (needed by crypto_box_seal_open) $secret_key = $privateKey->get(); $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key); $kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($secret_key, $public_key); // Now let's open that sealed box $message = \Sodium\crypto_box_seal_open($source, $kp); // Always memzero after retrieving a value \Sodium\memzero($secret_key); \Sodium\memzero($public_key); \Sodium\memzero($kp); } else { /** * Polyfill for libsodium < 1.0.3 */ // Let's generate the box keypair $my_secret = $privateKey->get(); $my_public = \Sodium\crypto_box_publickey_from_secretkey($my_secret); $eph_public = mb_substr($source, 0, \Sodium\CRYPTO_BOX_PUBLICKEYBYTES, '8bit'); $box_kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($my_secret, $eph_public); // Calculate the nonce as libsodium does $nonce = \Sodium\crypto_generichash($eph_public . $my_public, null, \Sodium\CRYPTO_BOX_NONCEBYTES); // $boxed is the ciphertext from crypto_box_seal $boxed = mb_substr($source, \Sodium\CRYPTO_BOX_PUBLICKEYBYTES, null, '8bit'); $message = \Sodium\crypto_box_open($boxed, $nonce, $box_kp); \Sodium\memzero($my_secret); \Sodium\memzero($my_public); \Sodium\memzero($box_kp); \Sodium\memzero($nonce); \Sodium\memzero($eph_public); } if ($message === false) { throw new CryptoAlert\InvalidKey('Incorrect secret key'); } // We have our encrypted message here return $message; } throw new CryptoAlert\InvalidKey('Expected a secret key'); }
/** * Get a relative BLAKE2b hash of an input. Formatted as two lookup * directories followed by a cache entry. 'hh/hh/hhhhhhhh...' * * @param string $preHash The cache identifier (will be hashed) * @param bool $asString Return a string? * @return string|array * @throws InvalidType */ public static function getRelativeHash(string $preHash, bool $asString = false) { $state = State::instance(); $cacheKey = $state->keyring['cache.hash_key']; if (!$cacheKey instanceof Key) { throw new InvalidType(\trk('errors.type.wrong_class', '\\ParagonIE\\Halite\\Key')); } // We use a keyed hash, with a distinct key per Airship deployment to // make collisions unlikely, $hash = \Sodium\crypto_generichash($preHash, $cacheKey->getRawKeyMaterial(), self::HASH_SIZE); $relHash = [\Sodium\bin2hex($hash[0]), \Sodium\bin2hex($hash[1]), \Sodium\bin2hex(Util::subString($hash, 2))]; if ($asString) { return \implode(DIRECTORY_SEPARATOR, $relHash); } return $relHash; }