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:

  1. Key Generation - Your agent generates an RSA key pair (public and private keys)
  2. Public Key Sharing - The public key is sent with each OTP request
  3. Encryption on Capture - When an OTP is captured, it's immediately encrypted using the public key
  4. Decryption by Agent - Only the agent with the private key can decrypt the OTP

Encryption Algorithm

Agent OTP uses the following cryptographic parameters:

AlgorithmRSA-OAEP
Key Size2048 bits
Hash FunctionSHA-256
Key FormatSPKI (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:

  1. Generate a new key pair
  2. Update your secrets storage with the new private key
  3. Update the public key used in OTP requests
  4. 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);

See Also