<?php
if ( ! defined( 'ABSPATH' ) ) exit;
class AWG_Crypto {
private static $master_key = null;
public static function get_master_key(): string {
if ( self::$master_key !== null ) {
return self::$master_key;
}
$salt = defined( 'AWG_PLUGIN_SALT' ) ? AWG_PLUGIN_SALT : 'awg_fallback_' . DB_NAME;
$material = AUTH_KEY . SECURE_AUTH_KEY . $salt;
self::$master_key = sodium_crypto_generichash(
$material,
'',
SODIUM_CRYPTO_SECRETBOX_KEYBYTES
);
return self::$master_key;
}
/* ---- symmetric encrypt / decrypt ---- */
public static function encrypt( string $plaintext ): string {
$key = self::get_master_key();
$nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
$ct = sodium_crypto_secretbox( $plaintext, $nonce, $key );
return base64_encode( $nonce . $ct );
}
public static function decrypt( string $blob ) {
$key = self::get_master_key();
$decoded = base64_decode( $blob, true );
if ( $decoded === false || strlen( $decoded ) < SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + 1 ) {
return false;
}
$nonce = mb_substr( $decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit' );
$ct = mb_substr( $decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit' );
$plain = sodium_crypto_secretbox_open( $ct, $nonce, $key );
return $plain !== false ? $plain : false;
}
/* ---- wp_options helpers ---- */
public static function encrypt_option( string $option, $value ): void {
$json = wp_json_encode( $value );
$encrypted = self::encrypt( $json );
update_option( $option, $encrypted, false );
}
public static function decrypt_option( string $option, $default = null ) {
$raw = get_option( $option, null );
if ( $raw === null || $raw === false ) {
return $default;
}
$plain = self::decrypt( $raw );
if ( $plain === false ) {
return $default;
}
return json_decode( $plain, true );
}
public static function delete_option( string $option ): void {
delete_option( $option );
}
/* ---- HMAC (keyed hash) ---- */
public static function hmac( string $data ): string {
return sodium_crypto_generichash( $data, self::get_master_key(), 32 );
}
public static function hmac_hex( string $data ): string {
return sodium_bin2hex( self::hmac( $data ) );
}
public static function hmac_verify( string $data, string $expected_hex ): bool {
return hash_equals( self::hmac_hex( $data ), $expected_hex );
}
/* ---- sealed box (asymmetric, anonymous) ---- */
public static function seal( string $plaintext, string $public_key ): string {
return sodium_crypto_box_seal( $plaintext, $public_key );
}
public static function seal_open( string $ciphertext, string $keypair ): string {
$result = sodium_crypto_box_seal_open( $ciphertext, $keypair );
if ( $result === false ) {
throw new RuntimeException( 'seal_open failed' );
}
return $result;
}
public static function generate_box_keypair(): array {
$kp = sodium_crypto_box_keypair();
return [
'public' => sodium_bin2hex( sodium_crypto_box_publickey( $kp ) ),
'secret' => sodium_bin2hex( sodium_crypto_box_secretkey( $kp ) ),
'keypair' => sodium_bin2hex( $kp ),
];
}
/* ---- generators ---- */
public static function generate_password( int $length = 48 ): string {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#%^&*_-+=';
$out = '';
$max = strlen( $chars ) - 1;
for ( $i = 0; $i < $length; $i++ ) {
$out .= $chars[ random_int( 0, $max ) ];
}
return $out;
}
public static function generate_username( int $length = 12 ): string {
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
$out = 'wp_';
$max = strlen( $chars ) - 1;
for ( $i = 0; $i < $length; $i++ ) {
$out .= $chars[ random_int( 0, $max ) ];
}
return $out;
}
public static function random_hex( int $bytes = 32 ): string {
return sodium_bin2hex( random_bytes( $bytes ) );
}
/* ---- cleanup ---- */
public static function wipe(): void {
if ( self::$master_key !== null ) {
sodium_memzero( self::$master_key );
self::$master_key = null;
}
}
}