Secure Storage and Transmission

Secure Storage and Transmission

API keys require careful handling throughout their lifecycle to prevent exposure. Keys should never be stored in plain text, whether in databases, configuration files, or code repositories. Even hashed storage requires careful implementation to prevent rainbow table attacks or timing attacks during comparison.

// Node.js secure API key storage and handling
const crypto = require('crypto');
const bcrypt = require('bcrypt');

class SecureAPIKeyStore {
    constructor(encryptionKey) {
        // Derive encryption key using PBKDF2
        this.encryptionKey = crypto.pbkdf2Sync(
            encryptionKey,
            'api-key-encryption-salt',
            100000,
            32,
            'sha256'
        );
    }
    
    async storeAPIKey(userId, apiKey, metadata = {}) {
        // Never store the actual API key
        const keyHash = await this.hashAPIKey(apiKey);
        
        // Encrypt sensitive metadata
        const encryptedMetadata = this.encryptMetadata(metadata);
        
        // Store in database
        const keyRecord = {
            id: crypto.randomUUID(),
            user_id: userId,
            key_hash: keyHash,
            key_prefix: apiKey.substring(0, 8) + '...', // For identification
            encrypted_metadata: encryptedMetadata,
            created_at: new Date(),
            last_used_at: null,
            is_active: true
        };
        
        await db.apiKeys.insert(keyRecord);
        
        return keyRecord.id;
    }
    
    async hashAPIKey(apiKey) {
        // Use bcrypt for secure hashing with salt
        return bcrypt.hash(apiKey, 12);
    }
    
    async verifyAPIKey(apiKey) {
        // Extract key identifier if using prefixed keys
        const keyPrefix = apiKey.substring(0, 8) + '...';
        
        // Find potential matches by prefix
        const candidates = await db.apiKeys.find({
            key_prefix: keyPrefix,
            is_active: true
        });
        
        // Verify against each candidate
        for (const candidate of candidates) {
            const isValid = await bcrypt.compare(apiKey, candidate.key_hash);
            if (isValid) {
                // Update last used timestamp
                await db.apiKeys.update(candidate.id, {
                    last_used_at: new Date()
                });
                
                return {
                    valid: true,
                    userId: candidate.user_id,
                    metadata: this.decryptMetadata(candidate.encrypted_metadata)
                };
            }
        }
        
        return { valid: false };
    }
    
    encryptMetadata(metadata) {
        const iv = crypto.randomBytes(16);
        const cipher = crypto.createCipheriv(
            'aes-256-gcm',
            this.encryptionKey,
            iv
        );
        
        const encrypted = Buffer.concat([
            cipher.update(JSON.stringify(metadata), 'utf8'),
            cipher.final()
        ]);
        
        const authTag = cipher.getAuthTag();
        
        return {
            iv: iv.toString('base64'),
            authTag: authTag.toString('base64'),
            data: encrypted.toString('base64')
        };
    }
    
    decryptMetadata(encryptedData) {
        const decipher = crypto.createDecipheriv(
            'aes-256-gcm',
            this.encryptionKey,
            Buffer.from(encryptedData.iv, 'base64')
        );
        
        decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'base64'));
        
        const decrypted = Buffer.concat([
            decipher.update(Buffer.from(encryptedData.data, 'base64')),
            decipher.final()
        ]);
        
        return JSON.parse(decrypted.toString('utf8'));
    }
}

// Secure key transmission
class APIKeyDelivery {
    static async deliverKeySecurely(userId, apiKey) {
        // Option 1: One-time view link
        const token = crypto.randomBytes(32).toString('hex');
        const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
        
        await db.oneTimeTokens.insert({
            token,
            user_id: userId,
            encrypted_key: await this.encryptForDelivery(apiKey),
            expires_at: expires,
            viewed: false
        });
        
        const viewUrl = `https://api.example.com/keys/view/${token}`;
        
        // Send notification without key
        await emailService.send(userId, {
            subject: 'Your API Key is Ready',
            body: `Your API key has been generated. View it once at: ${viewUrl}`
        });
        
        return viewUrl;
    }
    
    static async retrieveKeyOnce(token) {
        const record = await db.oneTimeTokens.findOne({ 
            token,
            viewed: false,
            expires_at: { $gt: new Date() }
        });
        
        if (!record) {
            throw new Error('Invalid or expired token');
        }
        
        // Mark as viewed
        await db.oneTimeTokens.update(record.id, { viewed: true });
        
        // Decrypt and return key
        return this.decryptDelivery(record.encrypted_key);
    }
}

Configuration management for API keys requires special attention. Never commit API keys to version control, even in encrypted form. Use environment variables for server-side applications, but understand their limitations. Implement secure secret management solutions like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault for production environments.