Example #1
1
 /**
  * Make sure you wipe the key from memory on destruction
  */
 public function __destruct()
 {
     if (!$this->isPublicKey) {
         \Sodium\memzero($this->keyMaterial);
         $this->keyMaterial = null;
     }
 }
Example #2
0
function safeDecrypt($encrypted, $key)
{
    $decoded = base64_decode($encrypted);
    $nonce = mb_substr($decoded, 0, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
    $ciphertext = mb_substr($decoded, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
    $plain = \Sodium\crypto_secretbox_open($ciphertext, $nonce, $key);
    \Sodium\memzero($ciphertext);
    \Sodium\memzero($key);
    return $plain;
}
Example #3
0
 /**
  * Check the given plain value against a hash.
  *
  * @param  string  $value
  * @param  string  $hashedValue
  * @param  array   $options
  * @return bool
  */
 public function check($value, $hashedValue, array $options = [])
 {
     if (\Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify($hashedValue, $value)) {
         \Sodium\memzero($value);
         return true;
     } else {
         \Sodium\memzero($value);
         return false;
     }
 }
Example #4
0
 /**
  * Execute the login command
  *
  * @param array $args - CLI arguments
  * @echo
  * @return null
  */
 public function fire(array $args = [])
 {
     $username = \count($args) > 0 ? $args[0] : $this->prompt("Please enter your username: "******"Password:"******"\n";
         exit(255);
     }
     // Let's store the result in our local config
     if (!\array_key_exists('suppliers', $this->config)) {
         $this->config['suppliers'] = [];
     }
     if (\array_key_exists($username, $this->config['suppliers'])) {
         $this->config['suppliers'][$username]['token'] = $result['token'];
         foreach ($result['signing_keys'] as $res_key) {
             $found = false;
             foreach ($this->config['suppliers'][$username]['signing_keys'] as $key) {
                 if ($key['public_key'] === $res_key['public_key']) {
                     $found = true;
                     break;
                 }
             }
             // If we loaded our salt into the skyport, import it:
             if (!$found && isset($res_key['salt'])) {
                 $this->config['suppliers'][$username]['signing_keys'][] = $res_key;
             }
         }
         echo 'Authentication successful', "\n";
     } else {
         $this->config['suppliers'][$username] = $result;
     }
 }
Example #5
0
 /**
  * Stream decryption - Do not call directly
  *
  * @param ReadOnlyFile $input
  * @param MutableFile $output
  * @param EncryptionKey $encKey
  * @param string $nonce
  * @param string $mac (hash context for BLAKE2b)
  * @param Config $config
  * @param array &$chunk_macs
  * @return bool
  * @throws FileAccessDenied
  * @throws CannotPerformOperation
  * @throws FileModified
  * @throws InvalidKey
  * @throws InvalidMessage
  */
 private static final function streamDecrypt(ReadOnlyFile $input, MutableFile $output, EncryptionKey $encKey, string $nonce, string $mac, Config $config, array &$chunk_macs) : bool
 {
     $start = $input->getPos();
     $cipher_end = $input->getSize() - $config->MAC_SIZE;
     // Begin the streaming decryption
     $input->reset($start);
     while ($input->remainingBytes() > $config->MAC_SIZE) {
         /**
          * Would a full BUFFER read put it past the end of the
          * ciphertext? If so, only return a portion of the file.
          */
         if ($input->getPos() + $config->BUFFER > $cipher_end) {
             $read = $input->readBytes($cipher_end - $input->getPos());
         } else {
             $read = $input->readBytes($config->BUFFER);
         }
         // Version 2+ uses a keyed BLAKE2b hash instead of HMAC
         \Sodium\crypto_generichash_update($mac, $read);
         $calcMAC = Util::safeStrcpy($mac);
         $calc = \Sodium\crypto_generichash_final($calcMAC, $config->MAC_SIZE);
         if (empty($chunk_macs)) {
             // Someone attempted to add a chunk at the end.
             throw new InvalidMessage('Invalid message authentication code');
         } else {
             $chunkMAC = \array_shift($chunk_macs);
             if (!\hash_equals($chunkMAC, $calc)) {
                 // This chunk was altered after the original MAC was verified
                 throw new InvalidMessage('Invalid message authentication code');
             }
         }
         // This is where the decryption actually occurs:
         $decrypted = \Sodium\crypto_stream_xor($read, $nonce, $encKey->getRawKeyMaterial());
         $output->writeBytes($decrypted);
         \Sodium\increment($nonce);
     }
     \Sodium\memzero($nonce);
     return true;
 }
 /**
  * 
  * 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 SignatureSecretKey ? $keys[1] : new SignatureSecretKey($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->isSigningKey()) {
                     throw new CryptoException\InvalidKey('Must be a signing key pair');
                 }
                 // crypto_sign - Ed25519
                 $pub = \Sodium\crypto_sign_publickey_from_secretkey($keys[1]->get());
                 $this->public_key = new SignaturePublicKey($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 SignatureSecretKey ? $keys[0] : new SignatureSecretKey($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->isSigningKey()) {
                     throw new CryptoException\InvalidKey('Must be a signing key pair');
                 }
                 // crypto_sign - Ed25519
                 $pub = \Sodium\crypto_sign_publickey_from_secretkey($keys[0]->get());
                 $this->public_key = new SignaturePublicKey($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 SignatureSecretKey ? $keys[0] : new SignatureSecretKey($keys[0]->get(), $keys[0]->isSigningKey());
             if (!$this->secret_key->isSigningKey()) {
                 throw new CryptoException\InvalidKey('Must be a signing key pair');
             }
             // 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 SignaturePublicKey($pub, true);
             \Sodium\memzero($pub);
             break;
         default:
             throw new \InvalidArgumentException('Halite\\EncryptionKeyPair expects 1 or 2 keys');
     }
 }
Example #7
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 #8
0
 /**
  * We are revoking a key.
  *
  * @param array $args
  * @throws \Exception
  * @return mixed
  */
 protected function handleKeyRevoke(array $args)
 {
     if (count($this->config['suppliers']) === 1) {
         $supplier = \count($args) > 0 ? $args[0] : \array_keys($this->config['suppliers'])[0];
     } else {
         $supplier = \count($args) > 0 ? $args[0] : $this->prompt("Please enter the name of the supplier: ");
     }
     if (!\array_key_exists($supplier, $this->config['suppliers'])) {
         echo 'Please authenticate before attempting to revoke keys.', "\n";
         echo 'Run this command: ', $this->c['yellow'], 'barge login', $this->c[''], "\n";
         exit(255);
     }
     $masterKeys = [];
     $keyList = [];
     foreach ($this->config['suppliers'][$supplier]['signing_keys'] as $key) {
         if ($key['type'] === 'master') {
             if (!empty($key['salt'])) {
                 $masterKeys[] = $key;
             } else {
                 $keyList[] = $key;
             }
         } else {
             $keyList[] = $key;
         }
     }
     if (empty($masterKeys)) {
         echo 'No usable master keys found. Make sure the salt is loaded locally.', "\n";
         exit(255);
     }
     if (empty($keyList)) {
         // If and only if you have nothing more to revoke, allow revoking the master key:
         $keyList = $masterKeys;
     }
     if (\count($masterKeys) === 1) {
         $masterKey = $masterKeys[0];
     } else {
         $masterKey = $this->selectKeyFromList('Select your master key: ', $masterKeys);
         // Add other master keys to the list
         foreach ($masterKeys as $key) {
             if ($key['public_key'] !== $masterKey['public_key']) {
                 $keyList[] = $key;
             }
         }
     }
     if (\count($keyList) === 1) {
         $revokingKey = $keyList[0];
     } else {
         $revokingKey = $this->selectKeyFromList('Select which key to revoke: ', $keyList);
     }
     $confirm_revoke = null;
     while ($confirm_revoke === null) {
         $choice = $this->prompt('Are you sure you wish to revoke this key? (y/N): ');
         switch ($choice) {
             case 'YES':
             case 'yes':
             case 'Y':
             case 'y':
                 $confirm_revoke = true;
                 break;
             case 'N':
             case 'NO':
             case 'n':
             case 'no':
             case '':
                 // Just pressing enter means "don't store it"!
                 $confirm_revoke = false;
                 break;
             default:
                 echo "\n", $this->c['yellow'], 'Invalid response. Please enter yes or no.', $this->c[''], "\n";
         }
     }
     // This is what get signed by our master key:
     $message = ['action' => 'REVOKE', 'date_revoked' => \date('Y-m-d\\TH:i:s'), 'public_key' => $revokingKey['public_key'], 'supplier' => $supplier];
     $messageToSign = \json_encode($message);
     $iter = false;
     do {
         if ($iter) {
             echo 'Incorrect password.', "\n";
         }
         $password = $this->silentPrompt('Enter the password for your master key: ');
         if (empty($password)) {
             // Okay, let's cancel.
             throw new \Exception('Aborted.');
         }
         $masterKeyPair = KeyFactory::deriveSignatureKeyPair($password, \Sodium\hex2bin($masterKey['salt']), false, KeyFactory::SENSITIVE);
         \Sodium\memzero($password);
         $masterPublicKeyString = \Sodium\bin2hex($masterKeyPair->getPublicKey()->getRawKeyMaterial());
         $iter = true;
     } while (!\hash_equals($masterKey['public_key'], $masterPublicKeyString));
     $signature = Asymmetric::sign($messageToSign, $masterKeyPair->getSecretKey());
     $response = $this->sendRevocation($supplier, $message, $signature, $masterPublicKeyString);
     if ($response['status'] === 'OK') {
         foreach ($this->config['suppliers'][$supplier]['signing_keys'] as $i => $key) {
             if ($key['public_key'] === $message['public_key']) {
                 unset($this->config['suppliers'][$supplier]['signing_keys'][$i]);
             }
         }
     }
     return $response;
 }
Example #9
0
 /**
  * Login using credentials.
  *
  * @param array $credentials.
  *
  * @return bool
  */
 public function login($credentials)
 {
     $currentUserID = $this->findIDByUsername($credentials['username']);
     if ($currentUserID) {
         $key_user = $this->usersprefix . $currentUserID;
         //$this->redis->hget("userlist", $username);
         $hash_str = $this->redis->hget($key_user, 'password');
         if (\Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify($hash_str, $credentials['password'])) {
             \Sodium\memzero($credentials['password']);
             $this->addFeedback("LOGGED IN.");
             $_SESSION['user'] = ['id' => $currentUserID, 'username' => $credentials['username']];
             $this->sessionTimeoutRestart();
             return true;
         } else {
             \Sodium\memzero($credentials['password']);
             $this->addFeedback("FAILED LOG IN for " . $key_user);
             return false;
         }
     } else {
         // Run a fake to take time.
         $hash_str = $this->redis->hget("userID:0", 'password');
         \Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify($hash_str, $password);
         // session_unset();
         $this->addFeedback("FAILED LOG IN.");
         return false;
     }
 }
 /**
  * Unsets/removes a variable.
  *
  * Important: When using PHPv7, make sure to have at least version 1.0.1 of
  * the Libsodium PECL (libsodium-php) installed. Otherwise this falls back to
  * the (insecure) PHP method of removing a variable.
  *
  * @link https://paragonie.com/book/pecl-libsodium/read/03-utilities-helpers.md#memzero
  * @param  string $var A variable, passed by reference
  */
 public function removeVar(&$var)
 {
     // check if version is compatible
     if (version_compare(PHP_VERSION, '7.0', '>=') && version_compare(phpversion('libsodium'), '1.0.1', '<')) {
         // if not, fall back to PHP implementation
         return parent::removeVar($var);
     }
     /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */
     return \Sodium\memzero($var);
 }
Example #11
0
 /**
  * Stream decryption - Do not call directly
  *
  * @param ReadOnlyFile $input
  * @param MutableFile $output
  * @param Key $encKey
  * @param string $nonce
  * @param resource $mac (hash context)
  * @param Config $config
  * @return bool
  * @throws CryptoException\AccessDenied
  * @throws CryptoException\CannotPerformOperation
  * @throws CryptoException\FileModified
  * @throws CryptoException\InvalidKey
  * @throws CryptoException\InvalidMessage
  */
 private static final function streamDecrypt(ReadOnlyFile $input, MutableFile $output, EncryptionKey $encKey, string $nonce, $mac, Config $config, array &$chunk_macs) : bool
 {
     $start = $input->getPos();
     $cipher_end = $input->getSize() - $config->MAC_SIZE;
     // Begin the streaming decryption
     $input->reset($start);
     while ($input->remainingBytes() > $config->MAC_SIZE) {
         /**
          * Would a full BUFFER read put it past the end of the
          * ciphertext? If so, only return a portion of the file.
          */
         if ($input->getPos() + $config->BUFFER > $cipher_end) {
             $read = $input->readBytes($cipher_end - $input->getPos());
         } else {
             $read = $input->readBytes($config->BUFFER);
         }
         if ($config->USE_BLAKE2B) {
             \Sodium\crypto_generichash_update($mac, $read);
             $calcMAC = '' . $mac;
             $calc = \Sodium\crypto_generichash_final($calcMAC, $config->MAC_SIZE);
         } else {
             \hash_update($mac, $read);
             $calcMAC = \hash_copy($mac);
             if ($calcMAC === false) {
                 throw new CryptoException\CannotPerformOperation('An unknown error has occurred');
             }
             $calc = \hash_final($calcMAC, true);
         }
         if (empty($chunk_macs)) {
             throw new CryptoException\InvalidMessage('Invalid message authentication code');
         } else {
             $chkmac = \array_shift($chunk_macs);
             if (!\hash_equals($chkmac, $calc)) {
                 throw new CryptoException\InvalidMessage('Invalid message authentication code');
             }
         }
         $decrypted = \Sodium\crypto_stream_xor($read, $nonce, $encKey->getRawKeyMaterial());
         $output->writeBytes($decrypted);
         \Sodium\increment($nonce);
     }
     \Sodium\memzero($nonce);
     return true;
 }
Example #12
0
 /**
  * Stream decryption - Do not call directly
  * 
  * @param ReadOnlyFile $input
  * @param MutableFile $output
  * @param Key $encKey
  * @param string $nonce
  * @param resource $mac (hash context)
  * @param Config $config
  * @throws FileAlert\AccessDenied
  */
 private static final function streamDecrypt(ReadOnlyFile $input, MutableFile $output, KeyInterface $encKey, $nonce, $mac, Config $config, array &$chunk_macs)
 {
     if (!$encKey instanceof EncryptionKey) {
         throw new \ParagonIE\Halite\Alerts\InvalidKey('Argument 3: Expected an instance of EncryptionKey');
     }
     $start = $input->getPos();
     $cipher_end = $input->getSize() - $config->MAC_SIZE;
     // Begin the streaming decryption
     $input->reset($start);
     while ($input->remainingBytes() > $config->MAC_SIZE) {
         if ($input->getPos() + $config->BUFFER > $cipher_end) {
             $read = $input->readBytes($cipher_end - $input->getPos());
         } else {
             $read = $input->readBytes($config->BUFFER);
         }
         \hash_update($mac, $read);
         $calcMAC = \hash_copy($mac);
         if ($calcMAC === false) {
             throw new CryptoException\CannotPerformOperation('An unknown error has occurred');
         }
         $calc = \hash_final($calcMAC, true);
         if (empty($chunk_macs)) {
             throw new CryptoException\InvalidMessage('Invalid message authentication code');
         } else {
             $chkmac = \array_shift($chunk_macs);
             if (!\hash_equals($chkmac, $calc)) {
                 throw new CryptoException\InvalidMessage('Invalid message authentication code');
             }
         }
         $decrypted = \Sodium\crypto_stream_xor($read, $nonce, $encKey->get());
         $output->writeBytes($decrypted);
         \Sodium\increment($nonce);
     }
     \Sodium\memzero($nonce);
     return true;
 }
Example #13
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 #14
0
 /**
  * Take a stored key string, get the derived key (after verifying the
  * checksum)
  * 
  * @param string $data
  * @return string
  * @throws Alerts\InvalidKey
  */
 public static function getKeyDataFromString(string $data) : string
 {
     $vtag = Util::safeSubstr($data, 0, Halite::VERSION_TAG_LEN);
     $kdat = Util::safeSubstr($data, Halite::VERSION_TAG_LEN, -\Sodium\CRYPTO_GENERICHASH_BYTES_MAX);
     $csum = Util::safeSubstr($data, -\Sodium\CRYPTO_GENERICHASH_BYTES_MAX, \Sodium\CRYPTO_GENERICHASH_BYTES_MAX);
     $calc = \Sodium\crypto_generichash($vtag . $kdat, '', \Sodium\CRYPTO_GENERICHASH_BYTES_MAX);
     if (!\hash_equals($calc, $csum)) {
         throw new Alerts\InvalidKey('Checksum validation fail');
     }
     \Sodium\memzero($data);
     \Sodium\memzero($vtag);
     \Sodium\memzero($calc);
     \Sodium\memzero($csum);
     return $kdat;
 }
Example #15
0
 /**
  * Verify a Message Authentication Code (MAC) of a message, with a shared
  * key.
  * 
  * @param string $mac             Message Authentication Code
  * @param string $message         The message to verify
  * @param string $authKey         Authentication key (symmetric)
  * @param SymmetricConfig $config Configuration object
  * @return bool
  * @throws InvalidMessage
  * @throws InvalidSignature
  */
 protected static function verifyMAC(string $mac, string $message, string $authKey, SymmetricConfig $config) : bool
 {
     if (CryptoUtil::safeStrlen($mac) !== $config->MAC_SIZE) {
         throw new InvalidSignature('Argument 1: Message Authentication Code is not the correct length; is it encoded?');
     }
     if ($config->MAC_ALGO === 'BLAKE2b') {
         $calc = \Sodium\crypto_generichash($message, $authKey, $config->MAC_SIZE);
         $res = \hash_equals($mac, $calc);
         \Sodium\memzero($calc);
         return $res;
     }
     throw new InvalidMessage('Invalid Halite version');
 }
Example #16
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;
 }
Example #17
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 #18
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');
     }
 }
