/** * Generate a new key pair. * * @return KeyPair the new key pair */ public final function generateKeyPair() { /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ $kp = \Sodium\crypto_box_keypair(); /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ return new KeyPair(\Sodium\crypto_box_secretkey($kp), \Sodium\crypto_box_publickey($kp)); }
/** * Return an EncryptionSecretKey and EncryptionPublicKey which are related * * @param string $driver * @return Key[] */ public static function generateEncryptionKeyPair($driver = Common::DRIVER_SODIUM) { if ($driver === Common::DRIVER_SODIUM) { $kp = \Sodium\crypto_box_keypair(); return [new EncryptionSecretKey(\Sodium\crypto_box_secretkey($kp, $driver)), new EncryptionPublicKey(\Sodium\crypto_box_publickey($kp, $driver))]; } elseif ($driver === Common::DRIVER_OPENSSL) { $dhres = \openssl_pkey_new(['dh' => ['p' => self::GROUP14PRIME, 'g' => 2], 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_DH]); $details = \openssl_pkey_get_details($dhres); return [new EncryptionSecretKey($details['dh']['priv_key'], $driver), new EncryptionPublicKey($details['dh']['pub_key'], $driver)]; } }
/** * Returns a new set of keys for message encryption and signing. * * @param string $seed The seed to use to create repeatable keys. * @param string $hashKey The key to hash the key with. * @return array */ public static function generateKeys($seed = null, $hashKey = '') { # The keys are being generated from a seed. if ($seed !== null) { # Generate some repeatable hashes to create keys against for recovery $encrHash = Hash::hash($seed, $hashKey, Constants::BOX_SEEDBYTES); $signHash = Hash::hash($seed, $hashKey, Constants::SIGN_SEEDBYTES); # Build recoverable pre-seeded key pairs. $seeds = ['encr' => \Sodium\crypto_box_keypair($encrHash), 'sign' => \Sodium\crypto_sign_keypair($signHash)]; } else { # Build un-recoverable key pairs. $seeds = ['encr' => \Sodium\crypto_box_keypair(), 'sign' => \Sodium\crypto_sign_keypair()]; } # Return the two generated key pairs to the client. return ['encr' => ['pri' => Helpers::bin2hex(\Sodium\crypto_box_secretkey($seeds['encr'])), 'pub' => Helpers::bin2hex(\Sodium\crypto_box_publickey($seeds['encr']))], 'sign' => ['pri' => Helpers::bin2hex(\Sodium\crypto_sign_secretkey($seeds['sign'])), 'pub' => Helpers::bin2hex(\Sodium\crypto_sign_publickey($seeds['sign']))]]; }
/** * @covers Asymmetric::seal() * @covers Asymmetric::unseal() */ public function testSeal() { if (\Sodium\library_version_major() < 7 || \Sodium\library_version_major() == 7 && \Sodium\library_version_minor() < 5) { $this->markTestSkipped("Your version of libsodium is too old"); } $alice = KeyFactory::generateEncryptionKeyPair(); $enc_secret = $alice->getSecretKey(); $enc_public = $alice->getPublicKey(); $this->assertEquals(\Sodium\crypto_box_publickey_from_secretkey($enc_secret->getRawKeyMaterial()), $enc_public->getRawKeyMaterial()); $message = 'This is for your eyes only'; $kp = \Sodium\crypto_box_keypair(); $test = \Sodium\crypto_box_seal($message, \Sodium\crypto_box_publickey($kp)); $decr = \Sodium\crypto_box_seal_open($test, $kp); $this->assertTrue($decr !== false); $sealed = Asymmetric::seal($message, new EncryptionPublicKey(\Sodium\crypto_box_publickey($kp))); $opened = Asymmetric::unseal($sealed, new EncryptionSecretKey(\Sodium\crypto_box_secretkey($kp))); $sealed = Asymmetric::seal($message, $enc_public); $opened = Asymmetric::unseal($sealed, $enc_secret); $this->assertEquals($opened, $message); $sealed_raw = Asymmetric::seal($message, $alice->getPublicKey()); $opened_raw = Asymmetric::unseal($sealed_raw, $alice->getSecretKey()); $this->assertEquals($opened_raw, $message); }
/** * Encrypt a message with a target users' public key * * @param string $source Message to encrypt * @param string $publicKey * @param boolean $raw Don't hex encode the output? * * @return string */ public static function seal($source, Contract\CryptoKeyInterface $publicKey, $raw = false) { if ($publicKey->isPublicKey()) { if (function_exists('\\Sodium\\crypto_box_seal')) { $sealed = \Sodium\crypto_box_seal($source, $publicKey->get()); } else { /** * Polyfill for libsodium < 1.0.3 */ // Generate an ephemeral keypair $eph_kp = \Sodium\crypto_box_keypair(); $eph_secret = \Sodium\crypto_box_secretkey($eph_kp); $eph_public = \Sodium\crypto_box_publickey($eph_kp); $seal_pubkey = $publicKey->get(); $box_kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($eph_secret, $seal_pubkey); // Calculate the nonce $nonce = \Sodium\crypto_generichash($eph_public . $seal_pubkey, null, \Sodium\CRYPTO_BOX_NONCEBYTES); // Seal the message $sealed = $eph_public . \Sodium\crypto_box($source, $nonce, $box_kp); // Don't forget to wipe \Sodium\memzero($seal_pubkey); \Sodium\memzero($eph_kp); \Sodium\memzero($eph_secret); \Sodium\memzero($eph_public); \Sodium\memzero($nonce); \Sodium\memzero($box_kp); } if ($raw) { return $sealed; } return \Sodium\bin2hex($sealed); } throw new CryptoAlert\InvalidKey('Expected a public key'); }
/** * Generate a key * * @param int $type * @param &string $secret_key - Reference to optional variable to store secret key in * @return array|Key */ public static function generate($type = self::CRYPTO_SECRETBOX, &$secret_key = null) { // Set this to true to flag a key as a signing key $signing = false; /** * Are we doing public key cryptography? */ if (($type & self::ASYMMETRIC) !== 0) { /** * Are we doing encryption or digital signing? */ if (($type & self::ENCRYPTION) !== 0) { // Encryption keypair $kp = \Sodium\crypto_box_keypair(); $secret_key = \Sodium\crypto_box_secretkey($kp); $public_key = \Sodium\crypto_box_publickey($kp); } elseif (($type & self::SIGNATURE) !== 0) { // Digital signature keypair $signing = true; $kp = \Sodium\crypto_sign_keypair(); $secret_key = \Sodium\crypto_sign_secretkey($kp); $public_key = \Sodium\crypto_sign_publickey($kp); } else { throw new CryptoException\InvalidFlags('Must specify encryption or authentication'); } // Let's wipe our $kp variable \Sodium\memzero($kp); // Let's return an array with two keys return [new ASecretKey($secret_key, $signing), new APublicKey($public_key, $signing)]; } elseif ($type & self::SECRET_KEY !== 0) { /** * Are we doing encryption or authentication? */ if ($type & self::ENCRYPTION !== 0) { $secret_key = \random_bytes(\Sodium\CRYPTO_SECRETBOX_KEYBYTES); } elseif ($type & self::SIGNATURE !== 0) { $signing = true; // ...let it throw, let it throw! $secret_key = \random_bytes(\Sodium\CRYPTO_AUTH_KEYBYTES); } return new SecretKey($secret_key, $signing); } else { throw new CryptoException\InvalidFlags('Must specify symmetric-key or asymmetric-key'); } }
/** * Derive a key pair for public key encryption from a password and salt * * @param string $password * @param string $salt * @param bool $legacy Use scrypt? * * @return EncryptionKeyPair * @throws CryptoException\InvalidSalt */ public static function deriveEncryptionKeyPair(string $password, string $salt, bool $legacy = false) : EncryptionKeyPair { if ($legacy) { if (CryptoUtil::safeStrlen($salt) !== \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES) { throw new CryptoException\InvalidSalt('Expected ' . \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES . ' bytes, got ' . CryptoUtil::safeStrlen($salt)); } // Diffie Hellman key exchange key pair $seed = \Sodium\crypto_pwhash_scryptsalsa208sha256(\Sodium\CRYPTO_BOX_SEEDBYTES, $password, $salt, \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE, \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE); } else { if (CryptoUtil::safeStrlen($salt) !== \Sodium\CRYPTO_PWHASH_SALTBYTES) { throw new CryptoException\InvalidSalt('Expected ' . \Sodium\CRYPTO_PWHASH_SALTBYTES . ' bytes, got ' . CryptoUtil::safeStrlen($salt)); } // Diffie Hellman key exchange key pair $seed = \Sodium\crypto_pwhash(\Sodium\CRYPTO_BOX_SEEDBYTES, $password, $salt, \Sodium\CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, \Sodium\CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE); } $keypair = \Sodium\crypto_box_seed_keypair($seed); $secret_key = \Sodium\crypto_box_secretkey($keypair); // Let's wipe our $kp variable \Sodium\memzero($keypair); return new EncryptionKeyPair(new EncryptionSecretKey($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'}; }
/** * Derive a key pair for public key encryption from a password and salt * * @param string $secret_key * @return EncryptionKeyPair */ public static function deriveEncryptionKeyPair($password, $salt) { // Digital signature keypair $seed = \Sodium\crypto_pwhash_scryptsalsa208sha256(\Sodium\CRYPTO_SIGN_SEEDBYTES, $password, $salt, \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE, \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE); $keypair = \Sodium\crypto_box_seed_keypair($seed); $secret_key = \Sodium\crypto_box_secretkey($keypair); // Let's wipe our $kp variable \Sodium\memzero($keypair); return new EncryptionKeyPair(new EncryptionSecretKey($secret_key)); }
/** * Derive a key pair for public key encryption from a password and salt * * @param HiddenString $password * @param string $salt * @param string $level Security level for KDF * * @return EncryptionKeyPair * @throws CryptoException\InvalidSalt */ public static function deriveEncryptionKeyPair(HiddenString $password, string $salt, string $level = self::INTERACTIVE) : EncryptionKeyPair { $kdfLimits = self::getSecurityLevels($level); // VERSION 2+ (argon2) if (Util::safeStrlen($salt) !== \Sodium\CRYPTO_PWHASH_SALTBYTES) { throw new CryptoException\InvalidSalt('Expected ' . \Sodium\CRYPTO_PWHASH_SALTBYTES . ' bytes, got ' . Util::safeStrlen($salt)); } // Diffie Hellman key exchange key pair $seed = \Sodium\crypto_pwhash(\Sodium\CRYPTO_BOX_SEEDBYTES, $password->getString(), $salt, $kdfLimits[0], $kdfLimits[1]); $keyPair = \Sodium\crypto_box_seed_keypair($seed); $secretKey = \Sodium\crypto_box_secretkey($keyPair); // Let's wipe our $kp variable \Sodium\memzero($keyPair); return new EncryptionKeyPair(new EncryptionSecretKey(new HiddenString($secretKey))); }