Imported from mgagp/ezkey (
ezkey-crypto-api/AGENTS.md). Install upstream withnpx skills add mgagp/ezkey --skill ezkey-crypto-api. Copyright stays with the author.
Ezkey Crypto API – Agent Notes
This file is UTF-8 without BOM.
🎯 Purpose and Role
Crypto API is a testing and debugging tool that provides cryptographic primitives as REST endpoints. It enables:
- Testing: Generate keys, sign data, validate signatures, and encrypt/decrypt values for end-to-end testing flows
- Debugging: Decrypt encrypted database columns to investigate issues during development
- Integration: Support tools like Bruno that don't have built-in cryptographic capabilities
⚠️ CRITICAL: This API is NOT for production use. It has no authentication, exposes sensitive operations, and should only be used in secure, isolated testing environments.
🐳 Docker Integration
Crypto API is included in the Docker stack and starts automatically with ./clean-start.sh:
- Port:
9090(Auth API direct is8080;8085is HAProxy Auth stats — seedocs/LOCAL_STACK_PORTS.md) - Container:
ezkey-crypto-api - Base URL:
http://localhost:9090 - Swagger UI:
http://localhost:9090/swagger-ui/index.html
Quick Check:
# Verify Crypto API is running
docker ps | grep crypto-api
# Check logs
docker logs ezkey-crypto-api --tail 50
# Test health
curl http://localhost:9090/actuator/health
🔑 Core Endpoints
1. Proof Token Generation
GET /api/v1/crypto/prooftoken
Generates cryptographically secure proof tokens for authentication flows.
Use Case: Generate tokens for enrollment or auth attempt flows when testing.
2. EC P-256 Key Pair Generation
GET /api/v1/crypto/keypair
Generates EC P-256 (secp256r1) key pairs for device simulation.
Use Case: Create device keys for enrollment testing without needing native crypto libraries.
3. Sign Data
POST /api/v1/crypto/sign
Signs data using EC P-256 ECDSA-SHA256 with a private key.
Use Case: Sign proof tokens or challenge data during enrollment/auth flows.
4. Validate Signature
POST /api/v1/crypto/validate
Validates EC P-256 ECDSA-SHA256 signatures.
Use Case: Verify signatures in test assertions or debugging scenarios.
5. Sign Integration Payload (Ed25519)
POST /api/v1/crypto/sign-ed25519
Signs UTF-8 payloads using an Ed25519 PKCS#8 private key and returns a raw 64-byte signature as Base64URL without padding.
Use Case: Generate integration signatures for Pending or RespondResult oracle validation.
6. Verify Integration Signature (Ed25519)
POST /api/v1/crypto/verify-ed25519
Verifies an Ed25519 signature using a raw 32-byte public key encoded as Base64URL without padding.
Use Case: Validate integration signatures from Auth API payloads in Bruno. Experimental Dart
helpers live in ezkey_dart/ only — not a first-class docs/ consumer.
7. Build Canonical EZKey Payload
POST /api/v1/crypto/payload-helper
Builds canonical payload strings using the exact EZKey NFC and separator rules: auth-attempt types
(pending, respond, respond-result) and enrollment types (enrollment-bind,
enrollment-verify-device, enrollment-verify-result), delegating to the same builders as
ezkey-core (docs/ENROLLMENT_SIGNATURE_PAYLOAD.md).
Use Case: Eliminate duplicated payload-building logic in Bruno and serve as a protocol oracle.
Experimental Dart interop stays in ezkey_dart/.
8. Encrypt Plaintext Value
POST /api/v1/crypto/encrypt
⚠️ TESTING TOOL: Encrypts plaintext values and returns them in the standard encrypted format (ENC:keyID:Base64(ciphertext)).
Use Case:
- Generate encrypted test data for testing flows
- Verify encryption/decryption round-trips
- Create test fixtures with encrypted values
Request Format:
{
"plaintext": "my-secret-value"
}
Response Includes:
encryptedValue: Encrypted value (or original plaintext if encryption failed/unavailable)encryptionSuccessful: Whether encryption succeededkeyId: Primary key ID used for encryptionencryptedFormat: Output format (ENC:keyID:Base64 or PLAINTEXT)errorMessage: Error details if encryption failedencryptionAvailable: Whether EncryptionService is initialized
Note: If input is already encrypted (has ENC: prefix), the endpoint detects this and skips re-encryption.
9. Decrypt Encrypted Database Column
POST /api/v1/crypto/decrypt
⚠️ DEBUGGING TOOL: Decrypts encrypted database column values for investigation.
Use Case:
- Investigate encrypted fields:
enrollment_proof_token,auth_attempt_proof_token,integration_private_key - Device proof is hash-only (
device_proof_token_hash); there is no recoverabledevice_proof_tokencolumn (ADR-0007 Tier 0) - Compare decrypted values with trace logs
- Troubleshoot encryption/decryption issues
Request Format:
{
"encryptedValue": "ENC:2865054995:AarFRRPHitKf32X1m/o8j0lJY71IGc1IN9dd65kd/..."
}
Response Includes:
plaintext: Decrypted value (or null if failed)isEncrypted: Whether input had ENC: prefixdecryptionSuccessful: Whether decryption succeededkeyId: Extracted key ID from encrypted valueencryptedFormat: Detected format (ENC:keyID:Base64, ENC:INVALID, or PLAINTEXT)errorMessage: Error details if decryption failedencryptionAvailable: Whether EncryptionService is initialized
🔄 Keyset Management
Keyset Loading Behavior
Keyset is loaded at startup from the configured keyset file (/etc/ezkey/keysets/keyset.json.encrypted in Docker).
Key Rotation Impact
When key rotation occurs in the main application (admin-api or auth-api):
- ✅ Keyset file is updated with new primary key
- ⚠️ Crypto API continues using keyset loaded at startup (old keyset)
- 🔄 Restart Crypto API to load the new keyset
Why Restart is Required:
- Simplicity: Crypto API is a debugging tool - restart ensures predictable behavior
- No database sync: Crypto API uses FILE storage mode only (no database access)
- Predictable state: Guarantees exact keyset from file at startup time
Workflow After Key Rotation:
# 1. Key rotation occurs in admin-api/auth-api
# 2. Keyset file is updated
# 3. Restart Crypto API
docker restart ezkey-crypto-api
# 4. Crypto API now uses new keyset
Decryption Capabilities
Crypto API can decrypt values encrypted with:
- ✅ Current keyset: Keys loaded at startup
- ✅ Previous keysets: Tink supports multiple keys - old keys remain available until removed from keyset
- ❌ Keys not in keyset: Decryption fails if key was removed from keyset file
💡 Common Workflows
Complete Enrollment Flow
Do not sign the raw enrollmentProofToken. Device ECDSA covers the canonical verify-device
string (docs/ENROLLMENT_SIGNATURE_PAYLOAD.md). Exploratory chain: bruno/enrollments-auth/
(payload-helper → verify Ed25519 bind → device keypair → payload-helper verify-device → sign that
payload → Auth API verify → payload-helper verify-result → verify Ed25519). Crypto API is the
oracle (enrollment-bind, enrollment-verify-device, enrollment-verify-result); Bruno is the
source of truth, not postman/.
Testing Encryption/Decryption Round-trip
-
Encrypt a test value:
curl -X POST http://localhost:9090/api/v1/crypto/encrypt \ -H "Content-Type: application/json" \ -d '{"plaintext": "test-secret-value"}' -
Copy
encryptedValuefrom response -
Decrypt to verify round-trip:
curl -X POST http://localhost:9090/api/v1/crypto/decrypt \ -H "Content-Type: application/json" \ -d '{"encryptedValue": "ENC:2865054995:AarFRRPHitKf32X1m/..."}' -
Verify
plaintextmatches original value
Debugging Encrypted Columns
-
Query database for encrypted value:
SELECT enrollment_proof_token FROM ezkey_enrollment WHERE enrollment_id = 123; -
Copy encrypted value (format:
ENC:keyID:Base64...) -
Decrypt via API:
curl -X POST http://localhost:9090/api/v1/crypto/decrypt \ -H "Content-Type: application/json" \ -d '{"encryptedValue": "ENC:2865054995:AarFRRPHitKf32X1m/..."}' -
Compare
plaintextwith trace logs or expected values -
Use metadata fields (
keyId,encryptedFormat,errorMessage) to troubleshoot
⚠️ Watchouts
Port Configuration
- ✅ Crypto API: Port
9090 - ✅ Auth API (direct): Port
8080 - ❌ Port 8085: HAProxy Auth stats, not Auth API
- Always verify you're calling the correct port
Security Warnings
- ⚠️ No authentication: API is open - only use in isolated test environments
- ⚠️ Sensitive operations: Decrypt endpoint returns plaintext values
- ⚠️ Private keys in requests: Keys are transmitted in API calls
- ⚠️ Never expose in production: This is a testing/debugging tool only
Keyset Synchronization
- ⚠️ Restart required after key rotation: Crypto API doesn't auto-reload keyset
- ⚠️ File-based only: No database sync - uses keyset file only
- ⚠️ Startup keyset: Uses keyset loaded at startup, not current file state
Error Handling
- 200 OK doesn't mean success: Check
decryptionSuccessfulfield in decrypt response - Plaintext can be null: Even with 200 OK if decryption failed
- Check
encryptionAvailable: Verify EncryptionService is initialized
🔍 Investigation Protocol
When Decryption Fails
- Check
encryptionAvailable: Should betrue - Verify
encryptedFormat: Should beENC:keyID:Base64, notENC:INVALIDorPLAINTEXT - Check
keyId: Verify key exists in current keyset - Review
errorMessage: Provides specific failure reason - Verify keyset: Check if Crypto API was restarted after key rotation
- Check logs:
docker logs ezkey-crypto-apifor initialization errors
When API is Unavailable
- Verify container is running:
docker ps | grep crypto-api - Check health endpoint:
curl http://localhost:9090/actuator/health - Review startup logs:
docker logs ezkey-crypto-api --tail 100 - Check port mapping: Ensure port 9090 is accessible
- Verify volume mounts: Encryption secrets volume should be mounted
📚 Documentation References
Primary Documentation
- README.md - Complete API documentation with all endpoints, examples, and usage patterns
- docs/ENDPOINT.md - Main API endpoint documentation
Related Documentation
- ezkey-core/README.md - Core encryption service documentation
🎯 Best Practices
For Testing
- Use Crypto API to generate test data (keys, tokens, signatures)
- Keep test flows independent - don't rely on shared state
- Use unique identifiers to avoid conflicts
- Prefer direct API calls over complex crypto libraries in tests
For Debugging
- Always verify
decryptionSuccessfulfield, not just HTTP status - Use metadata fields (
keyId,encryptedFormat) to understand encryption state - Compare decrypted values with trace logs for verification
- Check
encryptionAvailablebefore troubleshooting decryption failures
For Integration
- Crypto API is stateless - each request is independent
- No session management - no need to maintain state between calls
- Fast operations - suitable for high-frequency test scenarios
- Docker integration - always available in test stack
Next Steps
- Verify Crypto API is accessible on port 9090 before running tests
- Use decrypt endpoint for investigating encrypted column issues
- Remember to restart Crypto API after key rotations
- Keep security warnings in mind - never expose in production