Example #19
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 #20
0
 /**
  * Wipe it from memory after it's been used.
  */
 public function __destruct()
 {
     \Sodium\memzero($this->internalStringValue);
 }
Example #21
0
 /**
  * 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;
 }
Example #22
0
 /**
  * Execute the keygen command
  *
  * @param array $args - CLI arguments
  * @echo
  * @return null
  * @throws \Error
  */
 public function fire(array $args = [])
 {
     if (count($this->config['suppliers']) === 1) {
         $supplier = \count($args) > 0 ? $args[0] : \array_keys($this->config['suppliers'])[0];
     } else {
         $supplier = \count($args) > 0 ? $args[0] : $this->prompt("Please enter the name of the supplier: ");
     }
     if (!\array_key_exists($supplier, $this->config['suppliers'])) {
         echo 'Please authenticate before attempting to generate a key.', "\n";
         echo 'Run this command: ', $this->c['yellow'], 'barge login', $this->c[''], "\n";
         exit(255);
     }
     if (\count($this->config['suppliers'][$supplier]['signing_keys']) === 0) {
         // Your first key is a master key; always.
         $has_master = false;
         $key_type = 'master';
     } else {
         $has_master = true;
         echo 'Please enter the key type you would like to generate (master, signing).', "\n";
         do {
             $key_type = $this->prompt('Key type: ');
             switch ($key_type) {
                 case 'm':
                 case 'main':
                 case 'master':
                 case 'primary':
                     $key_type = 'master';
                     break;
                 case 's':
                 case 'secondary':
                 case 'sub':
                 case 'subkey':
                 case 'signing':
                     $key_type = 'signing';
                     break;
                 default:
                     echo 'Acceptable key types: master, signing', "\n";
                     $key_type = null;
             }
         } while (empty($key_type));
     }
     // Each key gets its own unique Argon2 salt
     echo 'Generating a unique salt...', "\n";
     $salt = \random_bytes(\Sodium\CRYPTO_PWHASH_SALTBYTES);
     $store_in_cloud = null;
     // This is optional and not recommended, but some people prefer convenience.
     // We really hope this is adequate information to make an informed choice
     // based on personal risk tolerance:
     echo 'Do you wish to store the salt for generating your signing key in the Skyport?', "\n";
     echo 'This is a security-convenience trade-off. The default is NO.', "\n\n";
     echo $this->c['green'], 'Pro:', $this->c[''], ' It\'s there if you need it, and the salt alone is not enough for us to', "\n", '     reproduce your signing key.', "\n";
     echo $this->c['red'], 'Con:', $this->c[''], ' If your salt is stored online, the security of your signing key depends', "\n", '     entirely on your password.', "\n\n";
     // Iterate until we get a valid response
     while ($store_in_cloud === null) {
         $choice = $this->prompt('Store salt in the Skyport? (y/N): ');
         switch ($choice) {
             case 'YES':
             case 'yes':
             case 'Y':
             case 'y':
                 $store_in_cloud = true;
                 break;
             case 'N':
             case 'NO':
             case 'n':
             case 'no':
             case '':
                 // Just pressing enter means "don't store it"!
                 $store_in_cloud = false;
                 break;
             default:
                 echo "\n", $this->c['yellow'], 'Invalid response. Please enter yes or no.', $this->c[''], "\n";
         }
     }
     $zxcvbn = new Zxcvbn();
     $userInput = $this->getZxcvbnKeywords($supplier);
     // If we're storing in the cloud, our standards should be much higher.
     $min_score = $store_in_cloud ? 3 : 2;
     do {
         // Next, let's get a password.
         echo 'Please enter a strong passphrase to use for your signing key.', "\n";
         $password = $this->silentPrompt("Passphrase:");
         $password2 = $this->silentPrompt("Confirm passphrase:");
         if (!\hash_equals($password, $password2)) {
             unset($password);
             echo $this->c['red'], 'Passwords did not match!', $this->c[''], "\n";
             continue;
         }
         // Use zxcvbn to assess password strength
         $strength = $zxcvbn->passwordStrength($password, $userInput);
         if ($strength['score'] < $min_score) {
             echo $this->c['yellow'], 'Sorry, that password is not strong enough. Try making ', 'your password longer and use a wider variety of characters.', $this->c[''], "\n";
             $password = false;
         }
     } while (empty($password));
     echo 'Generating signing key...';
     if ($key_type === 'master') {
         // Master keys are treated as sensitive.
         $sign_level = KeyFactory::SENSITIVE;
     } else {
         // Signing keys (day-to-day) are still moderately sensitive.
         // We're using a KDF locally so we don't have DDoS concerns
         // (which usually calls for INTERACTIVE).
         $sign_level = KeyFactory::MODERATE;
     }
     $keyPair = KeyFactory::deriveSignatureKeyPair($password, $salt, false, $sign_level);
     $sign_public = $keyPair->getPublicKey();
     echo 'DONE!', "\n";
     // Wipe the password from memory
     \Sodium\memzero($password);
     // Store this in the configuration
     $new_key = ['date_generated' => \date('Y-m-d\\TH:i:s'), 'store_in_cloud' => $store_in_cloud, 'salt' => \Sodium\bin2hex($salt), 'public_key' => \Sodium\bin2hex($sign_public->getRawKeyMaterial()), 'type' => $key_type];
     // This is the message we are signing.
     $message = \json_encode(['action' => 'CREATE', 'date_generated' => $new_key['date_generated'], 'public_key' => $new_key['public_key'], 'supplier' => $supplier, 'type' => $new_key['type']]);
     if ($has_master) {
         list($masterSig, $masterPubKey) = $this->signNewKeyWithMasterKey($supplier, $message);
     } else {
         // This is our first key, so we don't need it.
         $masterSig = '';
         $masterPubKey = '';
     }
     // Save the configuration
     $this->config['suppliers'][$supplier]['signing_keys'][] = $new_key;
     // Send the public kay (and, maybe, the salt) to the Skyport.
     $response = $this->sendToSkyport($supplier, $new_key, $message, $masterSig, $masterPubKey);
     if (!empty($response['status'])) {
         if ($response['status'] === 'ERROR') {
             echo "Error message returned!\n";
             throw new \Error($response['message']);
         }
         $pk = Base64UrlSafe::encode(\Sodium\hex2bin($new_key['public_key']));
         if ($new_key['type'] === 'master') {
             echo 'New master key: ', $this->c['red'], $pk, $this->c[''], "\n";
         } else {
             echo 'New signing key: ', $this->c['yellow'], $pk, $this->c[''], "\n";
         }
     }
 }
