/** * SharedMemory constructor *. * @param Key|null $cacheKey * @param AuthenticationKey|null $authKey * @param string $personalization */ public function __construct(Key $cacheKey = null, AuthenticationKey $authKey = null, string $personalization = '') { if (!$cacheKey) { $state = State::instance(); $cacheKey = $state->keyring['cache.hash_key']; } // We need a short hash key: $this->cacheKeyL = CryptoUtil::safeSubstr($cacheKey->getRawKeyMaterial(), 0, \Sodium\CRYPTO_SHORTHASH_KEYBYTES); $this->cacheKeyR = CryptoUtil::safeSubstr($cacheKey->getRawKeyMaterial(), \Sodium\CRYPTO_SHORTHASH_KEYBYTES, \Sodium\CRYPTO_SHORTHASH_KEYBYTES); if ($authKey) { $this->authKey = $authKey; } $this->personalization = $personalization; }
/** * 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(string $password, string $stored, EncryptionKey $secret_key) : bool { // First let's decrypt the hash $hash_str = Crypto::decrypt($stored, $secret_key); // Upon successful decryption, verify the password is correct $isArgon2 = \hash_equals(CryptoUtil::safeSubstr($hash_str, 0, 9), \Sodium\CRYPTO_PWHASH_STRPREFIX); $isScrypt = \hash_equals(CryptoUtil::safeSubstr($hash_str, 0, 3), \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_STRPREFIX); if ($isArgon2) { return \Sodium\crypto_pwhash_str_verify($hash_str, $password); } elseif ($isScrypt) { return \Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify($hash_str, $password); } return false; }
/** * Unpack a message string into an array (assigned to variables via list()). * * Should return exactly 6 elements. * * @param string $ciphertext * @return string[] * @throws InvalidMessage */ public static function unpackMessageForDecryption(string $ciphertext) : array { $length = CryptoUtil::safeStrlen($ciphertext); // Fail fast on invalid messages if ($length < Halite::VERSION_TAG_LEN) { throw new InvalidMessage('Message is too short'); } // 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 InvalidMessage('Message is too short'); } // The salt is used for key splitting (via HKDF) $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 $encrypted = 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 + $config->MAC_SIZE)); // $auth is the last 32 bytes $auth = CryptoUtil::safeSubstr($ciphertext, $length - $config->MAC_SIZE); // We don't need this anymore. \Sodium\memzero($ciphertext); // Now we return the pieces in a specific order: return [$version, $config, $salt, $nonce, $encrypted, $auth]; }