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.