Phase 03 — Cryptography Layer
Implement all cryptographic primitives so license keys are signed, stored encrypted, and device fingerprints are anonymized before touching the database.
What was built
Section titled “What was built”src/Crypto/KeyVault.php— Encrypts/decrypts license key strings with AES-256-GCM (prefers hardware-acceleratedsodium_crypto_aead_aes256gcm_*; falls back to XChaCha20-Poly1305). Nonce prepended to ciphertext, whole blob stored as base64.src/Crypto/Signer.php— Signs arbitrary payloads with Ed25519 (sodium_crypto_sign_detached). Output format:base64url(json_payload).base64url(signature). Clients split on.and verify without calling the server.src/Crypto/Fingerprint.php— HMAC-SHA256 anonymization of device fingerprints. Also provides a staticsha256()helper for the license hash column.
Public APIs introduced
Section titled “Public APIs introduced”| Method | Description |
|---|---|
KeyVault::encrypt(string $plaintext): string | Encrypt a license key for storage |
KeyVault::decrypt(string $ciphertext): string | Decrypt a stored license key |
Signer::sign(array $payload): string | Sign a payload, return compact token |
Signer::verify(string $token): ?array | Verify and decode a signed token |
Signer::get_public_key_base64(): string | Return the public key for REST exposure |
Fingerprint::hash(string $raw): string | HMAC-SHA256 a device fingerprint |
Fingerprint::sha256(string $data): string | Plain SHA-256 hash |
Signed token format
Section titled “Signed token format”eyJrZXkiOiJBQkNELUVGR0gtSUpLTC1NTk9QIn0.dD3...└──────────────────────────────────────── └──────── base64url(json_payload) base64url(Ed25519 sig)Clients verify offline:
$parts = explode('.', $token, 2);$valid = sodium_crypto_sign_verify_detached( WPLM\Crypto\Signer::base64url_decode($parts[1]), $parts[0], base64_decode($public_key));How to test
Section titled “How to test”# 1. Activate the plugin (Seeder generates keypair + encryption key).# 2. Create a license via the UI or REST.# 3. Check the DB: license_key column must be ciphertext, hash must be hex.# SELECT license_key, hash FROM wp_wplm_licenses LIMIT 1;
# PHPUnit:# vendor/bin/phpunit --filter CryptoTestAcceptance criteria covered
Section titled “Acceptance criteria covered”- Key issue + sign —
wplm_create_license()returns a key whose signature verifies with the public key. - Encrypt at rest —
license_keycolumn is ciphertext; plaintext never persisted.