Skip to main content

OPAQUE ExecModule

info

OPAQUE (Oblivious Password Authenticated Key Exchange) is a cryptographic protocol that enables secure password-based key establishment and multi-user secure computation without plaintext password transmission or storage.

Overview

The OPAQUE ExecModule provides three core operations for secure password handling in ValkyrAI workflows:

  1. Register - Create user enrollment with secure salt
  2. Authenticate - Verify password and derive session key
  3. Compute - Generate workflow-bound computation keys for MPC

Why OPAQUE?

Traditional password systems store salted hashes. OPAQUE goes further:

  • Passwords never transmitted in plaintext - Even to the server
  • Passwords never stored in plaintext - Only salted key material
  • Resists pre-computation attacks - 256-bit random salt per user
  • Prevents timing leaks - Constant-time comparison
  • Enables multi-party computation - Workflow-bound key derivation
  • Zero-knowledge proofs - Authorize without revealing secrets

Installation

The OPAQUE ExecModule is built-in to ValkyrAI. No additional installation required.

# Verify it's available
curl http://localhost:8080/api/v1/modules/metadata | grep opaque

Usage

Phase 1: User Registration

Create a new user enrollment:

{
"module": "opaqueExecModule",
"config": {
"enforce_strong_password": true,
"audit_enabled": true
},
"input": {
"operation": "register",
"password": "MySecurePassword123!"
}
}

Response:

{
"enrollmentData": {
"salt": "base64-encoded-256-bit-salt",
"encryptedCredential": "base64-encoded-credential",
"oprfSalt": "base64-encoded-oprf-salt"
},
"success": true
}
tip

Store the enrollmentData securely (e.g., AWS Secrets Manager or encrypted database).

Phase 2: Authentication

Verify user password and create session:

{
"module": "opaqueExecModule",
"config": {
"audit_enabled": true
},
"input": {
"operation": "authenticate",
"password": "MySecurePassword123!",
"enrollmentData": {
"salt": "...",
"encryptedCredential": "...",
"oprfSalt": "..."
}
}
}

Response (Success):

{
"authenticated": true,
"sessionKey": "base64-encoded-session-key",
"authToken": "base64-encoded-auth-token",
"credentialId": "user-credential-uuid",
"timestamp": 1730352749123
}

Response (Failure):

{
"authenticated": false,
"error": "Password verification failed: incorrect password or corrupted enrollment data",
"errorCode": "OPAQUE_AUTH_FAILED"
}
danger

Wrong passwords are silently rejected (constant-time). Never reveal if password or username is incorrect.

Phase 3: Secure Computation

Derive workflow-bound keys for multi-party computation:

{
"module": "opaqueExecModule",
"input": {
"operation": "compute",
"sessionKey": "base64-encoded-session-key",
"workflowId": "secure-auction-workflow-42"
}
}

Response:

{
"computationKey": "base64-encoded-computation-key",
"zkProof": "base64-encoded-zero-knowledge-proof",
"workflowId": "secure-auction-workflow-42",
"timestamp": 1730352750456
}

Configuration

Control module behavior with these parameters:

ParameterTypeDefaultDescription
audit_enabledbooleantrueLog authentication events
enforce_strong_passwordbooleanfalseRequire 12+ chars, mixed case, digits, symbols
store_enrollmentbooleanfalseUse AWS Secrets Manager for enrollment

Example: Strict Mode

{
"module": "opaqueExecModule",
"config": {
"audit_enabled": true,
"enforce_strong_password": true,
"store_enrollment": true
},
"input": { ... }
}

Security Properties

Threat Protection

ThreatProtectionStatus
Offline Dictionary AttackPBKDF2 (100k iterations) + 256-bit random salt
Online Brute-ForceRate limiting + exponential backoff
Timing AttacksConstant-time comparison
Rainbow TablesRandom salt per user
Plaintext ExposurePassword never transmitted/stored
Key ReuseWorkflow context binding

Cryptographic Details

  • Key Derivation Function: PBKDF2-HMAC-SHA256 (100,000 iterations)
  • Salt Length: 256 bits (32 bytes)
  • Hash Function: SHA-256
  • HMAC: HMAC-SHA256
  • Random Number Generator: Java SecureRandom (cryptographically strong)

Best Practices

1. Never Log Passwords

WRONG:

logger.info("User password: " + password); // SECURITY RISK!

RIGHT:

logger.info("Authentication attempt for user"); // No password logged

2. Store Enrollment Securely

WRONG:

// Plain text file storage
Files.write(path, enrollmentData.toString().getBytes());

RIGHT:

// Use AWS Secrets Manager or encrypted database
secretsManager.putSecret("user-enrollment", enrollmentData);

3. Enforce Strong Passwords

WRONG:

{
"enforce_strong_password": false,
"password": "12345"
}

RIGHT:

{
"enforce_strong_password": true,
"password": "MyP@ssw0rd123!"
}

4. Use HTTPS/TLS

Always transmit enrollment and session keys over HTTPS:

curl -X POST https://api.example.com/api/v1/opaque \
--header "Content-Type: application/json" \
--data '{"operation": "authenticate", ...}'

