Backend Security Fundamentals - 2/3
From Input Protection to Cryptographic Defense
You’ve mastered the OWASP Top 10, implemented bulletproof input validation, and built XSS and CSRF protection that stops attackers at your application’s boundaries. Your endpoints reject malicious requests before they can reach your core business logic. But here’s the cryptographic reality that separates secure applications from security theater: even with perfect input validation, your application is only as secure as how it handles the most sensitive data—passwords, personal information, and communications between client and server.
The cryptographic weakness that destroys trust:
// Your perfectly validated, SQL injection-proof login system
const registerUser = async (req, res) => {
const validatedData = validateUserRegistration(req.body); // Perfect validation
try {
// ❌ The security disaster hiding in plain sight
const newUser = await db.users.create({
email: validatedData.email,
username: validatedData.username,
password: validatedData.password, // Stored in plaintext
createdAt: new Date(),
});
res.status(201).json({
success: true,
message: "Account created successfully",
});
// When your database gets breached:
// - Every user password is immediately exposed
// - Users who reuse passwords lose accounts across multiple services
// - Your company faces massive lawsuits and regulatory fines
// - Brand reputation destroyed permanently
} catch (error) {
res.status(500).json({ error: "Registration failed" });
}
};
The uncomfortable cryptographic truth: Attackers don’t need to bypass your input validation if they can compromise your database and find passwords stored in plaintext, or if they can intercept unencrypted communications between your users and servers. Modern applications require cryptographic security that protects data even when other security layers fail.
Real-world password breach consequences:
// What happens when password security fails:
const passwordBreachImpact = {
databaseCompromise: {
plaintextPasswords: "23 million users exposed instantly",
passwordReuse: "Users lose accounts on 15+ other services",
accountTakeovers: "Attackers access banking, email, social media",
legalConsequences: "Class action lawsuits, GDPR fines up to 4% revenue",
},
inadequateHashing: {
md5Cracked: "Rainbow tables break hashes in seconds",
sha1Cracked: "GPU farms crack billions of hashes per second",
noSaltHashing: "Common passwords exposed across all users",
fastHashing: "Brute force attacks succeed in hours",
},
// The most secure application logic means nothing
// when cryptographic foundations are weak
};
Cryptographic security mastery requires understanding:
- Password hashing algorithms (bcrypt, scrypt, Argon2) that resist brute force attacks
- Encryption vs. hashing fundamentals and when to use each approach
- SSL/TLS implementation that secures all client-server communications
- Certificate management and public key infrastructure (PKI)
- Cryptographic best practices that remain secure as computing power increases
This article elevates your security knowledge from input protection to cryptographic defense. You’ll implement password security that survives database breaches, encryption that protects sensitive data at rest, and SSL/TLS configurations that secure communications against sophisticated attacks.
Password Security: The Cryptographic Foundation
Understanding Password Hashing vs Encryption
The fundamental difference that determines security:
// ❌ Common cryptographic mistakes that seem secure
const crypto = require("crypto");
class InsecurePasswordManager {
// MISTAKE 1: Using encryption instead of hashing
encryptPassword(password) {
const algorithm = "aes-256-cbc";
const key = process.env.ENCRYPTION_KEY;
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(algorithm, key);
let encrypted = cipher.update(password, "utf8", "hex");
encrypted += cipher.final("hex");
return encrypted;
// Problem: Encryption is reversible
// If encryption key is compromised, all passwords are exposed
// Encryption is for data you need to decrypt later
}
// MISTAKE 2: Using fast hashing algorithms
hashPasswordMD5(password) {
return crypto.createHash("md5").update(password).digest("hex");
// Problem: MD5 is cryptographically broken
// Modern GPUs can compute billions of MD5 hashes per second
// Rainbow tables make common passwords instantly crackable
}
hashPasswordSHA256(password) {
return crypto.createHash("sha256").update(password).digest("hex");
// Problem: SHA-256 is too fast
// Designed for data integrity, not password security
// No built-in salt means identical passwords have identical hashes
}
// MISTAKE 3: Using weak salts or no salts
hashPasswordWithWeakSalt(password) {
const salt = "12345"; // Static salt
return crypto
.createHash("sha256")
.update(password + salt)
.digest("hex");
// Problem: Same salt for all passwords
// Attackers can pre-compute rainbow tables for your salt
// Dictionary attacks still work against common passwords
}
}
Professional password hashing with bcrypt:
// ✅ Cryptographically secure password management
const bcrypt = require("bcrypt");
const crypto = require("crypto");
const zxcvbn = require("zxcvbn"); // Password strength estimation
class SecurePasswordManager {
constructor() {
// Cost factor for bcrypt (2^12 = 4096 iterations)
// Increase this as hardware becomes faster
this.saltRounds = 12;
// For highly sensitive applications, consider higher values
// this.saltRounds = 15; // Takes ~3 seconds on modern hardware
}
async hashPassword(password) {
try {
// Validate password strength first
const strength = this.validatePasswordStrength(password);
if (!strength.isStrong) {
throw new PasswordError(
"Password does not meet security requirements",
{
suggestions: strength.suggestions,
score: strength.score,
}
);
}
// Generate salt and hash password
// bcrypt automatically generates a unique salt for each password
const saltedHash = await bcrypt.hash(password, this.saltRounds);
return {
hash: saltedHash,
algorithm: "bcrypt",
saltRounds: this.saltRounds,
hashedAt: new Date(),
};
} catch (error) {
console.error("Password hashing failed:", error);
throw error;
}
}
async verifyPassword(plainPassword, hashedPassword) {
try {
// bcrypt automatically extracts the salt from the hash
const isValid = await bcrypt.compare(plainPassword, hashedPassword);
return isValid;
} catch (error) {
console.error("Password verification failed:", error);
return false;
}
}
validatePasswordStrength(password) {
// Use zxcvbn for sophisticated password strength analysis
const result = zxcvbn(password);
const requirements = {
minLength: password.length >= 12,
hasUppercase: /[A-Z]/.test(password),
hasLowercase: /[a-z]/.test(password),
hasNumbers: /\d/.test(password),
hasSpecialChars: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password),
notCommonPassword: result.score >= 3, // zxcvbn score 0-4
noSequentialChars: !/(.)\1\1/.test(password), // No 3+ repeated characters
noKeyboardPatterns: !/(qwerty|asdf|123456|password)/i.test(password),
};
const failedRequirements = Object.entries(requirements)
.filter(([key, passed]) => !passed)
.map(([key]) => key);
const suggestions = this.generatePasswordSuggestions(
failedRequirements,
result
);
return {
isStrong: failedRequirements.length === 0 && result.score >= 3,
score: result.score,
requirements,
failedRequirements,
suggestions,
crackTime: result.crack_times_display.offline_slow_hashing_1e4_per_second,
};
}
generatePasswordSuggestions(failedRequirements, zxcvbnResult) {
const suggestions = [];
if (failedRequirements.includes("minLength")) {
suggestions.push("Use at least 12 characters");
}
if (failedRequirements.includes("hasUppercase")) {
suggestions.push("Include uppercase letters");
}
if (failedRequirements.includes("hasLowercase")) {
suggestions.push("Include lowercase letters");
}
if (failedRequirements.includes("hasNumbers")) {
suggestions.push("Include numbers");
}
if (failedRequirements.includes("hasSpecialChars")) {
suggestions.push("Include special characters (!@#$%^&*)");
}
if (failedRequirements.includes("notCommonPassword")) {
suggestions.push("Avoid common passwords and dictionary words");
}
if (failedRequirements.includes("noSequentialChars")) {
suggestions.push("Avoid repeating the same character multiple times");
}
// Add zxcvbn-specific suggestions
suggestions.push(...zxcvbnResult.feedback.suggestions);
return suggestions;
}
// Secure random password generation
generateSecurePassword(length = 16) {
const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const lowercase = "abcdefghijklmnopqrstuvwxyz";
const numbers = "0123456789";
const symbols = "!@#$%^&*()_+-=[]{}|;:,.<>?";
const allChars = uppercase + lowercase + numbers + symbols;
let password = "";
// Ensure at least one character from each category
password += uppercase[crypto.randomInt(0, uppercase.length)];
password += lowercase[crypto.randomInt(0, lowercase.length)];
password += numbers[crypto.randomInt(0, numbers.length)];
password += symbols[crypto.randomInt(0, symbols.length)];
// Fill remaining length with random characters
for (let i = 4; i < length; i++) {
password += allChars[crypto.randomInt(0, allChars.length)];
}
// Shuffle the password to avoid predictable patterns
return this.shuffleString(password);
}
shuffleString(str) {
const arr = str.split("");
for (let i = arr.length - 1; i > 0; i--) {
const j = crypto.randomInt(0, i + 1);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr.join("");
}
// Password rotation and security policies
async checkPasswordAge(userPasswordData) {
const now = new Date();
const passwordAge = now - new Date(userPasswordData.hashedAt);
const maxAge = 90 * 24 * 60 * 60 * 1000; // 90 days
return {
needsRotation: passwordAge > maxAge,
ageInDays: Math.floor(passwordAge / (24 * 60 * 60 * 1000)),
daysUntilExpiry: Math.floor(
(maxAge - passwordAge) / (24 * 60 * 60 * 1000)
),
};
}
}
// Integration with user registration
class SecureUserService {
constructor() {
this.passwordManager = new SecurePasswordManager();
}
async registerUser(userData) {
try {
// Validate all input data
const validatedData = this.validateRegistrationData(userData);
// Hash password securely
const passwordData = await this.passwordManager.hashPassword(
validatedData.password
);
// Create user with hashed password
const newUser = await db.users.create({
email: validatedData.email,
username: validatedData.username,
passwordHash: passwordData.hash,
passwordAlgorithm: passwordData.algorithm,
passwordSaltRounds: passwordData.saltRounds,
passwordCreatedAt: passwordData.hashedAt,
emailVerified: false,
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
loginAttempts: 0,
lockoutUntil: null,
});
// Generate email verification token
const verificationToken = await this.generateVerificationToken(
newUser.id
);
// Send verification email (implementation depends on your email service)
await this.sendVerificationEmail(newUser.email, verificationToken);
// Log security event
this.logSecurityEvent({
type: "user_registered",
userId: newUser.id,
email: newUser.email,
timestamp: new Date(),
ipAddress: userData.ipAddress,
userAgent: userData.userAgent,
});
return {
id: newUser.id,
email: newUser.email,
username: newUser.username,
emailVerified: false,
message:
"Account created successfully. Please check your email for verification.",
};
} catch (error) {
if (error instanceof PasswordError) {
throw new ValidationError("Password requirements not met", {
suggestions: error.details.suggestions,
score: error.details.score,
});
}
console.error("User registration failed:", error);
throw new Error("Registration failed");
}
}
async authenticateUser(credentials) {
try {
const { email, password, ipAddress, userAgent } = credentials;
// Find user by email
const user = await db.users.findOne({
email: email.toLowerCase(),
isActive: true,
});
if (!user) {
// Prevent username enumeration by timing attack
await this.passwordManager.hashPassword("dummy_password");
throw new AuthenticationError("Invalid credentials");
}
// Check for account lockout
if (user.lockoutUntil && user.lockoutUntil > new Date()) {
throw new AuthenticationError(
"Account temporarily locked due to too many failed attempts"
);
}
// Verify password
const isValidPassword = await this.passwordManager.verifyPassword(
password,
user.passwordHash
);
if (!isValidPassword) {
// Increment failed login attempts
await this.handleFailedLogin(user.id, ipAddress);
throw new AuthenticationError("Invalid credentials");
}
// Check if password needs rotation
const passwordAge = await this.passwordManager.checkPasswordAge({
hashedAt: user.passwordCreatedAt,
});
// Reset login attempts on successful login
await db.users.updateOne(
{ id: user.id },
{
$unset: { lockoutUntil: 1 },
$set: {
loginAttempts: 0,
lastLoginAt: new Date(),
lastLoginIp: ipAddress,
},
}
);
// Log successful authentication
this.logSecurityEvent({
type: "authentication_success",
userId: user.id,
email: user.email,
timestamp: new Date(),
ipAddress,
userAgent,
passwordNeedsRotation: passwordAge.needsRotation,
});
return {
user: {
id: user.id,
email: user.email,
username: user.username,
emailVerified: user.emailVerified,
},
passwordNeedsRotation: passwordAge.needsRotation,
daysUntilPasswordExpiry: passwordAge.daysUntilExpiry,
};
} catch (error) {
console.error("Authentication failed:", error);
throw error;
}
}
async handleFailedLogin(userId, ipAddress) {
const maxAttempts = 5;
const lockoutDuration = 30 * 60 * 1000; // 30 minutes
const user = await db.users.findOne({ id: userId });
const loginAttempts = (user.loginAttempts || 0) + 1;
const updateData = { loginAttempts };
if (loginAttempts >= maxAttempts) {
updateData.lockoutUntil = new Date(Date.now() + lockoutDuration);
// Log security event for potential brute force attack
this.logSecurityEvent({
type: "account_locked",
userId,
loginAttempts,
timestamp: new Date(),
ipAddress,
severity: "high",
});
}
await db.users.updateOne({ id: userId }, { $set: updateData });
}
}
Advanced Password Hashing with Argon2
Next-generation password hashing:
// ✅ Argon2 - Winner of the Password Hashing Competition
const argon2 = require("argon2");
class Argon2PasswordManager extends SecurePasswordManager {
constructor() {
super();
// Argon2 configuration - tuned for security vs performance
this.argon2Config = {
type: argon2.argon2id, // Most secure variant
memoryCost: 2 ** 16, // 64 MB memory usage
timeCost: 3, // 3 iterations
parallelism: 1, // Single-threaded (increase for multi-core servers)
hashLength: 32, // 32-byte hash output
saltLength: 16, // 16-byte salt
};
}
async hashPassword(password) {
try {
// Validate password strength
const strength = this.validatePasswordStrength(password);
if (!strength.isStrong) {
throw new PasswordError(
"Password does not meet security requirements",
{
suggestions: strength.suggestions,
score: strength.score,
}
);
}
// Hash with Argon2
const hash = await argon2.hash(password, this.argon2Config);
return {
hash,
algorithm: "argon2id",
config: this.argon2Config,
hashedAt: new Date(),
};
} catch (error) {
console.error("Argon2 password hashing failed:", error);
throw error;
}
}
async verifyPassword(plainPassword, hashedPassword) {
try {
return await argon2.verify(hashedPassword, plainPassword);
} catch (error) {
console.error("Argon2 password verification failed:", error);
return false;
}
}
// Performance tuning based on server capabilities
async benchmarkArgon2Performance() {
const testPassword = "test_password_for_benchmarking";
const configs = [
{ memoryCost: 2 ** 14, timeCost: 2, parallelism: 1 }, // Light
{ memoryCost: 2 ** 16, timeCost: 3, parallelism: 1 }, // Medium (recommended)
{ memoryCost: 2 ** 18, timeCost: 4, parallelism: 2 }, // Heavy
];
console.log("Benchmarking Argon2 configurations...");
for (const config of configs) {
const startTime = Date.now();
await argon2.hash(testPassword, { ...config, type: argon2.argon2id });
const duration = Date.now() - startTime;
console.log(
`Config: memory=${config.memoryCost / 1024}KB, time=${
config.timeCost
}, parallel=${config.parallelism} - Duration: ${duration}ms`
);
}
// Aim for 200-500ms on your production hardware
// Increase parameters if hashing is too fast
// Decrease if it's too slow for user experience
}
}
Data Encryption: Protecting Information at Rest
Encryption vs Hashing: When to Use Each
Understanding the cryptographic toolkit:
// ✅ Comprehensive encryption and hashing service
const crypto = require("crypto");
class CryptographicService {
constructor() {
// Encryption keys (store securely in environment variables)
this.encryptionKey = process.env.ENCRYPTION_KEY; // 32-byte key for AES-256
this.signingKey = process.env.SIGNING_KEY; // For HMAC signatures
if (!this.encryptionKey || !this.signingKey) {
throw new Error("Encryption keys not configured");
}
// Encryption algorithm
this.algorithm = "aes-256-gcm"; // Authenticated encryption
}
// Use encryption for data you need to decrypt later
encryptSensitiveData(plaintext) {
try {
const iv = crypto.randomBytes(16); // Random initialization vector
const cipher = crypto.createCipher(this.algorithm, this.encryptionKey);
cipher.setIV(iv);
let encrypted = cipher.update(plaintext, "utf8", "hex");
encrypted += cipher.final("hex");
// Get authentication tag for integrity verification
const authTag = cipher.getAuthTag();
// Combine IV, auth tag, and encrypted data
const result = {
iv: iv.toString("hex"),
authTag: authTag.toString("hex"),
encrypted: encrypted,
algorithm: this.algorithm,
};
return Buffer.from(JSON.stringify(result)).toString("base64");
} catch (error) {
console.error("Encryption failed:", error);
throw new Error("Failed to encrypt data");
}
}
decryptSensitiveData(encryptedData) {
try {
// Parse encrypted data structure
const data = JSON.parse(
Buffer.from(encryptedData, "base64").toString("utf8")
);
const iv = Buffer.from(data.iv, "hex");
const authTag = Buffer.from(data.authTag, "hex");
const encrypted = data.encrypted;
// Create decipher
const decipher = crypto.createDecipher(
this.algorithm,
this.encryptionKey
);
decipher.setIV(iv);
decipher.setAuthTag(authTag);
// Decrypt
let decrypted = decipher.update(encrypted, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
} catch (error) {
console.error("Decryption failed:", error);
throw new Error("Failed to decrypt data");
}
}
// Use hashing for data integrity verification (non-reversible)
createDataHash(data) {
return crypto.createHash("sha256").update(data).digest("hex");
}
// Use HMAC for authenticated hashing (prevents tampering)
createAuthenticatedHash(data) {
return crypto
.createHmac("sha256", this.signingKey)
.update(data)
.digest("hex");
}
verifyAuthenticatedHash(data, expectedHash) {
const actualHash = this.createAuthenticatedHash(data);
return crypto.timingSafeEqual(
Buffer.from(actualHash, "hex"),
Buffer.from(expectedHash, "hex")
);
}
// Secure random token generation
generateSecureToken(length = 32) {
return crypto.randomBytes(length).toString("hex");
}
generateApiKey() {
const prefix = "sk_"; // Identifiable prefix
const randomPart = crypto.randomBytes(32).toString("hex");
return prefix + randomPart;
}
}
// Real-world usage examples
class SecureDataService {
constructor() {
this.crypto = new CryptographicService();
}
// Encrypt personal information (PII)
async storeUserProfile(userId, profileData) {
try {
// Separate sensitive from non-sensitive data
const { ssn, creditCardNumber, medicalInfo, ...publicData } = profileData;
// Encrypt sensitive data
const encryptedData = {};
if (ssn) {
encryptedData.ssn = this.crypto.encryptSensitiveData(ssn);
}
if (creditCardNumber) {
encryptedData.creditCardNumber =
this.crypto.encryptSensitiveData(creditCardNumber);
}
if (medicalInfo) {
encryptedData.medicalInfo = this.crypto.encryptSensitiveData(
JSON.stringify(medicalInfo)
);
}
// Create integrity hash for public data
const publicDataHash = this.crypto.createAuthenticatedHash(
JSON.stringify(publicData)
);
await db.userProfiles.updateOne(
{ userId },
{
$set: {
...publicData,
encryptedData,
dataIntegrityHash: publicDataHash,
updatedAt: new Date(),
},
},
{ upsert: true }
);
return { success: true };
} catch (error) {
console.error("Profile storage failed:", error);
throw new Error("Failed to store profile");
}
}
async getUserProfile(userId) {
try {
const profile = await db.userProfiles.findOne({ userId });
if (!profile) return null;
// Verify data integrity
const { encryptedData, dataIntegrityHash, ...publicData } = profile;
const expectedHash = this.crypto.createAuthenticatedHash(
JSON.stringify(publicData)
);
if (
!crypto.timingSafeEqual(
Buffer.from(dataIntegrityHash, "hex"),
Buffer.from(expectedHash, "hex")
)
) {
console.error("Data integrity verification failed for user:", userId);
throw new Error("Data integrity compromised");
}
// Decrypt sensitive data
const decryptedData = {};
for (const [key, encryptedValue] of Object.entries(encryptedData)) {
try {
if (key === "medicalInfo") {
decryptedData[key] = JSON.parse(
this.crypto.decryptSensitiveData(encryptedValue)
);
} else {
decryptedData[key] =
this.crypto.decryptSensitiveData(encryptedValue);
}
} catch (decryptError) {
console.error(
`Failed to decrypt ${key} for user ${userId}:`,
decryptError
);
decryptedData[key] = null; // Handle gracefully
}
}
return {
...publicData,
...decryptedData,
};
} catch (error) {
console.error("Profile retrieval failed:", error);
throw new Error("Failed to retrieve profile");
}
}
// Token-based authentication with encrypted session data
async createSecureSession(userId, sessionData) {
try {
const sessionId = this.crypto.generateSecureToken();
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
// Encrypt session data
const encryptedSessionData = this.crypto.encryptSensitiveData(
JSON.stringify(sessionData)
);
await db.sessions.create({
sessionId,
userId,
encryptedData: encryptedSessionData,
createdAt: new Date(),
expiresAt,
isActive: true,
});
return sessionId;
} catch (error) {
console.error("Session creation failed:", error);
throw new Error("Failed to create session");
}
}
async validateSession(sessionId) {
try {
const session = await db.sessions.findOne({
sessionId,
isActive: true,
expiresAt: { $gt: new Date() },
});
if (!session) return null;
// Decrypt session data
const sessionData = JSON.parse(
this.crypto.decryptSensitiveData(session.encryptedData)
);
return {
userId: session.userId,
data: sessionData,
expiresAt: session.expiresAt,
};
} catch (error) {
console.error("Session validation failed:", error);
return null;
}
}
}
SSL/TLS: Securing Communications
Professional SSL/TLS Configuration
Bulletproof HTTPS implementation:
// ✅ Production-grade HTTPS server setup
const https = require("https");
const http = require("http");
const fs = require("fs");
const path = require("path");
const express = require("express");
class SecureServerManager {
constructor() {
this.app = express();
this.httpServer = null;
this.httpsServer = null;
this.setupSecurityMiddleware();
this.configureSSL();
}
setupSecurityMiddleware() {
// Trust proxy for accurate client IP detection
this.app.set("trust proxy", 1);
// Force HTTPS in production
if (process.env.NODE_ENV === "production") {
this.app.use((req, res, next) => {
if (!req.secure && req.get("x-forwarded-proto") !== "https") {
return res.redirect(
301,
`https://${req.get("host")}${req.originalUrl}`
);
}
next();
});
}
// Security headers
this.app.use((req, res, next) => {
// HTTP Strict Transport Security
res.set(
"Strict-Transport-Security",
"max-age=63072000; includeSubDomains; preload"
);
// Content Security Policy
res.set(
"Content-Security-Policy",
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; " +
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
"font-src 'self' https://fonts.gstatic.com; " +
"img-src 'self' data: https:; " +
"connect-src 'self'; " +
"frame-ancestors 'none'; " +
"base-uri 'self'; " +
"form-action 'self'"
);
// Additional security headers
res.set("X-Content-Type-Options", "nosniff");
res.set("X-Frame-Options", "DENY");
res.set("X-XSS-Protection", "1; mode=block");
res.set("Referrer-Policy", "strict-origin-when-cross-origin");
res.set("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
next();
});
}
configureSSL() {
if (process.env.NODE_ENV === "production") {
try {
// Production SSL configuration
this.sslOptions = {
// Certificate files (use Let's Encrypt or commercial certificates)
key: fs.readFileSync("/etc/ssl/private/server.key"),
cert: fs.readFileSync("/etc/ssl/certs/server.crt"),
ca: fs.readFileSync("/etc/ssl/certs/ca-bundle.crt"), // Certificate chain
// SSL/TLS security settings
secureProtocol: "TLSv1_2_method", // Minimum TLS 1.2
ciphers: this.getSecureCipherSuite(),
honorCipherOrder: true,
// Perfect Forward Secrecy
dhparam: fs.readFileSync("/etc/ssl/private/dhparam.pem"),
// Client certificate verification (if needed)
requestCert: false,
rejectUnauthorized: false,
};
} catch (error) {
console.error("SSL configuration failed:", error);
process.exit(1);
}
} else {
// Development self-signed certificate
this.sslOptions = this.generateSelfSignedCertificate();
}
}
getSecureCipherSuite() {
// Modern, secure cipher suite (as of 2024)
return [
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES256-SHA384",
"ECDHE-RSA-AES128-SHA256",
"ECDHE-RSA-AES256-SHA",
"ECDHE-RSA-AES128-SHA",
"AES256-GCM-SHA384",
"AES128-GCM-SHA256",
"AES256-SHA256",
"AES128-SHA256",
"AES256-SHA",
"AES128-SHA",
].join(":");
}
generateSelfSignedCertificate() {
// For development only - never use in production
const selfsigned = require("selfsigned");
const attrs = [
{ name: "commonName", value: "localhost" },
{ name: "organizationName", value: "Development" },
{ name: "organizationalUnitName", value: "IT Department" },
];
const pems = selfsigned.generate(attrs, {
keySize: 2048,
days: 365,
algorithm: "sha256",
});
return {
key: pems.private,
cert: pems.cert,
};
}
startServers() {
const httpPort = process.env.HTTP_PORT || 80;
const httpsPort = process.env.HTTPS_PORT || 443;
// HTTP server (redirects to HTTPS in production)
this.httpServer = http.createServer(this.app);
this.httpServer.listen(httpPort, () => {
console.log(`HTTP server listening on port ${httpPort}`);
});
// HTTPS server
this.httpsServer = https.createServer(this.sslOptions, this.app);
this.httpsServer.listen(httpsPort, () => {
console.log(`HTTPS server listening on port ${httpsPort}`);
});
// Graceful shutdown
this.setupGracefulShutdown();
}
setupGracefulShutdown() {
const shutdown = () => {
console.log("Received shutdown signal, closing servers...");
this.httpServer.close(() => {
console.log("HTTP server closed");
this.httpsServer.close(() => {
console.log("HTTPS server closed");
process.exit(0);
});
});
// Force shutdown after 30 seconds
setTimeout(() => {
console.error("Forcing shutdown after timeout");
process.exit(1);
}, 30000);
};
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
}
}
// SSL/TLS certificate management
class CertificateManager {
constructor() {
this.certPath = "/etc/ssl/certs/";
this.keyPath = "/etc/ssl/private/";
}
async checkCertificateExpiry() {
try {
const cert = fs.readFileSync(path.join(this.certPath, "server.crt"));
const certInfo = this.parseCertificate(cert);
const now = new Date();
const expiryDate = new Date(certInfo.validTo);
const daysUntilExpiry = Math.floor(
(expiryDate - now) / (1000 * 60 * 60 * 24)
);
if (daysUntilExpiry <= 30) {
console.warn(`SSL certificate expires in ${daysUntilExpiry} days!`);
await this.sendCertificateExpiryAlert(daysUntilExpiry);
}
return {
expiryDate,
daysUntilExpiry,
needsRenewal: daysUntilExpiry <= 30,
};
} catch (error) {
console.error("Certificate expiry check failed:", error);
return null;
}
}
parseCertificate(certBuffer) {
// Parse X.509 certificate (simplified)
// In production, use a proper X.509 parsing library
const certString = certBuffer.toString();
// Extract validity dates (this is a simplified example)
const validFromMatch = certString.match(/Not Before: (.+)/);
const validToMatch = certString.match(/Not After : (.+)/);
return {
validFrom: validFromMatch ? validFromMatch[1] : null,
validTo: validToMatch ? validToMatch[1] : null,
};
}
async sendCertificateExpiryAlert(daysUntilExpiry) {
// Send alert to operations team
const alert = {
type: "certificate_expiry_warning",
message: `SSL certificate expires in ${daysUntilExpiry} days`,
urgency: daysUntilExpiry <= 7 ? "high" : "medium",
timestamp: new Date(),
};
// Implementation depends on your alerting system
console.error("CERTIFICATE ALERT:", alert);
}
}
// Usage
const secureServer = new SecureServerManager();
const certManager = new CertificateManager();
// Check certificate expiry daily
setInterval(() => {
certManager.checkCertificateExpiry();
}, 24 * 60 * 60 * 1000);
secureServer.startServers();
Key Takeaways
Cryptographic security transforms your application from vulnerable to bulletproof. Password hashing with bcrypt or Argon2 protects user credentials even when databases are compromised. Proper encryption safeguards sensitive data at rest. SSL/TLS secures all communications between clients and servers.
The cryptographic security mindset:
- Hash passwords, encrypt data: Passwords should never be reversible; sensitive data should be encrypted when you need to decrypt it later
- Use proven algorithms: bcrypt, Argon2, AES-256-GCM are battle-tested; avoid creating custom cryptography
- Forward secrecy matters: SSL/TLS configurations should protect past communications even if keys are compromised
- Key management is critical: Encryption is only as secure as how you protect the keys
What distinguishes cryptographically secure applications:
- Password hashing that survives rainbow table attacks and GPU-accelerated brute forcing
- Encryption implementations that protect data integrity with authenticated encryption modes
- SSL/TLS configurations that enforce modern security standards and cipher suites
- Certificate management that prevents service interruptions due to expired certificates
What’s Next
With cryptographic foundations secured, the final piece of backend security covers rate limiting, API security, secrets management, and compliance requirements. You’ll learn to defend against DDoS attacks, implement proper secrets management, and ensure your applications meet regulatory security standards.
Cryptography protects your data. The final security layer protects your entire application infrastructure from operational attacks and regulatory violations.