key = $key; } /** * Derives a 32-byte key from arbitrary key material (e.g. WordPress salts). * * @param string $material Key material. */ public static function derive_key( string $material ): string { return sodium_crypto_generichash( 'tidelist|' . $material, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES ); } /** * Encrypts a string. * * @param string $plain Plain text. * @return string Prefixed base64 string, safe to store in an option. */ public function encrypt( string $plain ): string { $nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- encoding ciphertext for storage. return self::PREFIX . base64_encode( $nonce . sodium_crypto_secretbox( $plain, $nonce, $this->key ) ); } /** * Decrypts a string produced by encrypt(). * * @param string $sealed Value from encrypt(). * @return string|null Plain text, or null if the value is malformed, tampered with or the key changed. */ public function decrypt( string $sealed ): ?string { if ( 0 !== strpos( $sealed, self::PREFIX ) ) { return null; } // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- decoding stored ciphertext. $raw = base64_decode( substr( $sealed, strlen( self::PREFIX ) ), true ); if ( false === $raw || strlen( $raw ) <= SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ) { return null; } $nonce = substr( $raw, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); $cipher = substr( $raw, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); $plain = sodium_crypto_secretbox_open( $cipher, $nonce, $this->key ); return false === $plain ? null : $plain; } }