Example #1
0
 /**
  * @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);
 }
Example #2
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 #3
0
 /**
  * Load a key from a file
  * 
  * @param string $filePath
  * @param int $type
  * @return array|\ParagonIE\Halite\Key
  * @throws CryptoException\InvalidFlags
  */
 public static function fromFile($filePath, $type = self::CRYPTO_SECRETBOX)
 {
     // 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?
          */
         $secret_key = \file_get_contents($filePath);
         if (($type & self::ENCRYPTION) !== 0) {
             $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key);
         } elseif (($type & self::SIGNATURE) !== 0) {
             // Digital signature keypair
             $signing = true;
             $public_key = \Sodium\crypto_sign_publickey_from_secretkey($secret_key);
         } else {
             throw new CryptoException\InvalidFlags('Must specify encryption or authentication');
         }
         // 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::SIGNATURE !== 0) {
             $signing = true;
         }
         $secret_key = \file_get_contents($filePath);
         return new SecretKey($secret_key, $signing);
     } else {
         throw new CryptoException\InvalidFlags('Must specify symmetric-key or asymmetric-key');
     }
 }
Example #4
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;
 }
 /**
  * See the appropriate derived class.
  * 
  * @return SignaturePublicKey
  */
 public function derivePublicKey()
 {
     $publicKey = \Sodium\crypto_box_publickey_from_secretkey($this->getRawKeyMaterial());
     return new EncryptionPublicKey($publicKey);
 }
Example #6
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 #7
0
 /**
  * Decrypt a sealed message with our private key
  * 
  * @param string $source Encrypted message (string or resource for a file)
  * @param EncryptionSecretKey $privateKey
  * @param boolean $raw Don't hex decode the input?
  * @return string
  * @throws CryptoException\InvalidKey
  */
 public static function unseal(string $source, EncryptionSecretKey $privateKey, bool $raw = false) : string
 {
     if (!$raw) {
         $source = \Sodium\hex2bin($source);
     }
     // Get a box keypair (needed by crypto_box_seal_open)
     $secret_key = $privateKey->getRawKeyMaterial();
     $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key);
     $kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($secret_key, $public_key);
     // Wipe these immediately:
     \Sodium\memzero($secret_key);
     \Sodium\memzero($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($kp);
     if ($message === false) {
         throw new CryptoException\InvalidKey('Incorrect secret key for this sealed message');
     }
     // We have our encrypted message here
     return $message;
 }
