/** * @param string $box * @param string $recipientPrivateKey * @param string $senderPublicKey * @param string $nonce * @return null|string */ protected function openBox($box, $recipientPrivateKey, $senderPublicKey, $nonce) { /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ $kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($recipientPrivateKey, $senderPublicKey); /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ return \Sodium\crypto_box_open($box, $nonce, $kp); }
/** * Decrypt a public key encrypted message. * * @param string $message The message to be encrypted. * @param string $sender_public The senders public key. * @param string $receiver_private The receivers private key. * @return string The JSON string for the encrypted message. * @throws DecryptionException * @throws InvalidTypeException */ public static function decrypt($message, $sender_public, $receiver_private) { # Test to make sure all the required variables are strings. Helpers::isString($message, 'PublicKeyEncryption', 'decrypt'); Helpers::isString($sender_public, 'PublicKeyEncryption', 'decrypt'); Helpers::isString($receiver_private, 'PublicKeyEncryption', 'decrypt'); # Generate a keypair for the message to be received. $messageKeyPair = \Sodium\crypto_box_keypair_from_secretkey_and_publickey(Helpers::hex2bin($receiver_private), Helpers::hex2bin($sender_public)); # Deconstruct the message from JSON. $message = base64_decode(json_decode($message, true)); # Attempt to decrypt the message. $plaintext = \Sodium\crypto_box_open(Helpers::hex2bin($message['msg']), Helpers::hex2bin($message['nonce']), $messageKeyPair); # Test if the message was able to be decrypted. if ($plaintext === false) { throw new DecryptionException('Failed to decrypt message using key'); } return $plaintext; }
/** * 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'); }
/** * 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'}; }