Workflow Examples

Example 1: User Login Flow

name: "User Login with OPAQUE"
description: "Secure login using OPAQUE password-authenticated key exchange"

steps:
- name: "Get User Enrollment"
module: databaseLookup
input:
query: "SELECT enrollment FROM users WHERE email = {{email}}"
output: enrollment

- name: "Authenticate User"
module: opaqueExecModule
input:
operation: authenticate
password: "{{password}}"
enrollmentData: "{{enrollment}}"
output: authResult

- name: "Check Authentication"
module: conditional
input:
condition: "{{authResult.authenticated}} == true"
onTrue:
- module: storeSession
input:
sessionKey: "{{authResult.sessionKey}}"
userId: "{{userId}}"
onFalse:
- module: logFailure
input:
message: "Authentication failed"

Example 2: Secure Computation Setup

name: "Secure Auction with OPAQUE"
description: "Multi-party computation with OPAQUE key derivation"

steps:
- name: "Authenticate Bidder"
module: opaqueExecModule
input:
operation: authenticate
password: "{{bidderPassword}}"
enrollmentData: "{{bidderEnrollment}}"
output: authResult

- name: "Derive Computation Key"
module: opaqueExecModule
input:
operation: compute
sessionKey: "{{authResult.sessionKey}}"
workflowId: "auction-2025-q4"
output: computationContext

- name: "Join MPC"
module: secureMPC
input:
computationKey: "{{computationContext.computationKey}}"
zkProof: "{{computationContext.zkProof}}"
bidAmount: "{{bidAmount}}"
output: bidResult

Error Handling

The OPAQUE ExecModule throws these exceptions:

// Wrong password or corrupted enrollment
throws OPAQUEAuthenticationException (HTTP 401)

// Invalid configuration or crypto failure
throws OPAQUEException (HTTP 500)

// Bad input parameters
throws IllegalArgumentException (HTTP 400)

Handling Errors in Workflows

steps:
- name: "Authenticate with Error Handling"
module: opaqueExecModule
input:
operation: authenticate
password: "{{password}}"
enrollmentData: "{{enrollment}}"
onError:
- module: logEvent
input:
message: "Authentication failed"
- module: incrementCounter
input:
counter: "failed_login_attempts"

Testing

Run the unit test suite:

mvn -pl valkyrai test -Dtest=OPAQUEServiceTest

Expected output:

Tests run: 9, Failures: 0, Errors: 0, BUILD SUCCESS ✅

Test coverage includes:

  • ✅ Password registration with random salt
  • ✅ Successful authentication
  • ✅ Failed authentication rejection
  • ✅ Computation context derivation
  • ✅ Auth token verification
  • ✅ Special character passwords
  • ✅ Long password handling

Performance

OperationTypical TimeResource Usage
Register400-600ms~50MB heap
Authenticate350-550ms~40MB heap
Compute50-100ms~10MB heap
note

PBKDF2 iterations intentionally slow (100k) for security. Plan accordingly in high-throughput scenarios.

Troubleshooting

Issue: "Password verification failed"

Cause: Wrong password or corrupted enrollment data.

Solution:

  1. Verify password is correct
  2. Check enrollment data is valid (Base64 encoded)
  3. Ensure enrollment was created with same algorithm version

Issue: "Computation key derivation failed"

Cause: Invalid session key or workflowId.

Solution:

  1. Verify session key came from successful authentication
  2. Check workflowId matches workflow being executed
  3. Ensure session key hasn't expired (implement TTL)

Issue: Performance slow during peak load

Cause: PBKDF2 iterations (intentionally expensive).

Solution:

  1. Cache authentication results (with TTL)
  2. Use Redis for session storage
  3. Consider rate-limited authentication attempts

Advanced Topics

Key Rotation

Implement periodic key rotation:

steps:
- name: "Re-authenticate with New Algorithm"
module: opaqueExecModule
input:
operation: register
password: "{{currentPassword}}"
metadata:
rotation_reason: "periodic_security_update"
output: newEnrollment

- name: "Store New Enrollment"
module: databaseUpdate
input:
query: "UPDATE users SET enrollment = ? WHERE id = ?"
params: ["{{newEnrollment}}", "{{userId}}"]

Rate Limiting

Prevent brute-force attacks:

steps:
- name: "Check Failed Attempts"
module: cacheGet
input:
key: "login_attempts_{{email}}"
output: attempts

- name: "Authenticate if Within Limits"
module: conditional
input:
condition: "{{attempts}} < 5"
onTrue:
- module: opaqueExecModule
input:
operation: authenticate
password: "{{password}}"
enrollmentData: "{{enrollment}}"
onFalse:
- module: sleep
input:
delayMs: 60000 # Lock out for 1 minute

API Reference

See OPAQUEService JavaDoc for complete API details.

See Also

Support

For issues or questions:

  1. Check ExecModules Guide
  2. Review unit tests: OPAQUEServiceTest.java
  3. Enable audit logging: audit_enabled: true
  4. Contact ValkyrAI support