Example #8
0
 /**
  * Key Exchange
  *
  * @param string $kexinit_payload_server
  * @throws \UnexpectedValueException on receipt of unexpected packets
  * @throws \RuntimeException on other errors
  * @throws \phpseclib\Exception\NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible
  * @access private
  */
 function _key_exchange($kexinit_payload_server)
 {
     $kex_algorithms = array('*****@*****.**', 'diffie-hellman-group1-sha1', 'diffie-hellman-group14-sha1', 'diffie-hellman-group-exchange-sha1', 'diffie-hellman-group-exchange-sha256');
     if (!function_exists('\\Sodium\\library_version_major')) {
         $kex_algorithms = array_diff($kex_algorithms, array('*****@*****.**'));
     }
     $server_host_key_algorithms = array('ssh-rsa', 'ssh-dss');
     $encryption_algorithms = array('arcfour256', 'arcfour128', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'twofish128-ctr', 'twofish192-ctr', 'twofish256-ctr', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc', 'twofish128-cbc', 'twofish192-cbc', 'twofish256-cbc', 'twofish-cbc', 'blowfish-ctr', 'blowfish-cbc', '3des-ctr', '3des-cbc');
     if (extension_loaded('openssl') && !extension_loaded('mcrypt')) {
         // OpenSSL does not support arcfour256 in any capacity and arcfour128 / arcfour support is limited to
         // instances that do not use continuous buffers
         $encryption_algorithms = array_diff($encryption_algorithms, array('arcfour256', 'arcfour128', 'arcfour'));
     }
     if (class_exists('\\phpseclib\\Crypt\\RC4') === false) {
         $encryption_algorithms = array_diff($encryption_algorithms, array('arcfour256', 'arcfour128', 'arcfour'));
     }
     if (class_exists('\\phpseclib\\Crypt\\Rijndael') === false) {
         $encryption_algorithms = array_diff($encryption_algorithms, array('aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc'));
     }
     if (class_exists('\\phpseclib\\Crypt\\Twofish') === false) {
         $encryption_algorithms = array_diff($encryption_algorithms, array('twofish128-ctr', 'twofish192-ctr', 'twofish256-ctr', 'twofish128-cbc', 'twofish192-cbc', 'twofish256-cbc', 'twofish-cbc'));
     }
     if (class_exists('\\phpseclib\\Crypt\\Blowfish') === false) {
         $encryption_algorithms = array_diff($encryption_algorithms, array('blowfish-ctr', 'blowfish-cbc'));
     }
     if (class_exists('\\phpseclib\\Crypt\\TripleDES') === false) {
         $encryption_algorithms = array_diff($encryption_algorithms, array('3des-ctr', '3des-cbc'));
     }
     $encryption_algorithms = array_values($encryption_algorithms);
     $mac_algorithms = array('hmac-sha2-256', 'hmac-sha1-96', 'hmac-sha1', 'hmac-md5-96', 'hmac-md5');
     $compression_algorithms = array('none');
     // some SSH servers have buggy implementations of some of the above algorithms
     switch ($this->server_identifier) {
         case 'SSH-2.0-SSHD':
             $mac_algorithms = array_values(array_diff($mac_algorithms, array('hmac-sha1-96', 'hmac-md5-96')));
     }
     $str_kex_algorithms = implode(',', $kex_algorithms);
     $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms);
     $encryption_algorithms_server_to_client = $encryption_algorithms_client_to_server = implode(',', $encryption_algorithms);
     $mac_algorithms_server_to_client = $mac_algorithms_client_to_server = implode(',', $mac_algorithms);
     $compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(',', $compression_algorithms);
     $client_cookie = Random::string(16);
     $response = $kexinit_payload_server;
     $this->_string_shift($response, 1);
     // skip past the message number (it should be SSH_MSG_KEXINIT)
     $server_cookie = $this->_string_shift($response, 16);
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length']));
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length']));
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->encryption_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->encryption_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->mac_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->mac_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->compression_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->compression_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->languages_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->languages_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
     extract(unpack('Cfirst_kex_packet_follows', $this->_string_shift($response, 1)));
     $first_kex_packet_follows = $first_kex_packet_follows != 0;
     // the sending of SSH2_MSG_KEXINIT could go in one of two places.  this is the second place.
     $kexinit_payload_client = pack('Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN', NET_SSH2_MSG_KEXINIT, $client_cookie, strlen($str_kex_algorithms), $str_kex_algorithms, strlen($str_server_host_key_algorithms), $str_server_host_key_algorithms, strlen($encryption_algorithms_client_to_server), $encryption_algorithms_client_to_server, strlen($encryption_algorithms_server_to_client), $encryption_algorithms_server_to_client, strlen($mac_algorithms_client_to_server), $mac_algorithms_client_to_server, strlen($mac_algorithms_server_to_client), $mac_algorithms_server_to_client, strlen($compression_algorithms_client_to_server), $compression_algorithms_client_to_server, strlen($compression_algorithms_server_to_client), $compression_algorithms_server_to_client, 0, '', 0, '', 0, 0);
     if (!$this->_send_binary_packet($kexinit_payload_client)) {
         return false;
     }
     // here ends the second place.
     // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
     // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
     // diffie-hellman key exchange as fast as possible
     $decrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_server_to_client);
     $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt);
     if ($decryptKeyLength === null) {
         $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
         throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found');
     }
     $encrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_client_to_server);
     $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt);
     if ($encryptKeyLength === null) {
         $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
         throw new NoSupportedAlgorithmsException('No compatible client to server encryption algorithms found');
     }
     // through diffie-hellman key exchange a symmetric key is obtained
     $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms);
     if ($kex_algorithm === false) {
         $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
         throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found');
     }
     // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty.
     $exchange_hash_rfc4419 = '';
     if ($kex_algorithm === '*****@*****.**') {
         $x = Random::string(32);
         $eBytes = \Sodium\crypto_box_publickey_from_secretkey($x);
         $clientKexInitMessage = NET_SSH2_MSG_KEX_ECDH_INIT;
         $serverKexReplyMessage = NET_SSH2_MSG_KEX_ECDH_REPLY;
         $kexHash = new Hash('sha256');
     } else {
         if (strpos($kex_algorithm, 'diffie-hellman-group-exchange') === 0) {
             $dh_group_sizes_packed = pack('NNN', $this->kex_dh_group_size_min, $this->kex_dh_group_size_preferred, $this->kex_dh_group_size_max);
             $packet = pack('Ca*', NET_SSH2_MSG_KEXDH_GEX_REQUEST, $dh_group_sizes_packed);
             if (!$this->_send_binary_packet($packet)) {
                 return false;
             }
             $response = $this->_get_binary_packet();
             if ($response === false) {
                 user_error('Connection closed by server');
                 return false;
             }
             extract(unpack('Ctype', $this->_string_shift($response, 1)));
             if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) {
                 user_error('Expected SSH_MSG_KEX_DH_GEX_GROUP');
                 return false;
             }
             extract(unpack('NprimeLength', $this->_string_shift($response, 4)));
             $primeBytes = $this->_string_shift($response, $primeLength);
             $prime = new BigInteger($primeBytes, -256);
             extract(unpack('NgLength', $this->_string_shift($response, 4)));
             $gBytes = $this->_string_shift($response, $gLength);
             $g = new BigInteger($gBytes, -256);
             $exchange_hash_rfc4419 = pack('a*Na*Na*', $dh_group_sizes_packed, $primeLength, $primeBytes, $gLength, $gBytes);
             $clientKexInitMessage = NET_SSH2_MSG_KEXDH_GEX_INIT;
             $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_GEX_REPLY;
         } else {
             switch ($kex_algorithm) {
                 // see http://tools.ietf.org/html/rfc2409#section-6.2 and
                 // http://tools.ietf.org/html/rfc2412, appendex E
                 case 'diffie-hellman-group1-sha1':
                     $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
                     break;
                     // see http://tools.ietf.org/html/rfc3526#section-3
                 // see http://tools.ietf.org/html/rfc3526#section-3
                 case 'diffie-hellman-group14-sha1':
                     $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
                     break;
             }
             // For both diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1
             // the generator field element is 2 (decimal) and the hash function is sha1.
             $g = new BigInteger(2);
             $prime = new BigInteger($prime, 16);
             $clientKexInitMessage = NET_SSH2_MSG_KEXDH_INIT;
             $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_REPLY;
         }
         switch ($kex_algorithm) {
             case 'diffie-hellman-group-exchange-sha256':
                 $kexHash = new Hash('sha256');
                 break;
             default:
                 $kexHash = new Hash('sha1');
         }
         /* To increase the speed of the key exchange, both client and server may
                     reduce the size of their private exponents.  It should be at least
                     twice as long as the key material that is generated from the shared
                     secret.  For more details, see the paper by van Oorschot and Wiener
                     [VAN-OORSCHOT].
         
                     -- http://tools.ietf.org/html/rfc4419#section-6.2 */
         $one = new BigInteger(1);
         $keyLength = min($kexHash->getLength(), max($encryptKeyLength, $decryptKeyLength));
         $max = $one->bitwise_leftShift(16 * $keyLength);
         // 2 * 8 * $keyLength
         $max = $max->subtract($one);
         $x = BigInteger::random($one, $max);
         $e = $g->modPow($x, $prime);
         $eBytes = $e->toBytes(true);
     }
     $data = pack('CNa*', $clientKexInitMessage, strlen($eBytes), $eBytes);
     if (!$this->_send_binary_packet($data)) {
         throw new \RuntimeException('Connection closed by server');
     }
     $response = $this->_get_binary_packet();
     if ($response === false) {
         throw new \RuntimeException('Connection closed by server');
     }
     extract(unpack('Ctype', $this->_string_shift($response, 1)));
     if ($type != $serverKexReplyMessage) {
         throw new \UnexpectedValueException('Expected SSH_MSG_KEXDH_REPLY');
     }
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->server_public_host_key = $server_public_host_key = $this->_string_shift($response, $temp['length']);
     $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
     $public_key_format = $this->_string_shift($server_public_host_key, $temp['length']);
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $fBytes = $this->_string_shift($response, $temp['length']);
     $temp = unpack('Nlength', $this->_string_shift($response, 4));
     $this->signature = $this->_string_shift($response, $temp['length']);
     $temp = unpack('Nlength', $this->_string_shift($this->signature, 4));
     $this->signature_format = $this->_string_shift($this->signature, $temp['length']);
     if ($kex_algorithm === '*****@*****.**') {
         if (strlen($fBytes) !== 32) {
             user_error('Received curve25519 public key of invalid length.');
             return false;
         }
         $key = new BigInteger(\Sodium\crypto_scalarmult($x, $fBytes), 256);
         \Sodium\memzero($x);
     } else {
         $f = new BigInteger($fBytes, -256);
         $key = $f->modPow($x, $prime);
     }
     $keyBytes = $key->toBytes(true);
     $this->exchange_hash = pack('Na*Na*Na*Na*Na*a*Na*Na*Na*', strlen($this->identifier), $this->identifier, strlen($this->server_identifier), $this->server_identifier, strlen($kexinit_payload_client), $kexinit_payload_client, strlen($kexinit_payload_server), $kexinit_payload_server, strlen($this->server_public_host_key), $this->server_public_host_key, $exchange_hash_rfc4419, strlen($eBytes), $eBytes, strlen($fBytes), $fBytes, strlen($keyBytes), $keyBytes);
     $this->exchange_hash = $kexHash->hash($this->exchange_hash);
     if ($this->session_id === false) {
         $this->session_id = $this->exchange_hash;
     }
     $server_host_key_algorithm = $this->_array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
     if ($server_host_key_algorithm === false) {
         $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
         throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found');
     }
     if ($public_key_format != $server_host_key_algorithm || $this->signature_format != $server_host_key_algorithm) {
         $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
         throw new \RuntimeException('Server Host Key Algorithm Mismatch');
     }
     $packet = pack('C', NET_SSH2_MSG_NEWKEYS);
     if (!$this->_send_binary_packet($packet)) {
         return false;
     }
     $response = $this->_get_binary_packet();
     if ($response === false) {
         throw new \RuntimeException('Connection closed by server');
     }
     extract(unpack('Ctype', $this->_string_shift($response, 1)));
     if ($type != NET_SSH2_MSG_NEWKEYS) {
         throw new \UnexpectedValueException('Expected SSH_MSG_NEWKEYS');
     }
     $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
     $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt);
     if ($this->encrypt) {
         if ($this->crypto_engine) {
             $this->encrypt->setEngine($this->crypto_engine);
         }
         if ($this->encrypt->block_size) {
             $this->encrypt_block_size = $this->encrypt->block_size;
         }
         $this->encrypt->enableContinuousBuffer();
         $this->encrypt->disablePadding();
         if ($this->encrypt->usesIV()) {
             $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
             while ($this->encrypt_block_size > strlen($iv)) {
                 $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
             }
             $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
         }
         $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
         while ($encryptKeyLength > strlen($key)) {
             $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
         }
         $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
     }
     $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt);
     if ($this->decrypt) {
         if ($this->crypto_engine) {
             $this->decrypt->setEngine($this->crypto_engine);
         }
         if ($this->decrypt->block_size) {
             $this->decrypt_block_size = $this->decrypt->block_size;
         }
         $this->decrypt->enableContinuousBuffer();
         $this->decrypt->disablePadding();
         if ($this->decrypt->usesIV()) {
             $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
             while ($this->decrypt_block_size > strlen($iv)) {
                 $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
             }
             $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
         }
         $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
         while ($decryptKeyLength > strlen($key)) {
             $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
         }
         $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
     }
     /* The "arcfour128" algorithm is the RC4 cipher, as described in
                [SCHNEIER], using a 128-bit key.  The first 1536 bytes of keystream
                generated by the cipher MUST be discarded, and the first byte of the
                first encrypted packet MUST be encrypted using the 1537th byte of
                keystream.
     
                -- http://tools.ietf.org/html/rfc4345#section-4 */
     if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') {
         $this->encrypt->encrypt(str_repeat("", 1536));
     }
     if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') {
         $this->decrypt->decrypt(str_repeat("", 1536));
     }
     $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_client_to_server);
     if ($mac_algorithm === false) {
         $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
         throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found');
     }
     $createKeyLength = 0;
     // ie. $mac_algorithm == 'none'
     switch ($mac_algorithm) {
         case 'hmac-sha2-256':
             $this->hmac_create = new Hash('sha256');
             $createKeyLength = 32;
             break;
         case 'hmac-sha1':
             $this->hmac_create = new Hash('sha1');
             $createKeyLength = 20;
             break;
         case 'hmac-sha1-96':
             $this->hmac_create = new Hash('sha1-96');
             $createKeyLength = 20;
             break;
         case 'hmac-md5':
             $this->hmac_create = new Hash('md5');
             $createKeyLength = 16;
             break;
         case 'hmac-md5-96':
             $this->hmac_create = new Hash('md5-96');
             $createKeyLength = 16;
     }
     $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_server_to_client);
     if ($mac_algorithm === false) {
         $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
         throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found');
     }
     $checkKeyLength = 0;
     $this->hmac_size = 0;
     switch ($mac_algorithm) {
         case 'hmac-sha2-256':
             $this->hmac_check = new Hash('sha256');
             $checkKeyLength = 32;
             $this->hmac_size = 32;
             break;
         case 'hmac-sha1':
             $this->hmac_check = new Hash('sha1');
             $checkKeyLength = 20;
             $this->hmac_size = 20;
             break;
         case 'hmac-sha1-96':
             $this->hmac_check = new Hash('sha1-96');
             $checkKeyLength = 20;
             $this->hmac_size = 12;
             break;
         case 'hmac-md5':
             $this->hmac_check = new Hash('md5');
             $checkKeyLength = 16;
             $this->hmac_size = 16;
             break;
         case 'hmac-md5-96':
             $this->hmac_check = new Hash('md5-96');
             $checkKeyLength = 16;
             $this->hmac_size = 12;
     }
     $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
     while ($createKeyLength > strlen($key)) {
         $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
     }
     $this->hmac_create->setKey(substr($key, 0, $createKeyLength));
     $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
     while ($checkKeyLength > strlen($key)) {
         $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
     }
     $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
     $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_server_to_client);
     if ($compression_algorithm === false) {
         $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
         throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found');
     }
     $this->decompress = $compression_algorithm == 'zlib';
     $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_client_to_server);
     if ($compression_algorithm === false) {
         $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
         throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found');
     }
     $this->compress = $compression_algorithm == 'zlib';
     return true;
 }
