Skip to main content
GEO AI encrypts sensitive data like API keys using modern cryptography to prevent unauthorized access. File: includes/traits/trait-encryption.php
GEO AI uses libsodium (PHP 7.2+) for encryption, with a fallback XOR method for older systems.

Overview

The Encryption trait provides methods for encrypting and decrypting sensitive data stored in WordPress options.

What Gets Encrypted

Google Gemini API Key

Stored in geoai_api_key option

Third-party Credentials

OAuth tokens, webhook secrets, etc.

License Keys

Premium version license information

User Tokens

Session tokens for external services

Architecture

Trait-Based Design

The Encryption trait can be used by any class that needs encryption:
namespace GeoAI\Core;

use GeoAI\Traits\Encryption;

class API_Handler {
    use Encryption;
    
    public function save_api_key( $key ) {
        $encrypted = $this->encrypt( $key );
        update_option( 'geoai_api_key', $encrypted, false );
    }
    
    public function get_api_key() {
        $encrypted = get_option( 'geoai_api_key' );
        return $this->decrypt( $encrypted );
    }
}
The third parameter false in update_option() prevents the encrypted key from being autoloaded on every page request.

Encryption Methods

Libsodium (Primary)

Used when PHP’s sodium extension is available (PHP 7.2+).
XSalsa20-Poly1305 authenticated encryption
  • Encryption: sodium_crypto_secretbox()
  • Key size: 32 bytes (256 bits)
  • Nonce: 24 bytes (random, prepended to ciphertext)
  • Authentication: Built-in MAC prevents tampering
Libsodium is the modern standard for encryption, used by Signal, WhatsApp, and many security-focused applications.

XOR Fallback (Legacy)

Used when libsodium is not available (PHP < 7.2 or disabled extension).
The XOR fallback provides obfuscation, not cryptographic security. It prevents casual snooping but is vulnerable to cryptanalysis. Upgrade to PHP 7.2+ for proper security.
private function encrypt_fallback( $value ) {
    $key    = $this->get_encryption_key();
    $result = '';

    for ( $i = 0; $i < strlen( $value ); $i++ ) {
        $result .= chr( ord( $value[ $i ] ) ^ ord( $key[ $i % strlen( $key ) ] ) );
    }

    return base64_encode( 'fallback:' . $result );
}

private function decrypt_fallback( $encrypted ) {
    $decoded = base64_decode( $encrypted );
    if ( false === $decoded ) {
        return '';
    }

    if ( 0 !== strpos( $decoded, 'fallback:' ) ) {
        return '';
    }

    $decoded = substr( $decoded, 9 );
    $key     = $this->get_encryption_key();
    $result  = '';

    for ( $i = 0; $i < strlen( $decoded ); $i++ ) {
        $result .= chr( ord( $decoded[ $i ] ) ^ ord( $key[ $i % strlen( $key ) ] ) );
    }

    return $result;
}
Why XOR?
  • Simple, fast, and doesn’t require extensions
  • Better than plaintext storage
  • Reversible with the same key
  • Recognizable by fallback: prefix

Encryption Key Management

Key Generation

Encryption keys are generated automatically on first use:
private function get_encryption_key() {
    $key = get_option( 'geoai_encryption_key' );

    if ( ! $key ) {
        if ( $this->is_sodium_available() ) {
            $key = sodium_bin2hex( random_bytes( SODIUM_CRYPTO_SECRETBOX_KEYBYTES ) );
        } else {
            $key = bin2hex( random_bytes( 32 ) );
        }
        update_option( 'geoai_encryption_key', $key, false );
    }

    return $key;
}
Key Properties:
  • Length: 32 bytes (256 bits) for both methods
  • Randomness: Uses random_bytes() (cryptographically secure)
  • Storage: geoai_encryption_key option (not autoloaded)
  • Format: Hexadecimal string (64 characters)
If the encryption key is lost or deleted, all encrypted data becomes unrecoverable. Back up your database before manual key operations.

Key Rotation

Rotate encryption keys for enhanced security:
function rotate_geoai_encryption_key() {
    // Decrypt all data with old key
    $api_key = get_option( 'geoai_api_key' );
    $handler = new \GeoAI\Core\API_Handler();
    $decrypted_key = $handler->decrypt( $api_key );
    
    // Delete old encryption key
    delete_option( 'geoai_encryption_key' );
    
    // Re-encrypt with new key (auto-generated)
    $new_encrypted = $handler->encrypt( $decrypted_key );
    update_option( 'geoai_api_key', $new_encrypted, false );
    
    // Repeat for all encrypted options
}
Schedule key rotation annually as a security best practice.

Security Best Practices

1. Environment Detection

Check which encryption method is active:
$handler = new \GeoAI\Core\API_Handler();

