getRawKey() ); return self::MAGIC_HEADER . Base64UrlSafe::encode($nonce . $ciphertext); } /** * Decrypt a message using XChaCha20-Poly1305 * * @param string $ciphertext * @param SymmetricKey $key * * @return string * @throws InvalidCiphertextException * @throws \SodiumException */ public function decrypt($ciphertext, SymmetricKey $key) { // Make sure we're using the correct version: $header = Binary::safeSubstr($ciphertext, 0, 5); if (!SodiumUtil::hashEquals($header, self::MAGIC_HEADER)) { throw new InvalidCiphertextException('Invalid ciphertext header.'); } // Decompose the encrypted message into its constituent parts: $decoded = Base64UrlSafe::decode(Binary::safeSubstr($ciphertext, 5)); if (Binary::safeStrlen($decoded) < (self::NONCE_SIZE + 16)) { throw new InvalidCiphertextException('Message is too short.'); } $nonce = Binary::safeSubstr($decoded, 0, self::NONCE_SIZE); $encrypted = Binary::safeSubstr($decoded, self::NONCE_SIZE); return SodiumCompat::crypto_aead_xchacha20poly1305_ietf_decrypt( $encrypted, $nonce, $nonce, $key->getRawKey() ); } /** * @param string $plaintext * @param SymmetricKey $key * @param int|null $bitLength * * @return string * @throws \SodiumException */ public function blindIndexFast( $plaintext, SymmetricKey $key, $bitLength = null ) { if (\is_null($bitLength)) { $bitLength = 256; } if ($bitLength > 512) { throw new \SodiumException('Output length is too high'); } if ($bitLength > 256) { $hashLength = $bitLength >> 3; } else { $hashLength = 32; } $hash = SodiumCompat::crypto_generichash( $plaintext, $key->getRawKey(), $hashLength ); return Util::andMask($hash, $bitLength); } /** * @param string $plaintext * @param SymmetricKey $key * @param int|null $bitLength * @param array $config * * @return string * @throws \SodiumException */ public function blindIndexSlow( $plaintext, SymmetricKey $key, $bitLength = null, array $config = [] ) { if (!SodiumCompat::crypto_pwhash_is_available()) { throw new \SodiumException( 'Not using the native libsodium bindings' ); } $opsLimit = SodiumCompat::CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE; $memLimit = SodiumCompat::CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE; if (isset($config['opslimit'])) { if ($config['opslimit'] > $opsLimit) { $opsLimit = (int) $config['opslimit']; } } if (isset($config['memlimit'])) { if ($config['memlimit'] > $memLimit) { $memLimit = (int) $config['memlimit']; } } if (\is_null($bitLength)) { $bitLength = 256; } /** @var int $pwHashLength */ $pwHashLength = $bitLength >> 3; if ($pwHashLength < 16) { $pwHashLength = 16; } if ($pwHashLength > 4294967295) { throw new \SodiumException('Output length is far too big'); } $hash = SodiumCompat::crypto_pwhash( $pwHashLength, $plaintext, SodiumCompat::crypto_generichash($key->getRawKey(), '', 16), $opsLimit, $memLimit, SodiumCompat::CRYPTO_PWHASH_ALG_ARGON2ID13 ); return Util::andMask($hash, $bitLength); } /** * @param string $tableName * @param string $fieldName * @param string $indexName * @return string * @throws \SodiumException */ public function getIndexTypeColumn($tableName, $fieldName, $indexName) { $hash = SodiumCompat::crypto_shorthash( Util::pack([$fieldName, $indexName]), SodiumCompat::crypto_generichash($tableName, '', 16) ); return Base32::encodeUnpadded($hash); } }