<?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; } } }