/** * Decrypt then verify a password * * @param string $password - The user-provided password * @param string $stored - The encrypted password hash * @param Key $secret_key - The master key for all passwords * @return boolean */ public static function verify($password, $stored, \ParagonIE\Halite\Contract\CryptoKeyInterface $secret_key) { // First let's decrypt the hash $hash_str = Symmetric::decrypt($stored, $secret_key); // And now to verify the hash return \Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify($hash_str, $password); }
/** * Decrypt a string using asymmetric cryptography * Wraps Symmetric::decrypt() * * @param string $source Ciphertext * @param string $ourPrivateKey Our private key * @param string $theirPublicKey Their public key * @param boolean $raw Don't hex decode the input? * * @return string */ public static function decrypt($source, Contract\CryptoKeyInterface $ourPrivateKey, Contract\CryptoKeyInterface $theirPublicKey, $raw = false) { list($secret, $public) = self::judgeKeys($ourPrivateKey, $theirPublicKey); $ecdh = new Key(self::getSharedSecret($secret, $public), false, false); $ciphertext = Symmetric::decrypt($source, $ecdh, $raw); unset($ecdh); return $ciphertext; }
/** * Decrypt then verify a password * * @param string $password - The user-provided password * @param string $stored - The encrypted password hash * @param EncryptionKey $secret_key - The master key for all passwords * @return boolean */ public static function verify($password, $stored, KeyInterface $secret_key) { if (!$secret_key instanceof EncryptionKey) { throw new \ParagonIE\Halite\Alerts\InvalidKey('Argument 3: Expected an instance of EncryptionKey'); } // First let's decrypt the hash $hash_str = Crypto::decrypt($stored, $secret_key); // Upon successful decryption, verify the password is correct return \Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify($hash_str, $password); }
/** * Decrypt a string using asymmetric cryptography * Wraps SymmetricCrypto::decrypt() * * @param string $source Ciphertext * @param EncryptionSecretKey $ourPrivateKey Our private key * @param EncryptionPublicKey $theirPublicKey Their public key * @param boolean $raw Don't hex decode the input? * * @return string * * @throws CryptoException\InvalidKey */ public static function decrypt($source, Contract\KeyInterface $ourPrivateKey, Contract\KeyInterface $theirPublicKey, $raw = false) { if (!$ourPrivateKey instanceof EncryptionSecretKey) { throw new CryptoException\InvalidKey('Argument 2: Expected an instance of EncryptionSecretKey'); } if (!$theirPublicKey instanceof EncryptionPublicKey) { throw new CryptoException\InvalidKey('Argument 3: Expected an instance of EncryptionPublicKey'); } $ecdh = new EncryptionKey(self::getSharedSecret($ourPrivateKey, $theirPublicKey)); $ciphertext = SymmetricCrypto::decrypt($source, $ecdh, $raw); unset($ecdh); return $ciphertext; }
public function testEncryptFail() { $key = new \ParagonIE\Halite\Key(\str_repeat('A', 32)); $message = Symmetric::encrypt('test message', $key, true); $r = \Sodium\randombytes_uniform(\mb_strlen($message, '8bit')); $message[$r] = \chr(\ord($message[$r]) ^ 1 << \Sodium\randombytes_uniform(8)); try { $plain = Symmetric::decrypt($message, $key, true); $this->assertEquals($plain, $message); throw new Exception('ERROR: THIS SHOULD ALWAYS FAIL'); } catch (CryptoException\InvalidMessage $e) { $this->assertTrue($e instanceof CryptoException\InvalidMessage); } }
public function testEncryptFail() { $key = new EncryptionKey(\str_repeat('A', 32)); $message = Symmetric::encrypt('test message', $key, true); $r = \Sodium\randombytes_uniform(\mb_strlen($message, '8bit')); $message[$r] = \chr(\ord($message[$r]) ^ 1 << \Sodium\randombytes_uniform(8)); try { $plain = Symmetric::decrypt($message, $key, true); $this->assertEquals($plain, $message); $this->fail('This should have thrown an InvalidMessage exception!'); } catch (CryptoException\InvalidMessage $e) { $this->assertTrue($e instanceof CryptoException\InvalidMessage); } }
/** * Store a value in an encrypted cookie * * @param string $name * @param mixed $value * @param int $expire (defaults to 0) * @param string $path (defaults to '/') * @param string $domain (defaults to NULL) * @param bool $secure (defaults to TRUE) * @param bool $httponly (defaults to TRUE) * @return bool */ public function store($name, $value, $expire = 0, $path = '/', $domain = null, $secure = true, $httponly = true) { return \setcookie($name, Crypto::encrypt(\json_encode($value), $this->key), $expire, $path, $domain, $secure, $httponly); }
/** * @param string $token * * @return string * * @throws \ParagonIE\Halite\Alerts\InvalidKey * @throws \ParagonIE\Halite\Alerts\InvalidMessage * @throws \ParagonIE\Halite\Alerts\InvalidType */ public function decryptToken($token = '') { $decyrpted = Symmetric::decrypt($token, $this->key); return $decyrpted; }
public function it_fails_when_the_user_tuple_and_random_verify_do_not_match($authenticationMapper, $userMapper) { $systemKey = $this->systemEncryptionKey; $userKey = new EncryptionKey($this->authenticationData->getSessionKey()); $hashCookieName = hash_hmac('sha256', $this->authenticationData->getSessionKey() . $this->authenticationData->getUsername(), $systemKey); $userTuple = base64_encode(Crypto::encrypt($this->authenticationData->getUserId() . ":" . $hashCookieName, $systemKey)); $hashCookieContents = base64_encode(Crypto::encrypt(time() . ':' . 2 . ':' . $this->authenticationData->getUsername(), $userKey)); $_COOKIE[AuthenticationService::COOKIE_USER] = $userTuple; $_COOKIE[AuthenticationService::COOKIE_HASH_PREFIX . $hashCookieName] = $hashCookieContents; $_COOKIE[AuthenticationService::COOKIE_VERIFY_A] = hash_hmac('sha256', $userTuple, $systemKey); $_COOKIE[AuthenticationService::COOKIE_VERIFY_B] = hash_hmac('sha256', $hashCookieContents, $userKey) . 'b'; $userMapper->getUser(Argument::any())->shouldNotBeCalled(); $this->getIdentity()->shouldBe(null); }
/** * @covers Symmetric::unpackMessageForDecryption() */ public function testUnpack() { $key = new EncryptionKey(new HiddenString(\str_repeat('A', 32))); // Randomly sized plaintext $size = \Sodium\randombytes_uniform(1023) + 1; $plaintext = \Sodium\randombytes_buf($size); $message = Symmetric::encrypt(new HiddenString($plaintext), $key, true); // Let's unpack our message $unpacked = Symmetric::unpackMessageForDecryption($message); // Now to test our expected results! $this->assertSame(Util::safeStrlen($unpacked[0]), Halite::VERSION_TAG_LEN); $this->assertTrue($unpacked[1] instanceof \ParagonIE\Halite\Symmetric\Config); $config = $unpacked[1]; if ($config instanceof \ParagonIE\Halite\Symmetric\Config) { $this->assertSame(Util::safeStrlen($unpacked[2]), $config->HKDF_SALT_LEN); $this->assertSame(Util::safeStrlen($unpacked[3]), \Sodium\CRYPTO_STREAM_NONCEBYTES); $this->assertSame(Util::safeStrlen($unpacked[4]), Util::safeStrlen($message) - (Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN + \Sodium\CRYPTO_STREAM_NONCEBYTES + $config->MAC_SIZE)); $this->assertSame(Util::safeStrlen($unpacked[5]), $config->MAC_SIZE); } else { $this->fail('Cannot continue'); } }
/** * Rifle through 4 cookies, ensuring that all details line up. If they do, we accept that the cookies authenticate * a specific user. * * Some notes: * * - COOKIE_VERIFY_A is a do-not-decrypt check of COOKIE_USER * - COOKIE_VERIFY_B is a do-not-decrypt check of the random-named-cookie specified by COOKIE_USER * - COOKIE_USER has its contents encrypted by the system key * - the random-named-cookie has its contents encrypted by the user key * * @see self::setSessionCookies * @return User|null */ public function getIdentity() { if ($this->identity) { return $this->identity; } if (!isset($_COOKIE[self::COOKIE_VERIFY_A])) { return null; } if (!isset($_COOKIE[self::COOKIE_USER])) { return null; } $systemKey = new EncryptionKey($this->systemEncryptionKey); $verificationCookie = $_COOKIE[self::COOKIE_VERIFY_A]; $hashPass = hash_equals(hash_hmac('sha256', $_COOKIE[self::COOKIE_USER], $systemKey), $verificationCookie); // // 1. Is the verify cookie still equivalent to the user cookie, if so, do not decrypt // if (!$hashPass) { return null; } // // 2. If the user cookie was not tampered with, decrypt its contents with the system key // try { $userTuple = Crypto::decrypt(base64_decode($_COOKIE[self::COOKIE_USER]), $systemKey); if (strpos($userTuple, ':') === false) { throw new \Exception(); } // paranoid, make sure we have everything we need @(list($cookieUserId, $hashCookieSuffix) = @explode(":", $userTuple, 2)); if (!isset($cookieUserId) || !isset($hashCookieSuffix) || !is_numeric($cookieUserId) || !trim($hashCookieSuffix)) { throw new \Exception(); } /** @var AuthenticationRecordInterface $auth */ if (!($auth = $this->authenticationProvider->findByUserId($cookieUserId))) { throw new \Exception(); } $hashCookieName = self::COOKIE_HASH_PREFIX . $hashCookieSuffix; // // 2. Check the hashCookie for corroborating data // if (!isset($_COOKIE[$hashCookieName])) { throw new \Exception(); } $userKey = new EncryptionKey($auth->getSessionKey()); $hashPass = hash_equals(hash_hmac('sha256', $_COOKIE[$hashCookieName], $userKey), $_COOKIE[self::COOKIE_VERIFY_B]); if (!$hashPass) { throw new \Exception(); } // // 3. Decrypt the hash cookie with the user key // $hashedCookieContents = Crypto::decrypt(base64_decode($_COOKIE[$hashCookieName]), $userKey); if (!substr_count($hashedCookieContents, ':') == 2) { throw new \Exception(); } list(, $hashedUserId, $hashedUsername) = explode(':', $hashedCookieContents); if ($hashedUserId != $cookieUserId) { throw new \Exception(); } if ($hashedUsername != $auth->getUsername()) { throw new \Exception(); } $this->purgeHashCookies($hashCookieName); // // 4. Cookies check out - it's up to the user provider now // $user = $this->userProvider->getUser($auth->getUserId()); if ($user) { $this->setIdentity($user); return $this->identity; } } catch (\Exception $x) { $this->purgeHashCookies(); } return null; }
/** * Perform decryption * * @param $text * @return mixed */ public static function decrypt($text) { $key = KeyFactory::loadEncryptionKey(config('laracrypt.path')); return Crypto::decrypt($text, $key); }
/** * Get the user's two-factor authentication secret * * @param int $userID * @return bool */ public function resetTwoFactorSecret(int $userID) : bool { $state = State::instance(); $this->db->beginTransaction(); $secret = \random_bytes(20); $this->db->update('airship_users', ['totp_secret' => Symmetric::encrypt($secret, $state->keyring['auth.password_key'])], ['userid' => $userID]); return $this->db->commit(); }
/** * Let's do an automatic login * * @param string $token * @param string $uid_idx * @param string $token_idx * @return bool * @throws LongTermAuthAlert (only in debug mode) * @throws \TypeError */ protected function doAutoLogin(string $token, string $uid_idx, string $token_idx) : bool { if (!$this->airship_auth instanceof Authentication) { $this->tightenSecurityBolt(); } $state = State::instance(); try { $userId = $this->airship_auth->loginByToken($token); \Sodium\memzero($token); if (!$this->verifySessionCanary($userId, false)) { return false; } // Regenerate session ID: Session::regenerate(true); // Set session variable $_SESSION[$uid_idx] = $userId; $autoPilot = Gears::getName('AutoPilot'); if (IDE_HACKS) { // We're using getName(), this is just to fool IDEs. $autoPilot = new AutoPilot(); } $httpsOnly = (bool) $autoPilot::isHTTPSConnection(); // Rotate the authentication token: Cookie::setcookie($token_idx, Symmetric::encrypt($this->airship_auth->rotateToken($token, $userId), $state->keyring['cookie.encrypt_key']), \time() + ($state->universal['long-term-auth-expire'] ?? self::DEFAULT_LONGTERMAUTH_EXPIRE), '/', '', $httpsOnly ?? false, true); return true; } catch (LongTermAuthAlert $e) { $state = State::instance(); // Let's wipe our long-term authentication cookies Cookie::setcookie($token_idx, null, 0, '/', '', $httpsOnly ?? false, true); // Let's log this incident if (\property_exists($this, 'log')) { $this->log($e->getMessage(), LogLevel::CRITICAL, ['exception' => \Airship\throwableToArray($e)]); } else { $state->logger->log(LogLevel::CRITICAL, $e->getMessage(), ['exception' => \Airship\throwableToArray($e)]); } // In debug mode, re-throw the exception: if ($state->universal['debug']) { throw $e; } } return false; }