/** * If the libsodium PHP extension is loaded, we'll use it above any other * solution. * * libsodium-php project: * @ref https://github.com/jedisct1/libsodium-php * * @param int $bytes * * @throws Exception * * @return string */ function random_bytes($bytes) { try { $bytes = RandomCompat_intval($bytes); } catch (TypeError $ex) { throw new TypeError('random_bytes(): $bytes must be an integer'); } if ($bytes < 1) { throw new Error('Length must be greater than 0'); } /** * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be * generated in one invocation. */ if ($bytes > 2147483647) { $buf = ''; for ($i = 0; $i < $bytes; $i += 1073741824) { $n = $bytes - $i > 1073741824 ? 1073741824 : $bytes - $i; $buf .= \Sodium\randombytes_buf($n); } } else { $buf = \Sodium\randombytes_buf($bytes); } if ($buf !== false) { if (RandomCompat_strlen($buf) === $bytes) { return $buf; } } /** * If we reach here, PHP has failed us. */ throw new Exception('Could not gather sufficient random data'); }
function safeEncrypt($message, $key) { $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES); $cipher = base64_encode($nonce . \Sodium\crypto_secretbox($message, $nonce, $key)); \Sodium\memzero($message); \Sodium\memzero($key); return $cipher; }
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); }
public function testFileRead() { $filename = \tempnam('/tmp', 'x'); $buf = \Sodium\randombytes_buf(65537); \file_put_contents($filename, $buf); $fStream = new ReadOnlyFile($filename); $this->assertSame($fStream->readBytes(65537), $buf); $fStream->reset(0); \file_put_contents($filename, Util::safeSubstr($buf, 0, 32768) . 'x' . Util::safeSubstr($buf, 32768)); try { $fStream->readBytes(65537); throw new \Exception('fail'); } catch (CryptoException\FileModified $ex) { $this->assertTrue($ex instanceof CryptoException\FileModified); } }
/** * Encrypt a message using the Halite encryption protocol * (Encrypt then MAC -- Xsalsa20 then HMAC-SHA-512/256) * * @param string $plaintext * @param EncryptionKey $secretKey * @param boolean $raw Don't hex encode the output? * @return string */ public static function encrypt(string $plaintext, EncryptionKey $secretKey, bool $raw = false) : string { $config = SymmetricConfig::getConfig(Halite::HALITE_VERSION, 'encrypt'); // Generate a nonce and HKDF salt: $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES); $salt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN); // Split our keys according to the HKDF salt: list($eKey, $aKey) = self::splitKeys($secretKey, $salt, $config); // Encrypt our message with the encryption key: $xored = \Sodium\crypto_stream_xor($plaintext, $nonce, $eKey); \Sodium\memzero($eKey); // Calculate an authentication tag: $auth = self::calculateMAC(Halite::HALITE_VERSION . $salt . $nonce . $xored, $aKey); \Sodium\memzero($aKey); if (!$raw) { return \Sodium\bin2hex(Halite::HALITE_VERSION . $salt . $nonce . $xored . $auth); } return Halite::HALITE_VERSION . $salt . $nonce . $xored . $auth; }
/** * Encrypt a message using the Halite encryption protocol * * @param string $plaintext * @param EncryptionKey $secretKey * @param boolean $raw Don't hex encode the output? * @return string */ public static function encrypt($plaintext, Contract\KeyInterface $secretKey, $raw = false) { if (!$secretKey instanceof EncryptionKey) { throw new CryptoException\InvalidKey('Expected an instance of EncryptionKey'); } $config = SymmetricConfig::getConfig(Halite::HALITE_VERSION, 'encrypt'); $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES); $salt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN); list($eKey, $aKey) = self::splitKeys($secretKey, $salt, $config); $xored = \Sodium\crypto_stream_xor($plaintext, $nonce, $eKey); $auth = self::calculateMAC(Halite::HALITE_VERSION . $salt . $nonce . $xored, $aKey); \Sodium\memzero($eKey); \Sodium\memzero($aKey); if (!$raw) { return \Sodium\bin2hex(Halite::HALITE_VERSION . $salt . $nonce . $xored . $auth); } return Halite::HALITE_VERSION . $salt . $nonce . $xored . $auth; }
/** * Seal a (file handle) * * @param ReadOnlyFile $input * @param MutableFile $output * @param EncryptionPublicKey $publickey * @return int */ protected static function sealData(ReadOnlyFile $input, MutableFile $output, EncryptionPublicKey $publickey) : int { // Generate a new keypair for this encryption $eph_kp = KeyFactory::generateEncryptionKeyPair(); $eph_secret = $eph_kp->getSecretKey(); $eph_public = $eph_kp->getPublicKey(); unset($eph_kp); // Calculate the shared secret key $key = AsymmetricCrypto::getSharedSecret($eph_secret, $publickey, true); // Destroy the secre tkey after we have the shared secret unset($eph_secret); $config = self::getConfig(Halite::HALITE_VERSION_FILE, 'seal'); // Generate a nonce as per crypto_box_seal $nonce = \Sodium\crypto_generichash($eph_public->getRawKeyMaterial() . $publickey->getRawKeyMaterial(), '', \Sodium\CRYPTO_STREAM_NONCEBYTES); // Generate a random HKDF salt $hkdfsalt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN); // Split the keys list($encKey, $authKey) = self::splitKeys($key, $hkdfsalt, $config); // We no longer need the original key after we split it unset($key); $output->writeBytes(Halite::HALITE_VERSION_FILE, Halite::VERSION_TAG_LEN); $output->writeBytes($eph_public->getRawKeyMaterial(), \Sodium\CRYPTO_BOX_PUBLICKEYBYTES); $output->writeBytes($hkdfsalt, $config->HKDF_SALT_LEN); if ($config->USE_BLAKE2B) { $mac = \Sodium\crypto_generichash_init($authKey); \Sodium\crypto_generichash_update($mac, Halite::HALITE_VERSION_FILE); \Sodium\crypto_generichash_update($mac, $eph_public->getRawKeyMaterial()); \Sodium\crypto_generichash_update($mac, $hkdfsalt); } else { $mac = \hash_init('sha256', HASH_HMAC, $authKey); // We no longer need $authKey after we set up the hash context unset($authKey); \hash_update($mac, Halite::HALITE_VERSION_FILE); \hash_update($mac, $eph_public->getRawKeyMaterial()); \hash_update($mac, $hkdfsalt); } \Sodium\memzero($authKey); unset($eph_public); return self::streamEncrypt($input, $output, new EncryptionKey($encKey), $nonce, $mac, $config); }
/** * @covers Symmetric::unpackMessageForDecryption() */ public function testUnpack() { $key = new EncryptionKey(new HiddenString(\str_repeat('A', 32))); // Randomly sized plaintext $size = \Sodium\randombytes_uniform(1023) + 1; $plaintext = \Sodium\randombytes_buf($size); $message = Symmetric::encrypt(new HiddenString($plaintext), $key, true); // Let's unpack our message $unpacked = Symmetric::unpackMessageForDecryption($message); // Now to test our expected results! $this->assertSame(Util::safeStrlen($unpacked[0]), Halite::VERSION_TAG_LEN); $this->assertTrue($unpacked[1] instanceof \ParagonIE\Halite\Symmetric\Config); $config = $unpacked[1]; if ($config instanceof \ParagonIE\Halite\Symmetric\Config) { $this->assertSame(Util::safeStrlen($unpacked[2]), $config->HKDF_SALT_LEN); $this->assertSame(Util::safeStrlen($unpacked[3]), \Sodium\CRYPTO_STREAM_NONCEBYTES); $this->assertSame(Util::safeStrlen($unpacked[4]), Util::safeStrlen($message) - (Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN + \Sodium\CRYPTO_STREAM_NONCEBYTES + $config->MAC_SIZE)); $this->assertSame(Util::safeStrlen($unpacked[5]), $config->MAC_SIZE); } else { $this->fail('Cannot continue'); } }
/** * Generates a string of random binary data of the specified length * * @param integer $length The number of bytes of random binary data to generate * @return string A binary string */ public function generate($length) { return \Sodium\randombytes_buf($length); }
function _appBaseEncrypt($data) { //return($data); $key = substr(_configBaseQuery("loadedHash"), 0, \Sodium\CRYPTO_SECRETBOX_KEYBYTES); $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES); $res = $nonce . \Sodium\crypto_secretbox($data, $nonce, $key); return $res; }
/** * Encrypt a message using the Halite encryption protocol * * @param string $plaintext * @param Key $secretKey * @param boolean $raw Don't hex encode the output? * @return string */ public static function encrypt($plaintext, Contract\CryptoKeyInterface $secretKey, $raw = false) { if ($secretKey->isAsymmetricKey()) { throw new CryptoAlert\InvalidKey('Expected a symmetric key, not an asymmetric key'); } if (!$secretKey->isEncryptionKey()) { throw new CryptoAlert\InvalidKey('Encryption key expected'); } $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES); $salt = \Sodium\randombytes_buf(Config::HKDF_SALT_LEN); list($eKey, $aKey) = self::splitKeys($secretKey, $salt); $xored = \Sodium\crypto_stream_xor($plaintext, $nonce, $eKey); $auth = self::calculateMAC(Config::HALITE_VERSION . $salt . $nonce . $xored, $aKey); \Sodium\memzero($eKey); \Sodium\memzero($aKey); if (!$raw) { return \Sodium\bin2hex(Config::HALITE_VERSION . $salt . $nonce . $xored . $auth); } return Config::HALITE_VERSION . $salt . $nonce . $xored . $auth; }
public function generatePublicKey() { return \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES); }
/** * @param int $size * @return string */ protected function createRandom($size) { /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ return \Sodium\randombytes_buf($size); }
/** * Seal a (file handle) * * @param ReadOnlyFile $input * @param MutableFile $output * @param EncryptionPublicKey $publickey */ public static function sealStream(ReadOnlyFile $input, MutableFile $output, KeyInterface $publickey) { if (!$publickey instanceof EncryptionPublicKey) { throw new \ParagonIE\Halite\Alerts\InvalidKey('Argument 3: Expected an instance of EncryptionPublicKey'); } // Generate a new keypair for this encryption $eph_kp = KeyFactory::generateEncryptionKeyPair(); $eph_secret = $eph_kp->getSecretKey(); $eph_public = $eph_kp->getPublicKey(); unset($eph_kp); // Calculate the shared secret key $key = AsymmetricCrypto::getSharedSecret($eph_secret, $publickey, true); // Destroy the secre tkey after we have the shared secret unset($eph_secret); $config = self::getConfig(Halite::HALITE_VERSION_FILE, 'seal'); // Generate a nonce as per crypto_box_seal $nonce = \Sodium\crypto_generichash($eph_public->get() . $publickey->get(), null, \Sodium\CRYPTO_STREAM_NONCEBYTES); // Generate a random HKDF salt $hkdfsalt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN); // Split the keys 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); // We no longer need to retain this after we've set up the hash context unset($authKey); $output->writeBytes(Halite::HALITE_VERSION_FILE, Halite::VERSION_TAG_LEN); $output->writeBytes($eph_public->get(), \Sodium\CRYPTO_BOX_PUBLICKEYBYTES); $output->writeBytes($hkdfsalt, $config->HKDF_SALT_LEN); \hash_update($mac, Halite::HALITE_VERSION_FILE); \hash_update($mac, $eph_public->get()); \hash_update($mac, $hkdfsalt); unset($eph_public); return self::streamEncrypt($input, $output, new EncryptionKey($encKey), $nonce, $mac, $config); }
/** * Generate an an encryption key (symmetric-key cryptography) * * @param &string $secret_key * @return EncryptionKey */ public static function generateEncryptionKey(string &$secret_key = '') : EncryptionKey { $secret_key = \Sodium\randombytes_buf(\Sodium\CRYPTO_STREAM_KEYBYTES); return new EncryptionKey($secret_key); }
/** * Encrypt a message using the Halite encryption protocol * * (Encrypt then MAC -- xsalsa20 then keyed-Blake2b) * You don't need to worry about chosen-ciphertext attacks. * * @param HiddenString $plaintext * @param EncryptionKey $secretKey * @param mixed $encoding * @return string */ public static function encrypt(HiddenString $plaintext, EncryptionKey $secretKey, $encoding = Halite::ENCODE_BASE64URLSAFE) : string { $config = SymmetricConfig::getConfig(Halite::HALITE_VERSION, 'encrypt'); // Generate a nonce and HKDF salt: $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES); $salt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN); /* Split our key into two keys: One for encryption, the other for authentication. By using separate keys, we can reasonably dismiss likely cross-protocol attacks. This uses salted HKDF to split the keys, which is why we need the salt in the first place. */ list($encKey, $authKey) = self::splitKeys($secretKey, $salt, $config); // Encrypt our message with the encryption key: $encrypted = \Sodium\crypto_stream_xor($plaintext->getString(), $nonce, $encKey); \Sodium\memzero($encKey); // Calculate an authentication tag: $auth = self::calculateMAC(Halite::HALITE_VERSION . $salt . $nonce . $encrypted, $authKey, $config); \Sodium\memzero($authKey); $message = Halite::HALITE_VERSION . $salt . $nonce . $encrypted . $auth; // Wipe every superfluous piece of data from memory \Sodium\memzero($nonce); \Sodium\memzero($salt); \Sodium\memzero($encrypted); \Sodium\memzero($auth); $encoder = Halite::chooseEncoder($encoding); if ($encoder) { return $encoder($message); } return $message; }
/** * Seal a (file handle) * * @param $input * @param $output * @param \ParagonIE\Halite\Contract\CryptoKeyInterface $publickey */ public static function sealResource($input, $output, \ParagonIE\Halite\Contract\CryptoKeyInterface $publickey) { // 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 (!$publickey->isPublicKey()) { throw new CryptoAlert\InvalidKey('Especter a public key'); } if (!$publickey->isAsymmetricKey()) { throw new CryptoAlert\InvalidKey('Expected a key intended for asymmetric-key cryptography'); } // Generate a new keypair for this encryption list($eph_secret, $eph_public) = Key::generate(Key::CRYPTO_BOX); // Calculate the shared secret key $key = Asymmetric::getSharedSecret($eph_secret, $publickey, true); // Destroy the secre tkey after we have the shared secret unset($eph_secret); $config = self::getConfig(Halite::HALITE_VERSION, 'seal'); // Generate a nonce as per crypto_box_seal $nonce = \Sodium\crypto_generichash($eph_public->get() . $publickey->get(), null, \Sodium\CRYPTO_STREAM_NONCEBYTES); // Generate a random HKDF salt $hkdfsalt = \Sodium\randombytes_buf($config['HKDF_SALT_LEN']); // Split the keys 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); // We no longer need to retain this after we've set up the hash context unset($authKey); $written = \fwrite($output, Halite::HALITE_VERSION, Halite::VERSION_TAG_LEN); if ($written === false) { throw new FileAlert\AccessDenied('Could not write to the file'); } $written &= \fwrite($output, $eph_public->get(), \Sodium\CRYPTO_BOX_PUBLICKEYBYTES); if ($written === false) { throw new FileAlert\AccessDenied('Could not write to the file'); } $written &= \fwrite($output, $hkdfsalt, Halite::HKDF_SALT_LEN); if ($written === false) { throw new FileAlert\AccessDenied('Could not write to the file'); } \hash_update($mac, Halite::HALITE_VERSION); \hash_update($mac, $eph_public->get()); \hash_update($mac, $hkdfsalt); unset($eph_public); return self::streamEncrypt($input, $output, new Key($encKey), $nonce, $mac, $config); }
/** * Generate an an encryption key (symmetric-key cryptography) * * @param &string $secret_key * @return EncryptionKey */ public static function generateEncryptionKey(&$secret_key = null) { $secret_key = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_KEYBYTES); return new EncryptionKey($secret_key); }
function crCryptoProcess($dir, $input, $eckey, $cipher) { // sanity checks $allowedDir = array('encrypt', 'decrypt'); $inputTypes = array('string', 'ad'); $cipherList = array('aes256gcm', 'chacha'); if (!in_array($dir, $allowedDir)) { $this->webLog($this->crLanguage('generic', 'invalidKey', array('dir', implode('|', $allowedDir))), __METHOD__, 'error'); return false; } if (is_string($input)) { $input = (object) array('string' => $input, 'ad' => ''); } if (is_object($input)) { if (count((array) $input) === 2) { foreach ($input as $key => $value) { if (!in_array($key, $inputTypes)) { $this->webLog($this->crLanguage('generic', 'invalidKey', array('input', implode('|', $inputTypes))), __METHOD__, 'error'); return false; } } } else { $this->webLog($this->crLanguage('generic', 'tooManyKeys', array(count($inputTypes), count($input), "input::(" . implode('|', $inputTypes) . ")")), __METHOD__, 'error'); return false; } } /*if ( !is_string($eckey) ) { $this->webLog($this->crLanguage('generic', 'missingKeyType', 'eckey'), __METHOD__, 'error'); return false; }*/ if (!in_array($cipher, $cipherList)) { $this->webLog($this->crLanguage('generic', 'invalidKey', array('cipher', implode('|', $cipherList))), __METHOD__, 'error'); return false; } // if the eckey matches the name of a stored key we'll map it to the proper eckey $keyid = '00-'; if (isset($this->crCryptoKeys->{$eckey})) { // set our keyid $keyid = $this->crCryptoKeys->{$eckey}->keyid; // create our index $this->crCryptoKeys->index->{$keyid} = $eckey; // set our eckey to the hex value $eckey = $this->crCryptoKeys->{$eckey}->eckey; } // process the request $start = microtime(true); // if we're decrypting then the input should be nonce.ciphertext if ($dir === 'decrypt') { // grab our nonce bytes param if ($cipher === 'aes256gcm') { $bytes = \Sodium\CRYPTO_AEAD_AES256GCM_NPUBBYTES; } if ($cipher === 'chacha') { $bytes = \Sodium\CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES; } // grab our message data // accepts two input types: <keyid>$<ciphertext> or just <ciphertext> $data = explode('$', $input->string); if (count($data) === 2) { // located a keyid and ciphertext $keyid = $data[0]; $message = $data[1]; // no keyid was located, so treat it as pure ciphertext } else { $data = $input->string; } // parse the message contents $message = \Sodium\hex2bin($message); $nonce = mb_substr($message, 0, $bytes, '8bit'); $ciphertext = mb_substr($message, $bytes, null, '8bit'); // on decrypt only: check if a key has been provided yet if ($eckey === false) { // check if the keyid is indexed if (isset($this->crCryptoKeys->index->{$keyid})) { // found a match; set our eckey $eckey = $this->crCryptoKeys->{$this->crCryptoKeys->index->{$keyid}}->eckey; } else { // no match and this far along means we can't decrypt $this->webLog("Cannot decrypt ciphertext because no suitable eckey was located", __METHOD__, 'error'); return false; } } } // if eckey is false, we cannot proceed if ($eckey === false) { $this->webLog(); return false; } // set our eckey to the actual encryption key $eckey = \Sodium\hex2bin($eckey); // process AES-256-GCM methods if ($cipher === 'aes256gcm') { //$eckey = ( $eckey === false ) ? \Sodium\randombytes_buf(\Sodium\CRYPTO_AEAD_AES256GCM_KEYBYTES) : \Sodium\hex2bin($eckey); if ($dir === 'encrypt') { // create the ciphertext $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_AEAD_AES256GCM_NPUBBYTES); $resultString = \Sodium\crypto_aead_aes256gcm_encrypt($input->string, $input->ad, $nonce, $eckey); } else { // decrypt the ciphertext $resultString = \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $input->ad, $nonce, $eckey); } // process CHACHA20-POLY1305 methods } else { if ($cipher === 'chacha') { //$eckey = ( $eckey === false ) ? \Sodium\randombytes_buf(\Sodium\CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES) : \Sodium\hex2bin($eckey); if ($dir === 'encrypt') { // create the ciphertext $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES); $resultString = \Sodium\crypto_aead_chacha20poly1305_encrypt($input->string, $input->ad, $nonce, $eckey); } else { // decrypt the ciphertext $resultString = \Sodium\crypto_aead_chacha20poly1305_decrypt($ciphertext, $input->ad, $nonce, $eckey); } } } // finishing up $totalTime = number_format(microtime(true) - $start, $this->config->precison + 10); $this->webLog("Performed '{$dir}' on a string with {$cipher} in {$totalTime} seconds", __METHOD__); $this->crCryptoAnalytics($eckey, $nonce, $dir, $cipher, strlen($input->string), $totalTime); \Sodium\memzero($eckey); // if decrypt, just send the string if ($dir === 'decrypt') { \Sodium\memzero($nonce); if ($resultString === false) { $this->webLog("Decryption failed!", __METHOD__, 'warn'); } return $resultString; } // if encrypt, send back the data in case we generated it $ciphertext = \Sodium\bin2hex($nonce . $resultString); \Sodium\memzero($nonce); return $keyid . '$' . $ciphertext; }
/** * 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'}; }
/** * Return a secrete nonce string as entropy to the client. * * @return string */ static function generateNonce() { return \Sodium\randombytes_buf(Constants::SECRETBOX_NONCEBYTES); }
/** * Generate a random string of the specified size * * @param int $size The size of the requested random string * * @return string A string of the requested size */ public function generate($size) { if (!$this->hasLibsodium || $size < 1) { return str_repeat(chr(0), $size); } return \Sodium\randombytes_buf($size); }