End-to-End Encryption
Agent OTP uses end-to-end encryption to ensure that only your agent can read verification codes. The relay service never sees plaintext OTPs.
How It Works
Agent OTP uses asymmetric encryption (RSA-OAEP) for secure OTP relay:
- Key Generation - Your agent generates an RSA key pair (public and private keys)
- Public Key Sharing - The public key is sent with each OTP request
- Encryption on Capture - When an OTP is captured, it's immediately encrypted using the public key
- Decryption by Agent - Only the agent with the private key can decrypt the OTP
Encryption Algorithm
Agent OTP uses the following cryptographic parameters:
| Algorithm | RSA-OAEP |
| Key Size | 2048 bits |
| Hash Function | SHA-256 |
| Key Format | SPKI (public), PKCS8 (private) |
Key Management
Generating Keys
Use the SDK to generate a key pair. This should be done once when setting up your agent:
import {
generateKeyPair,
exportPublicKey,
exportPrivateKey,
} from '@orrisai/agent-otp-sdk';
// Generate a new key pair
const { publicKey, privateKey } = await generateKeyPair();
// Export keys to base64 strings for storage
const publicKeyBase64 = await exportPublicKey(publicKey);
const privateKeyBase64 = await exportPrivateKey(privateKey);
// Store these securely
console.log('Public Key:', publicKeyBase64); // Can be shared
console.log('Private Key:', privateKeyBase64); // Keep secret!Storing Keys Securely
Critical: Your private key must be stored securely. Anyone with access to the private key can decrypt your OTPs.
Recommended storage options:
- Environment variables - Simple for single-agent deployments
- Secrets managers - AWS Secrets Manager, HashiCorp Vault, Google Secret Manager
- Hardware security modules - For high-security environments
Never store private keys in:
- Source code repositories
- Configuration files committed to version control
- Client-side code or browser storage
- Logs or debug output
Loading Keys at Runtime
import { importPrivateKey } from '@orrisai/agent-otp-sdk';
// Load from environment variable
const privateKey = await importPrivateKey(
process.env.AGENT_PRIVATE_KEY!
);
// Use for consuming OTPs
const { code } = await client.consumeOTP(requestId, privateKey);Key Rotation
While keys don't expire, you may want to rotate them periodically or if you suspect compromise:
- Generate a new key pair
- Update your secrets storage with the new private key
- Update the public key used in OTP requests
- Old requests will still use the old key until consumed
What Gets Encrypted
The following data is encrypted with your public key:
- The OTP code itself (e.g., "123456")
- The full message content (optional, if requested)
The following metadata is NOT encrypted (used for filtering/matching):
- Sender information (email address, phone number)
- Timestamp of receipt
- OTP source (SMS, email)
Security Guarantees
What E2E Encryption Protects Against
- Relay service compromise - Even if Agent OTP servers are breached, encrypted OTPs cannot be read
- Man-in-the-middle attacks - Intercepted encrypted payloads are useless without the private key
- Insider threats - Agent OTP staff cannot read your OTPs
What E2E Encryption Does NOT Protect Against
- Private key compromise - If your private key is stolen, the attacker can decrypt your OTPs
- Agent compromise - If your agent is compromised after decryption, the OTP is exposed
- Capture source compromise - If your SMS/email is compromised before capture, OTPs may be intercepted there
Technical Details
Encryption Process
// Simplified view of the encryption process (server-side)
// 1. OTP is captured from source
const otpCode = '123456';
// 2. Convert to bytes
const encoder = new TextEncoder();
const data = encoder.encode(otpCode);
// 3. Encrypt with agent's public key
const encrypted = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
agentPublicKey,
data
);
// 4. Store encrypted payload (base64)
const encryptedBase64 = btoa(String.fromCharCode(...new Uint8Array(encrypted)));Decryption Process
// Simplified view of the decryption process (SDK)
// 1. Fetch encrypted payload from server
const encryptedBase64 = response.encryptedPayload;
// 2. Decode from base64
const encryptedBytes = Uint8Array.from(
atob(encryptedBase64),
c => c.charCodeAt(0)
);
// 3. Decrypt with private key
const decrypted = await crypto.subtle.decrypt(
{ name: 'RSA-OAEP' },
privateKey,
encryptedBytes
);
// 4. Decode to string
const decoder = new TextDecoder();
const otpCode = decoder.decode(decrypted);