Example #9
0
 /**
  * 
  * Pass it a secret key, it will automatically generate a public key
  * 
  * @param ...Key $keys
  */
 public function __construct(Key ...$keys)
 {
     switch (\count($keys)) {
         /**
          * If we received two keys, it must be an asymmetric secret key and
          * an asymmetric public key, in either order.
          */
         case 2:
             if (!$keys[0]->isAsymmetricKey()) {
                 throw new CryptoException\InvalidKey('Only keys intended for asymmetric cryptography can be used in a KeyPair object');
             } elseif (!$keys[1]->isAsymmetricKey()) {
                 throw new CryptoException\InvalidKey('Only keys intended for asymmetric cryptography can be used in a KeyPair object');
             }
             if ($keys[0]->isPublicKey()) {
                 if ($keys[1]->isPublicKey()) {
                     throw new CryptoException\InvalidKey('Both keys cannot be public keys');
                 }
                 $sign = $keys[1]->isSigningKey();
                 // $keys[0] is public, $keys[1] is secret
                 if ($sign) {
                     $this->secret_key = $keys[1] instanceof SignatureSecretKey ? $keys[1] : new SignatureSecretKey($keys[1]->get());
                     $pub = \Sodium\crypto_sign_publickey_from_secretkey($keys[1]->get());
                     $this->public_key = new SignaturePublicKey($pub, true);
                     \Sodium\memzero($pub);
                 } else {
                     $this->secret_key = $keys[1] instanceof EncryptionSecretKey ? $keys[1] : new EncryptionSecretKey($keys[1]->get());
                     // crypto_box - Curve25519
                     $pub = \Sodium\crypto_box_publickey_from_secretkey($keys[1]->get());
                     $this->public_key = new EncryptionPublicKey($pub, false);
                     \Sodium\memzero($pub);
                 }
             } elseif ($keys[1]->isPublicKey()) {
                 $sign = $keys[0]->isSigningKey();
                 // We can deduce that $keys[0] is a secret key
                 if ($sign) {
                     $this->secret_key = $keys[0] instanceof SignatureSecretKey ? $keys[0] : new SignatureSecretKey($keys[0]->get());
                     // crypto_sign - Ed25519
                     $pub = \Sodium\crypto_sign_publickey_from_secretkey($keys[0]->get());
                     $this->public_key = new SignaturePublicKey($pub);
                     \Sodium\memzero($pub);
                 } else {
                     $this->secret_key = $keys[0] instanceof EncryptionSecretKey ? $keys[0] : new EncryptionSecretKey($keys[0]->get());
                     // crypto_box - Curve25519
                     $pub = \Sodium\crypto_box_publickey_from_secretkey($keys[0]->get());
                     $this->public_key = new EncryptionPublicKey($pub);
                     \Sodium\memzero($pub);
                 }
             } else {
                 throw new CryptoException\InvalidKey('Both keys cannot be secret keys');
             }
             break;
             /**
              * If we only received one key, it must be an asymmetric secret key!
              */
         /**
          * If we only received one key, it must be an asymmetric secret key!
          */
         case 1:
             if (!$keys[0]->isAsymmetricKey()) {
                 throw new CryptoException\InvalidKey('Only keys intended for asymmetric cryptography can be used in a KeyPair object');
             }
             if ($keys[0]->isPublicKey()) {
                 throw new CryptoException\InvalidKey('We cannot generate a valid keypair given only a public key; we can given only a secret key, however.');
             }
             $sign = $keys[0]->isSigningKey();
             // We can deduce that $keys[0] is a secret key
             if ($sign) {
                 $this->secret_key = $keys[0] instanceof SignatureSecretKey ? $keys[0] : new SignatureSecretKey($keys[0]->get());
                 // crypto_sign - Ed25519
                 $pub = \Sodium\crypto_sign_publickey_from_secretkey($keys[0]->get());
                 $this->public_key = new SignaturePublicKey($pub);
                 \Sodium\memzero($pub);
             } else {
                 $this->secret_key = $keys[0] instanceof EncryptionSecretKey ? $keys[0] : new EncryptionSecretKey($keys[0]->get());
                 // crypto_box - Curve25519
                 $pub = \Sodium\crypto_box_publickey_from_secretkey($keys[0]->get());
                 $this->public_key = new EncryptionPublicKey($pub);
                 \Sodium\memzero($pub);
             }
             break;
         default:
             throw new \InvalidArgumentException('Halite\\KeyPair expects 1 or 2 keys');
     }
 }
 /**
  * Derive the public key
  *
  * @param string $privateKey in binary
  * @return string public key as binary
  */
 public final function derivePublicKey($privateKey)
 {
     /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */
     return \Sodium\crypto_box_publickey_from_secretkey($privateKey);
 }
