cipher = (int) $cipher; $this->key = hash_pbkdf2( 'sha256', (string) $password, $salt, self::KDF_ITERATIONS, 32, true ); if ( ! self::supports( $this->cipher ) ) { throw Spinix_Error::make( 'php_extension_missing', array( 'ext' => esc_html( self::CIPHER_AES_GCM === $this->cipher ? 'openssl' : 'sodium' ) ) ); } } /** * Best cipher available on this server, or 0. * * @return int */ public static function best_cipher() { if ( self::supports( self::CIPHER_AES_GCM ) ) { return self::CIPHER_AES_GCM; } if ( self::supports( self::CIPHER_XCHACHA ) ) { return self::CIPHER_XCHACHA; } return 0; } /** * @param int $cipher Cipher. * @return bool */ public static function supports( $cipher ) { if ( self::CIPHER_AES_GCM === (int) $cipher ) { return function_exists( 'openssl_encrypt' ) && in_array( 'aes-256-gcm', array_map( 'strtolower', openssl_get_cipher_methods() ), true ); } if ( self::CIPHER_XCHACHA === (int) $cipher ) { return function_exists( 'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' ); } return false; } /** * Value stored in the header to check the password without decrypting data. * * @return string 32 bytes. */ public function key_check() { return hash_hmac( 'sha256', 'spinix-key-check-v1', $this->key, true ); } /** * Fixed per-block overhead in bytes. * * @return int */ public function overhead() { return self::CIPHER_AES_GCM === $this->cipher ? 28 : 40; } /** * Encrypt a block. * * @param string $plain Data. * @return string nonce . tag . ciphertext */ public function encrypt( $plain ) { if ( self::CIPHER_AES_GCM === $this->cipher ) { $iv = random_bytes( 12 ); $tag = ''; $ct = openssl_encrypt( $plain, 'aes-256-gcm', $this->key, OPENSSL_RAW_DATA, $iv, $tag, '', 16 ); return $iv . $tag . $ct; } $nonce = random_bytes( 24 ); $ct = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt( $plain, '', $nonce, $this->key ); return $nonce . $ct; } /** * Decrypt a block. * * @param string $data Data. * @return string|false */ public function decrypt( $data ) { if ( self::CIPHER_AES_GCM === $this->cipher ) { $iv = substr( $data, 0, 12 ); $tag = substr( $data, 12, 16 ); $ct = (string) substr( $data, 28 ); return openssl_decrypt( $ct, 'aes-256-gcm', $this->key, OPENSSL_RAW_DATA, $iv, $tag ); } $nonce = substr( $data, 0, 24 ); try { return sodium_crypto_aead_xchacha20poly1305_ietf_decrypt( (string) substr( $data, 24 ), '', $nonce, $this->key ); } catch ( Throwable $e ) { return false; } } }