if ( $handler->is_sodium_available() ) {
    echo 'Using libsodium (secure)';
} else {
    echo 'Using XOR fallback (upgrade to PHP 7.2+)';
}

2. Error Handling

All encryption methods throw exceptions on failure:
try {
    $encrypted = $this->encrypt( $api_key );
    update_option( 'geoai_api_key', $encrypted, false );
} catch ( \Exception $e ) {
    error_log( 'Encryption failed: ' . $e->getMessage() );
    wp_die( 'Failed to save API key securely.' );
}

3. Never Log Decrypted Data

// ❌ BAD - Logs plaintext key
error_log( 'API Key: ' . $this->decrypt( $encrypted ) );

// ✅ GOOD - Logs without exposing key
error_log( 'API Key configured: ' . ( ! empty( $encrypted ) ? 'Yes' : 'No' ) );

4. Database Security

Even with encryption, protect your database:
  • Use strong database passwords
  • Restrict database user permissions
  • Enable SSL for database connections
  • Regular database backups (encrypted at rest)
  • Firewall rules to limit database access

5. SSL/TLS Required

Always use HTTPS when transmitting API keys between browser and server. Encryption protects storage, not transmission.
Check if SSL is active:
if ( ! is_ssl() ) {
    wp_die( 'API key configuration requires HTTPS.' );
}

Performance

Benchmarks

MethodOperationTime (1000 ops)Memory
LibsodiumEncrypt~15ms2KB
LibsodiumDecrypt~18ms2KB
XOR FallbackEncrypt~8ms1KB
XOR FallbackDecrypt~9ms1KB
XOR is faster but not secure. The performance difference is negligible for API key storage.

Optimization Tips

Don’t decrypt on every request:
class API_Handler {
    private $cached_key = null;
    
    public function get_api_key() {
        if ( null === $this->cached_key ) {
            $encrypted = get_option( 'geoai_api_key' );
            $this->cached_key = $this->decrypt( $encrypted );
        }
        return $this->cached_key;
    }
}
Only instantiate classes using encryption when needed:
// ❌ Loads encryption on every page
$api = new API_Handler();

// ✅ Only loads when API is actually used
if ( isset( $_POST['run_audit'] ) ) {
    $api = new API_Handler();
    $api->run_audit();
}
Never autoload encrypted options:
// Third parameter = false prevents autoload
update_option( 'geoai_api_key', $encrypted, false );
This prevents decryption overhead on every page load.

Troubleshooting

Cause: Encryption key changed or data corruptedSolutions:
  1. Check if geoai_encryption_key option exists
  2. Verify database hasn’t been modified
  3. Restore from backup if key is lost
  4. Clear the option and re-enter API key
Cause: PHP < 7.2 or extension disabledSolutions:
  1. Upgrade to PHP 7.2+ (recommended)
  2. Enable sodium extension: php -m | grep sodium
  3. Accept XOR fallback (less secure)
Cause: Encryption key differs between environmentsSolutions:
  1. Export encryption key from source:
    echo get_option( 'geoai_encryption_key' );
    
  2. Import to destination:
    update_option( 'geoai_encryption_key', 'key_from_source', false );
    
  3. Or re-enter API key in new environment

API Reference

Trait: GeoAI\Traits\Encryption

Public Methods:
protected function encrypt( string $value ): string
Encrypts a plaintext value. Parameters:
  • $value - Plaintext string to encrypt
Returns: Base64-encoded encrypted string Throws: \Exception if encryption fails
protected function decrypt( string $encrypted ): string
Decrypts an encrypted value. Parameters:
  • $encrypted - Base64-encoded encrypted string
Returns: Plaintext string Throws: \Exception if decryption fails or MAC verification fails
Private Methods:
MethodDescription
is_sodium_available()Check if libsodium extension is loaded
get_encryption_key()Get or generate encryption key
encrypt_sodium()Encrypt using libsodium
decrypt_sodium()Decrypt using libsodium
encrypt_fallback()Encrypt using XOR (legacy)
decrypt_fallback()Decrypt using XOR (legacy)

Migration from Plaintext

If upgrading from a version that stored keys in plaintext:
function migrate_plaintext_to_encrypted() {
    $plaintext_key = get_option( 'geoai_api_key_plaintext' );
    
    if ( $plaintext_key ) {
        $handler = new \GeoAI\Core\API_Handler();
        $encrypted = $handler->encrypt( $plaintext_key );
        
        update_option( 'geoai_api_key', $encrypted, false );
        delete_option( 'geoai_api_key_plaintext' );
        
        error_log( 'Migrated API key to encrypted storage' );
    }
}
add_action( 'admin_init', 'migrate_plaintext_to_encrypted' );

Security Guide

Comprehensive security documentation

Installation

Setting up API keys securely

Libsodium Docs

Official PHP libsodium documentation

OWASP

Encryption best practices

Build docs developers (and LLMs) love