The Encryption Layer: AES-256 Everywhere

If you store customer email addresses in your WordPress database in cleartext, you’ve already failed. Every table in every AJT plugin that holds PII uses AES-256 encryption at rest.

How It Works

The encryption layer provides two functions: ajt_stripe_encrypt() and ajt_stripe_decrypt(). Under the hood, it’s AES-256-CBC with a per-installation key derived from WordPress’s wp_salt(). The IV is generated randomly for each encryption operation and stored alongside the ciphertext.

Encrypted fields: email, name, phone, address, payment method identifiers, cancellation reasons, customer notes. Anything that could identify an individual.

Blind Index Search

Encryption creates a problem: you can’t search encrypted columns with SQL WHERE clauses. The solution is blind indexes. When an email is stored, we also store its HMAC-SHA256 hash using a separate key. To search, we hash the search term with the same key and look up the hash. The hash is deterministic (same input = same output) but one-way, you can’t reverse it to get the email.

This is what ajt_stripe_email_hash() does. When you need to find a customer by email, you hash the email and query the customer_email_hash column. The actual email address never appears in a SQL query.

Separate Key Chains

The client plugins (running on customer sites) and the License Master (running on ajt.support) use separate encryption keys derived from different salts. Data encrypted on a customer’s site cannot be decrypted on the master, and vice versa. This is deliberate, even if someone compromises one installation, they can’t read data from another.

← Previous Microsoft 365 Lighthouse Integration
Next → Teams Integration: Adaptive Cards for MSP Workflows