Example #23
0
 /**
  * Stream encryption - Do not call directly
  * 
  * @param resource $input
  * @param resource $output
  * @param Key $encKey
  * @param string $nonce
  * @param resource $mac (hash context)
  * @param array $config
  * @throws FileAlert\AccessDenied
  */
 private static final function streamEncrypt($input, $output, \ParagonIE\Halite\Contract\CryptoKeyInterface $encKey, $nonce, $mac, array $config)
 {
     // Begin the streaming decryption
     while (!\feof($input)) {
         $read = \fread($input, $config['BUFFER']);
         if ($read === false) {
             throw new FileAlert\AccessDenied('Could not read from the file');
         }
         $encrypted = \Sodium\crypto_stream_xor($read, $nonce, $encKey->get());
         \hash_update($mac, $encrypted);
         $written = \fwrite($output, $encrypted);
         if ($written === false) {
             throw new FileAlert\AccessDenied('Could not write to the file');
         }
         \Sodium\increment($nonce);
     }
     \Sodium\memzero($nonce);
     \fwrite($output, \hash_final($mac, true));
 }
Example #24
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');
 }
 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;
 }
Example #26
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);
 }
Example #27
0
 /**
  * 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');
     }
 }
Example #28
0
 /**
  * Unpack a message string into an array.
  * 
  * @param string $ciphertext
  * @return array
  * @throws CryptoException\InvalidMessage
  */
 public static function unpackMessageForDecryption(string $ciphertext) : array
 {
     $length = CryptoUtil::safeStrlen($ciphertext);
     // The first 4 bytes are reserved for the version size
     $version = CryptoUtil::safeSubstr($ciphertext, 0, Halite::VERSION_TAG_LEN);
     $config = SymmetricConfig::getConfig($version, 'encrypt');
     if ($length < $config->SHORTEST_CIPHERTEXT_LENGTH) {
         throw new CryptoException\InvalidMessage('Message is too short');
     }
     // The HKDF is used for key splitting
     $salt = CryptoUtil::safeSubstr($ciphertext, Halite::VERSION_TAG_LEN, $config->HKDF_SALT_LEN);
     // This is the nonce (we authenticated it):
     $nonce = CryptoUtil::safeSubstr($ciphertext, Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN, \Sodium\CRYPTO_STREAM_NONCEBYTES);
     // This is the crypto_stream_xor()ed ciphertext
     $xored = CryptoUtil::safeSubstr($ciphertext, Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN + \Sodium\CRYPTO_STREAM_NONCEBYTES, $length - (Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN + \Sodium\CRYPTO_STREAM_NONCEBYTES + \Sodium\CRYPTO_AUTH_BYTES));
     // $auth is the last 32 bytes
     $auth = CryptoUtil::safeSubstr($ciphertext, $length - \Sodium\CRYPTO_AUTH_BYTES);
     // We don't need this anymore.
     \Sodium\memzero($ciphertext);
     return [$version, $config, $salt, $nonce, $xored, $auth];
 }
Example #29
0
 /**
  * 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;
 }
Example #30
0
 /**
  * Delete a cache entry
  *
  * @param string $key
  * @return bool
  */
 public function delete(string $key) : bool
 {
     $shmKey = $this->getSHMKey($key);
     if (!\apcu_exists($shmKey)) {
         return true;
     }
     // Fetch
     $fetch = \apcu_fetch($shmKey);
     $length = Util::stringLength($fetch);
     // Wipe:
     \Sodium\memzero($fetch);
     \apcu_store($shmKey, \str_repeat("", $length));
     // Delete
     return \apcu_delete($shmKey);
 }