Example #11
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 {
             throw new CryptoException\CannotPerformOperation('crypto_box_seal_open is not available');
         }
         if ($message === false) {
             throw new CryptoException\InvalidKey('Incorrect secret key');
         }
         // We have our encrypted message here
         return $message;
     }
     throw new CryptoException\InvalidKey('Expected a secret key');
 }
Example #12
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 #13
0
 /**
  * Decrypt a sealed message with our private key
  * 
  * @param string $source Encrypted message (string or resource for a file)
  * @param EncryptionSecretKey $privateKey
  * @param boolean $raw Don't hex decode the input?
  * 
  * @return string
  * 
  * @throws CryptoException\InvalidKey
  * @throws CryptoException\CannotPerformOperation
  */
 public static function unseal($source, Contract\KeyInterface $privateKey, $raw = false)
 {
     if (!$privateKey instanceof EncryptionSecretKey) {
         throw new CryptoException\InvalidKey('Argument 2: Expected an instance of EncryptionSecretKey');
     }
     if (!$raw) {
         $source = \Sodium\hex2bin($source);
     }
     if (!function_exists('\\Sodium\\crypto_box_seal_open')) {
         throw new CryptoException\CannotPerformOperation('crypto_box_seal_open is not available, please update/reinstall libsodium');
     }
     // 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);
     // Wipe these immediately:
     \Sodium\memzero($secret_key);
     \Sodium\memzero($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($kp);
     if ($message === false) {
         throw new CryptoException\InvalidKey('Incorrect secret key for this sealed message');
     }
     // We have our encrypted message here
     return $message;
 }
 /**
  * 
  * Pass it a secret key, it will automatically generate a public key
  * 
  * @param ...Key $keys
  */
 public function __construct(Key ...$keys)
 {
     switch (\count($keys)) {
         /**
          * If we received two keys, it must be an asymmetric secret key and
          * an asymmetric public key, in either order.
          */
         case 2:
             if (!$keys[0]->isAsymmetricKey() || !$keys[1]->isAsymmetricKey()) {
                 throw new CryptoException\InvalidKey('Only keys intended for asymmetric cryptography can be used in a KeyPair object');
             }
             if ($keys[0]->isPublicKey()) {
                 if ($keys[1]->isPublicKey()) {
                     throw new CryptoException\InvalidKey('Both keys cannot be public keys');
                 }
                 // $keys[0] is public, $keys[1] is secret
                 $this->secret_key = $keys[1] instanceof EncryptionSecretKey ? $keys[1] : new EncryptionSecretKey($keys[1]->get());
                 /**
                  * Let's use the secret key to calculate the *correct* 
                  * public key. We're effectively discarding $keys[0] but
                  * this ensures correct usage down the line.
                  */
                 if (!$this->secret_key->isEncryptionKey()) {
                     throw new CryptoException\InvalidKey('Must be an encryption key pair');
                 }
                 // crypto_box - Curve25519
                 $pub = \Sodium\crypto_box_publickey_from_secretkey($keys[1]->get());
                 $this->public_key = new EncryptionPublicKey($pub, true);
                 \Sodium\memzero($pub);
             } elseif ($keys[1]->isPublicKey()) {
                 // We can deduce that $keys[0] is a secret key
                 $this->secret_key = $keys[0] instanceof EncryptionSecretKey ? $keys[0] : new EncryptionSecretKey($keys[0]->get());
                 /**
                  * Let's use the secret key to calculate the *correct* 
                  * public key. We're effectively discarding $keys[0] but
                  * this ensures correct usage down the line.
                  */
                 if (!$this->secret_key->isEncryptionKey()) {
                     throw new CryptoException\InvalidKey('Must be an encryption key pair');
                 }
                 // crypto_box - Curve25519
                 $pub = \Sodium\crypto_box_publickey_from_secretkey($keys[0]->get());
                 $this->public_key = new EncryptionPublicKey($pub, true);
                 \Sodium\memzero($pub);
             } else {
                 throw new CryptoException\InvalidKey('Both keys cannot be secret keys');
             }
             break;
             /**
              * If we only received one key, it must be an asymmetric secret key!
              */
         /**
          * If we only received one key, it must be an asymmetric secret key!
          */
         case 1:
             if (!$keys[0]->isAsymmetricKey()) {
                 throw new CryptoException\InvalidKey('Only keys intended for asymmetric cryptography can be used in a KeyPair object');
             }
             if ($keys[0]->isPublicKey()) {
                 throw new CryptoException\InvalidKey('We cannot generate a valid keypair given only a public key; we can given only a secret key, however.');
             }
             $this->secret_key = $keys[0] instanceof EncryptionSecretKey ? $keys[0] : new EncryptionSecretKey($keys[0]->get(), $keys[0]->isEncryptionKey());
             if (!$this->secret_key->isEncryptionKey()) {
                 throw new CryptoException\InvalidKey('Must be a signing key pair');
             }
             // We need to calculate the public key from the secret key
             $pub = \Sodium\crypto_box_publickey_from_secretkey($keys[0]->get());
             $this->public_key = new EncryptionPublicKey($pub, true);
             \Sodium\memzero($pub);
             break;
         default:
             throw new \InvalidArgumentException('Halite\\EncryptionKeyPair expects 1 or 2 keys');
     }
 }
Example #15
0
 /**
  * 
  * Pass it a secret key, it will automatically generate a public key
  * 
  * @param ...Key $keys
  */
 public function __construct(Key ...$keys)
 {
     switch (\count($keys)) {
         case 2:
             if ($keys[0]->isPublicKey()) {
                 if ($keys[1]->isPublicKey()) {
                     throw new CryptoAlert\InvalidKey('Both keys cannot be public keys');
                 }
                 // $keys[0] is public, $keys[1] is secret
                 $this->secret_key = $keys[1] instanceof SecretKey ? $keys[1] : new SecretKey($keys[1]->get(), $keys[1]->isSigningKey());
                 /**
                  * Let's use the secret key to calculate the *correct* 
                  * public key. We're effectively discarding $keys[0] but
                  * this ensures correct usage down the line.
                  */
                 if ($this->secret_key->isSigningKey()) {
                     // crypto_sign - Ed25519
                     $pub = \Sodium\crypto_sign_publickey_from_secretkey($keys[1]->get());
                     $this->public_key = new PublicKey($pub, true);
                     \Sodium\memzero($pub);
                 } else {
                     // crypto_box - Curve25519
                     $pub = \Sodium\crypto_box_publickey_from_secretkey($keys[1]->get());
                     $this->public_key = new PublicKey($pub, false);
                     \Sodium\memzero($pub);
                 }
             } elseif ($keys[1]->isPublicKey()) {
                 // We can deduce that $keys[0] is a secret key
                 $this->secret_key = $keys[0] instanceof SecretKey ? $keys[0] : new SecretKey($keys[0]->get(), $keys[0]->isSigningKey());
                 /**
                  * Let's use the secret key to calculate the *correct* 
                  * public key. We're effectively discarding $keys[0] but
                  * this ensures correct usage down the line.
                  */
                 if ($this->secret_key->isSigningKey()) {
                     // crypto_sign - Ed25519
                     $pub = \Sodium\crypto_sign_publickey_from_secretkey($keys[0]->get());
                     $this->public_key = new PublicKey($pub, true);
                 } else {
                     // crypto_box - Curve25519
                     $pub = \Sodium\crypto_box_publickey_from_secretkey($keys[0]->get());
                     $this->public_key = new PublicKey($pub, false);
                     \Sodium\memzero($pub);
                 }
             } else {
                 throw new CryptoAlert\InvalidKey('Both keys cannot be secret keys');
             }
             break;
         case 1:
             if ($keys[0]->isPublicKey()) {
                 throw new CryptoAlert\InvalidKey('We cannot generate a valid keypair given only a public key; we can given only a secret key, however.');
             }
             $this->secret_key = $keys[0] instanceof SecretKey ? $keys[0] : new SecretKey($keys[0]->get(), $keys[0]->isSigningKey());
             if ($this->secret_key->isSigningKey()) {
                 // We need to calculate the public key from the secret key
                 $pub = \Sodium\crypto_sign_publickey_from_secretkey($keys[0]->get());
                 $this->public_key = new PublicKey($pub, true);
                 \Sodium\memzero($pub);
             } else {
                 // We need to calculate the public key from the secret key
                 $pub = \Sodium\crypto_box_publickey_from_secretkey($keys[0]->get());
                 $this->public_key = new PublicKey($pub, false);
                 \Sodium\memzero($pub);
             }
             break;
         default:
             throw new \InvalidArgumentException('Halite\\Keypair expects 1 or 2 keys');
     }
 }
Example #16
0
 /**
  * Decrypt a sealed message with our private key
  * 
  * @param string $ciphertext Encrypted message
  * @param EncryptionSecretKey $privateKey
  * @param mixed $encoding Which encoding scheme to use?
  * @return HiddenString
  * @throws InvalidKey
  * @throws InvalidMessage
  */
 public static function unseal(string $ciphertext, EncryptionSecretKey $privateKey, $encoding = Halite::ENCODE_BASE64URLSAFE) : HiddenString
 {
     $decoder = Halite::chooseEncoder($encoding, true);
     if ($decoder) {
         // We were given hex data:
         try {
             $ciphertext = $decoder($ciphertext);
         } catch (\RangeException $ex) {
             throw new InvalidMessage('Invalid character encoding');
         }
     }
     // Get a box keypair (needed by crypto_box_seal_open)
     $secret_key = $privateKey->getRawKeyMaterial();
     $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key);
     $key_pair = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($secret_key, $public_key);
     // Wipe these immediately:
     \Sodium\memzero($secret_key);
     \Sodium\memzero($public_key);
     // Now let's open that sealed box
     $message = \Sodium\crypto_box_seal_open($ciphertext, $key_pair);
     // Always memzero after retrieving a value
     \Sodium\memzero($key_pair);
     if ($message === false) {
         throw new InvalidKey('Incorrect secret key for this sealed message');
     }
     // We have our encrypted message here
     return new HiddenString($message);
 }