Example #1
0
 /**
  * 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);
 }
Example #2
0
 /**
  * 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;
 }
Example #3
0
 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));
 }
Example #4
0
 /**
  *
  * @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;
 }
Example #5
0
 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);
 }
Example #6
0
 /**
  * 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);
 }
Example #7
0
 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());
 }
Example #8
0
 /**
  * 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;
 }
Example #9
0
 /**
  * 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));
 }
Example #10
0
    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.
Example #11
0
 /**
  * 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);
 }
Example #12
0
 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'};
 }
Example #14
0
 /**
  * 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;
 }
Example #15
0
 /**
  * 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)));
 }
Example #16
0
 /**
  * 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;
 }
Example #17
0
 /**
  * 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');
 }
Example #18
0
 /**
  * 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)));
 }
Example #19
0
 /**
  * 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;
 }
Example #20
0
 /**
  * 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;
 }
Example #21
0
 /**
  * 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');
 }
Example #22
0
 /**
  * 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;
 }