File "User_Encryption_Sodium.php"
Full Path: /home/siazco/grocery.siazco.se/wp-content/plugins/better-wp-security/core/lib/encryption/User_Encryption_Sodium.php
File size: 4.49 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace iThemesSecurity\Encryption;
use Exception;
use RuntimeException;
use SodiumException;
final class User_Encryption_Sodium implements User_Encryption {
/**
* Prefix for encrypted secrets. Contains a version identifier.
*
* $t1$ -> v1 (RFC 6238, encrypted with XChaCha20-Poly1305, with a key derived from HMAC-SHA256
* of SECURE_AUTH_SAL.)
*
* @var string
*/
private const ENCRYPTED_PREFIX = '$t1$';
/**
* Current "version" of the encryption protocol.
*
* 1 -> $t1$nonce|ciphertext|tag
*/
private const ENCRYPTED_VERSION = 1;
/** @var string */
private $secret;
/**
* Instantiates a new User Encryption implementation.
*
* @param string $secret Random bytes.
*/
public function __construct( string $secret ) {
$this->secret = $secret;
if (
! function_exists( 'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' ) &&
! function_exists( 'sodiumCompatAutoloader' )
) {
require_once ABSPATH . WPINC . '/sodium_compat/autoload.php';
}
}
public static function is_encrypted( string $message ): bool {
if ( strlen( $message ) < 40 ) {
return false;
}
if ( strpos( $message, self::ENCRYPTED_PREFIX ) !== 0 ) {
return false;
}
return true;
}
public function encrypt( string $message, int $user_id ): string {
$prefix = $this->get_version_header();
try {
$nonce = random_bytes( 24 );
$ciphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
$message,
$this->serialize_aad( $prefix, $nonce, $user_id ),
$nonce,
$this->get_encryption_key()
);
} catch ( SodiumException $e ) {
throw new RuntimeException( 'Encryption failed', 0, $e );
} catch ( Exception $e ) {
throw new RuntimeException( 'Nonce generation failed.', 0, $e );
}
return self::ENCRYPTED_PREFIX . base64_encode( $nonce . $ciphertext );
}
public function decrypt( string $encrypted, int $user_id ): string {
if ( strlen( $encrypted ) < 4 ) {
throw new RuntimeException( 'Message is too short to be encrypted' );
}
$prefix = substr( $encrypted, 0, 4 );
$version = self::get_version_id( $prefix );
if ( $version !== 1 ) {
throw new RuntimeException( 'Unknown version: ' . $version );
}
$decoded = base64_decode( substr( $encrypted, 4 ) );
$nonce = substr( $decoded, 0, 24 );
$ciphertext = substr( $decoded, 24 );
try {
$decrypted = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
$ciphertext,
self::serialize_aad( $prefix, $nonce, $user_id ),
$nonce,
$this->get_encryption_key( $version )
);
} catch ( SodiumException $ex ) {
throw new RuntimeException( 'Decryption failed', 0, $ex );
}
// If we don't have a string, throw an exception because decryption failed.
if ( ! is_string( $decrypted ) ) {
throw new RuntimeException( 'Could not decrypt secret' );
}
return $decrypted;
}
/**
* Serializes the Additional Authenticated Data for secret encryption.
*
* @param string $prefix Version prefix.
* @param string $nonce Encryption nonce.
* @param int $user_id User ID.
*
* @return string
*/
private function serialize_aad( string $prefix, string $nonce, int $user_id ): string {
return $prefix . $nonce . pack( 'N', $user_id );
}
/**
* Gets the version prefix from a given version number.
*
* @param int $number Version number.
*
* @return string
* @throws RuntimeException For incorrect versions.
*/
private function get_version_header( int $number = self::ENCRYPTED_VERSION ): string {
switch ( $number ) {
case 1:
return '$t1$';
default:
throw new RuntimeException( 'Incorrect version number: ' . $number );
}
}
/**
* Gets the version prefix from a given version number.
*
* @param string $prefix Version prefix.
*
* @return int
* @throws RuntimeException For incorrect versions.
*/
private function get_version_id( string $prefix = self::ENCRYPTED_PREFIX ): int {
switch ( $prefix ) {
case '$t1$':
return 1;
default:
throw new RuntimeException( 'Incorrect version identifier: ' . $prefix );
}
}
/**
* Gets the encryption key for encrypting secrets.
*
* @param int $version Key derivation strategy.
*
* @return string
* @throws RuntimeException For incorrect versions.
*/
private function get_encryption_key( int $version = self::ENCRYPTED_VERSION ): string {
switch ( $version ) {
case 1:
return hash_hmac( 'sha256', $this->secret, 'itsec-user-encryption', true );
default:
throw new RuntimeException( 'Incorrect version number: ' . $version );
}
}
}