* * @package AI House */ defined( 'ABSPATH' ) || exit; /** * Class MYBOT_Crypto */ class MYBOT_Crypto { /** * Prefix that identifies an encrypted string. * * @var string */ const PREFIX = 'mybot:v1:'; /** * Symmetric encryption algorithm. * * @var string */ const METHOD = 'aes-256-gcm'; /** * Returned when decryption fails, to distinguish it from "not entered". * * @var string */ const UNDECRYPTABLE = '__mybot_undecryptable__'; /** * Whether OpenSSL is available. * * @return bool */ public static function is_available() { return function_exists( 'openssl_encrypt' ) && in_array( self::METHOD, openssl_get_cipher_methods(), true ); } /** * Whether the string is already encrypted. * * @param mixed $value Value to check. * @return bool */ public static function is_encrypted( $value ) { return is_string( $value ) && 0 === strpos( $value, self::PREFIX ); } /** * Encrypts a string. * * If OpenSSL is unavailable, the original value is returned with an admin notice * instead of breaking functionality. * * @param string $plain Plain text string. * @return string */ public static function encrypt( $plain ) { $plain = (string) $plain; if ( '' === $plain ) { return ''; } if ( self::is_encrypted( $plain ) ) { return $plain; } if ( ! self::is_available() ) { return $plain; } $iv_length = openssl_cipher_iv_length( self::METHOD ); $iv = openssl_random_pseudo_bytes( $iv_length ); $tag = ''; $cipher = openssl_encrypt( $plain, self::METHOD, self::key(), OPENSSL_RAW_DATA, $iv, $tag ); if ( false === $cipher ) { return $plain; } return self::PREFIX . base64_encode( $iv . $tag . $cipher ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- required to store binary data in the database as text. } /** * Decrypts a string. * * @param string $value Encrypted string, or a plain text string. * @return string */ public static function decrypt( $value ) { $value = (string) $value; if ( '' === $value ) { return ''; } if ( ! self::is_encrypted( $value ) ) { // Legacy value, or one pasted directly into wp-config by the user. return $value; } if ( ! self::is_available() ) { return self::UNDECRYPTABLE; } $raw = base64_decode( substr( $value, strlen( self::PREFIX ) ), true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode if ( false === $raw ) { return self::UNDECRYPTABLE; } $iv_length = openssl_cipher_iv_length( self::METHOD ); $tag_length = 16; if ( strlen( $raw ) <= ( $iv_length + $tag_length ) ) { return self::UNDECRYPTABLE; } $iv = substr( $raw, 0, $iv_length ); $tag = substr( $raw, $iv_length, $tag_length ); $cipher = substr( $raw, $iv_length + $tag_length ); $plain = openssl_decrypt( $cipher, self::METHOD, self::key(), OPENSSL_RAW_DATA, $iv, $tag ); return ( false === $plain ) ? self::UNDECRYPTABLE : $plain; } /** * Safe display: masks the body and only reveals the last 4 characters. * * @param string $secret Secret string. * @return string */ public static function mask( $secret ) { $secret = (string) $secret; if ( '' === $secret ) { return ''; } $tail = substr( $secret, -4 ); return '••••••••' . $tail; } /** * Encryption key derived from the salts in wp-config.php. * * @return string */ protected static function key() { $material = ''; foreach ( array( 'AUTH_KEY', 'SECURE_AUTH_KEY', 'NONCE_SALT' ) as $constant ) { if ( defined( $constant ) ) { $material .= constant( $constant ); } } if ( '' === $material ) { $material = 'mybot-fallback-' . DB_NAME . DB_USER; } return hash( 'sha256', $material . '|mybot', true ); } }