Skip to content

Secure Password Encryption

briansemify edited this page Oct 24, 2025 · 2 revisions

Secure Password Encryption for Third-Party Integration

This document outlines the recommended approach for securely encrypting passwords when integrating with the Semify API from third-party applications.

Overview

The Semify API supports secure password encryption using a hybrid RSA + AES approach, which provides maximum security while maintaining performance. This method ensures that passwords are never transmitted in plain text and can only be decrypted by the intended recipient.

Architecture

Key Components

  • RSA Key Pair: Public/private key pair for secure key exchange
  • AES Encryption: Symmetric encryption for actual password data
  • JWT Tokens: Optional additional layer for validation and auditing
  • Rate Limiting: Protection against brute force attacks

Process Flow

  1. Third party requests public key from Semify API
  2. Third party generates AES key and encrypts password
  3. Third party encrypts AES key with Semify's public key
  4. Third party sends encrypted data to Semify API
  5. Semify decrypts AES key with private key
  6. Semify decrypts password with AES key

API Endpoints

Get Public Key

  • Endpoint: GET /api/v1/accounts/iam/public-key
  • Description: Retrieves the current RSA public key for encryption
  • Response: Public key in PEM format
  • Rate Limit: 100 requests per hour per client

Submit Encrypted Password

  • Endpoint: POST /api/v1/accounts/iam/credentials
  • Description: Submits encrypted password data for decryption
  • Authentication: Bearer JWT token required
  • Rate Limit: 10 requests per minute per client

Encryption Process

Step 1: Generate AES Key and IV

// Generate random AES key (256-bit)
const aesKey = crypto.randomBytes(32);

// Generate random IV (128-bit)
const iv = crypto.randomBytes(16);

Step 2: Encrypt Password with AES

// Encrypt password using AES-256-CBC
const cipher = crypto.createCipher('aes-256-cbc', aesKey);
cipher.setAutoPadding(true);

let encryptedPassword = cipher.update(password, 'utf8', 'base64');
encryptedPassword += cipher.final('base64');

Step 3: Encrypt AES Key with RSA

// Encrypt AES key with RSA public key
const encryptedAesKey = crypto.publicEncrypt({
    key: publicKey,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
}, aesKey);

Step 4: Prepare Payload

const payload = {
    encryptedPassword: encryptedPassword,
    encryptedAesKey: encryptedAesKey.toString('base64'),
    iv: iv.toString('base64'),
    timestamp: new Date().toISOString(),
    clientId: 'your-client-id'
};

Request/Response Examples

Get Public Key Request

curl -H "Authorization: Bearer $TOKEN" \
     "https://uat.services.semify.com/api/v1/accounts/iam/public-key"

Get Public Key Response

{
  "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----",
  "keyId": "rsa-key-2024-01",
  "algorithm": "RSA-OAEP",
  "expiresAt": "2024-02-01T00:00:00Z"
}

Submit Encrypted Password Request

curl -X POST \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
       "encryptedPassword": "base64-encoded-aes-encrypted-password",
       "encryptedAesKey": "base64-encoded-rsa-encrypted-aes-key",
       "iv": "base64-encoded-iv",
       "timestamp": "2024-01-15T10:30:00Z",
       "clientId": "client-12345"
     }' \
     "https://uat.services.semify.com/api/v1/accounts/iam/credentials"

Submit Encrypted Password Response

{
  "success": true,
  "message": "Password decrypted and processed successfully",
  "processedAt": "2024-01-15T10:30:01Z",
  "clientId": "client-12345"
}

Error Handling

Common Error Responses

{
  "error": true,
  "errorCode": "DECRYPTION_FAILED",
  "message": "Failed to decrypt password. Check encryption parameters.",
  "timestamp": "2024-01-15T10:30:00Z"
}

Error Codes

  • INVALID_PUBLIC_KEY: Public key format is invalid
  • DECRYPTION_FAILED: Password decryption failed
  • RATE_LIMIT_EXCEEDED: Too many requests
  • INVALID_CLIENT_ID: Client ID not recognized
  • EXPIRED_TIMESTAMP: Request timestamp is too old (>5 minutes)

Security Considerations

Key Management

  • Key Rotation: RSA keys are rotated monthly
  • Key Storage: Private keys are stored securely using HSM or encrypted storage
  • Key Distribution: Public keys are distributed via secure API endpoint

Encryption Standards

  • RSA: 2048-bit minimum, OAEP padding
  • AES: 256-bit key, CBC mode
  • IV: Random 128-bit initialization vector
  • Padding: PKCS7 padding for AES

Rate Limiting

  • Public Key Requests: 100 per hour per client
  • Password Submissions: 10 per minute per client
  • Burst Protection: Maximum 5 requests in 10 seconds

Validation

  • Timestamp Validation: Requests older than 5 minutes are rejected
  • Client ID Validation: Only registered clients can submit passwords
  • Payload Size: Maximum 10KB per request

Implementation Guidelines

For Third-Party Developers

1. Key Retrieval

async function getPublicKey() {
    const response = await fetch('https://uat.services.semify.com/api/v1/accounts/iam/public-key', {
        headers: {
            'Authorization': `Bearer ${token}`
        }
    });
    
    const data = await response.json();
    return data.publicKey;
}

2. Password Encryption

async function encryptPassword(password, publicKey) {
    // Generate AES key and IV
    const aesKey = crypto.randomBytes(32);
    const iv = crypto.randomBytes(16);
    
    // Encrypt password with AES
    const cipher = crypto.createCipher('aes-256-cbc', aesKey);
    let encryptedPassword = cipher.update(password, 'utf8', 'base64');
    encryptedPassword += cipher.final('base64');
    
    // Encrypt AES key with RSA
    const encryptedAesKey = crypto.publicEncrypt({
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
    }, aesKey);
    
    return {
        encryptedPassword,
        encryptedAesKey: encryptedAesKey.toString('base64'),
        iv: iv.toString('base64'),
        timestamp: new Date().toISOString()
    };
}

3. Submit Encrypted Password

async function submitEncryptedPassword(encryptedData, clientId) {
    const response = await fetch('https://uat.services.semify.com/api/v1/accounts/iam/credentials', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            ...encryptedData,
            clientId: clientId
        })
    });
    
    return await response.json();
}

Best Practices

Security

  • Never store private keys in client applications
  • Use HTTPS only for all API communications
  • Validate certificates to prevent man-in-the-middle attacks
  • Implement proper error handling without exposing sensitive information

Performance

  • Cache public keys for up to 1 hour to reduce API calls
  • Implement retry logic with exponential backoff
  • Use connection pooling for high-volume applications

Monitoring

  • Log encryption attempts (without sensitive data)
  • Monitor rate limit usage to avoid service disruption
  • Track error rates to identify potential issues

Testing

Test Environment

  • Base URL: https://uat.services.semify.com
  • Test Client ID: test-client-12345
  • Test Password: TestPassword123!

Test Scenarios

  1. Successful encryption/decryption
  2. Invalid public key handling
  3. Rate limit enforcement
  4. Expired timestamp rejection
  5. Malformed payload handling

Support

For technical support or questions about password encryption:

Changelog

Version 1.0 (2024-01-15)

  • Initial release with RSA + AES hybrid encryption
  • Support for public key distribution
  • Rate limiting and validation
  • Comprehensive error handling

Clone this wiki locally