7 Cryptography Concepts Every Developer Should Know¶
Source: YouTube - Fireship
Tutorial: Fireship.io
Repository: GitHub
Date: October 28, 2021
Duration: 11 minutes
Overview¶
Cryptography forms the backbone of internet security. This tutorial covers seven essential cryptography concepts that every developer should understand, implemented using Node.js's built-in crypto module. You don't need to understand the underlying mathematics, but knowing these concepts is crucial for building secure applications.
1. Hash¶
Concept¶
A hash function takes an input of any length and produces a fixed-length output. The term "hash" comes from culinary roots meaning to "chop and mix" - exactly what the function does to data.
Key Properties¶
- Deterministic: Same input always produces the same output
- Fast to compute: But computationally expensive to reverse
- Unique: Small probability of collision
- One-way: Cannot derive the original input from the hash
Use Cases¶
- Password storage
- Data integrity verification
- Comparing values for equality
Implementation¶
const { createHash } = require('crypto');
// Create a string hash
function hash(str) {
return createHash('sha256').update(str).digest('hex');
}
// Compare two hashed passwords
let password = 'hi-mom!';
const hash1 = hash(password);
console.log(hash1);
// Later verification
password = 'hi-mom';
const hash2 = hash(password);
const match = hash1 === hash2;
console.log(match ? '✔️ good password' : '❌ password does not match');
Common Algorithms¶
- SHA-256: Secure Hashing Algorithm (256-bit)
- MD5: Older, less secure (not recommended for security)
- SHA-512: More secure variant with longer output
2. Salt¶
Concept¶
A salt is a random string added to input before hashing. This prevents attackers from using precomputed rainbow tables to crack common passwords.
Why Salt?¶
Users often choose weak passwords like "password123". Without salt: - Identical passwords produce identical hashes - Attackers can use rainbow tables (precomputed hash databases) - Database compromises expose all users with same password
With salt: - Same password + different salt = different hash - Rainbow tables become useless - Each password needs individual cracking
Implementation¶
const { scryptSync, randomBytes, timingSafeEqual } = require('crypto');
function signup(email, password) {
// Generate random salt (16 bytes)
const salt = randomBytes(16).toString('hex');
// Hash password with salt using scrypt
const hashedPassword = scryptSync(password, salt, 64).toString('hex');
// Store salt:hash format
const user = {
email,
password: `${salt}:${hashedPassword}`
};
users.push(user);
return user;
}
function login(email, password) {
const user = users.find(v => v.email === email);
// Split stored password into salt and hash
const [salt, key] = user.password.split(':');
// Hash the login attempt with stored salt
const hashedBuffer = scryptSync(password, salt, 64);
const keyBuffer = Buffer.from(key, 'hex');
// Timing-safe comparison to prevent timing attacks
const match = timingSafeEqual(hashedBuffer, keyBuffer);
if (match) {
return 'login success!';
} else {
return 'login fail!';
}
}
// Usage
const users = [];
const user = signup('foo@bar.com', 'pa$$word');
console.log(user);
const result = login('foo@bar.com', 'pa$$word');
console.log(result);
Best Practices¶
- Use cryptographically secure random salt generation
- Store salt alongside the hash (it's not secret)
- Use timing-safe comparison to prevent timing attacks
- Consider using bcrypt or argon2 for production
3. HMAC (Hash-based Message Authentication Code)¶
Concept¶
HMAC is a keyed hash - essentially a hash with a password. It verifies both the authenticity and the originator of data.
Key Properties¶
- Requires a secret key to generate
- Only someone with the key can create an authentic hash
- Different keys produce different outputs
- Verifies data hasn't been tampered with
Use Cases¶
- API authentication
- Message integrity verification
- JWT signatures
- Webhook verification
Implementation¶
const { createHmac } = require('crypto');
const password = 'super-secret!';
const message = '🎃 hello jack';
const hmac = createHmac('sha256', password)
.update(message)
.digest('hex');
console.log(hmac);
// To verify: recreate HMAC with same key and compare
Difference from Regular Hash¶
- Hash: No key, anyone can verify
- HMAC: Requires secret key, proves authenticity
4. Symmetric Encryption¶
Concept¶
Encryption makes a message confidential while allowing it to be reversed (decrypted) with the proper key. In symmetric encryption, the same key is used for both encryption and decryption.
Key Properties¶
- Same input produces different outputs each time (unlike hashes)
- Can be reversed with the correct key
- Fast and efficient
- Requires secure key exchange
Components¶
- Key: Secret used for encryption/decryption (32 bytes for AES-256)
- IV (Initialization Vector): Random value to ensure same plaintext encrypts differently each time (16 bytes)
- Algorithm: AES-256 is current standard
Implementation¶
const { createCipheriv, randomBytes, createDecipheriv } = require('crypto');
// Cipher setup
const message = 'i like turtles';
const key = randomBytes(32); // 256-bit key
const iv = randomBytes(16); // Initialization vector
// Encrypt
const cipher = createCipheriv('aes256', key, iv);
const encryptedMessage = cipher.update(message, 'utf8', 'hex') + cipher.final('hex');
console.log(`Encrypted: ${encryptedMessage}`);
// Decrypt
const decipher = createDecipheriv('aes256', key, iv);
const decryptedMessage = decipher.update(encryptedMessage, 'hex', 'utf-8') + decipher.final('utf8');
console.log(`Deciphered: ${decryptedMessage}`);
Use Cases¶
- File encryption
- Database encryption
- Encrypted messaging (when both parties have the key)
Challenge¶
Key Distribution Problem: How do both parties securely agree on the key over an insecure network? This is solved by asymmetric encryption.
5. Keypairs (Public/Private Keys)¶
Concept¶
Instead of sharing a single key, cryptographic keypairs consist of: - Public Key: Can be shared freely with anyone - Private Key: Must be kept secret
These are mathematically related but it's computationally infeasible to derive the private key from the public key.
Key Properties¶
- Public key can be distributed openly
- Private key must remain secret
- Operations with one key require the other to reverse
- Based on RSA, ECDSA, or similar algorithms
Implementation¶
const { generateKeyPairSync } = require('crypto');
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
modulusLength: 2048, // Key length in bits (2048 or 4096 recommended)
publicKeyEncoding: {
type: 'spki', // Recommended by Node.js docs
format: 'pem', // Privacy-Enhanced Mail format (text)
},
privateKeyEncoding: {
type: 'pkcs8', // Recommended by Node.js docs
format: 'pem',
},
});
console.log(publicKey); // Safe to share
console.log(privateKey); // Keep secret!
Output Format¶
- PEM Format: Base64-encoded text with headers
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY-----
Common Algorithms¶
- RSA: Most common, good for encryption and signing
- ECDSA: Elliptic Curve, smaller keys with same security
- Ed25519: Modern, fast, secure
6. Asymmetric Encryption¶
Concept¶
Uses keypair for encryption: - Encrypt with public key - Decrypt with private key
This solves the key distribution problem of symmetric encryption.
Real-World Usage¶
HTTPS/TLS: Every time you visit a secure website: 1. Browser retrieves server's public key (SSL certificate) 2. Browser encrypts session data with public key 3. Server decrypts with its private key 4. Secure connection established
Implementation¶
const { publicEncrypt, privateDecrypt } = require('crypto');
const { publicKey, privateKey } = require('./keypair');
const secretMessage = 'The eagle flies at midnight';
// Encrypt with PUBLIC key
const encryptedData = publicEncrypt(
publicKey,
Buffer.from(secretMessage)
);
console.log('Encrypted:', encryptedData.toString('hex'));
// Decrypt with PRIVATE key
const decryptedData = privateDecrypt(
privateKey,
encryptedData
);
console.log('Decrypted:', decryptedData.toString('utf-8'));
Key Characteristics¶
- Slower than symmetric encryption
- Used to exchange symmetric keys securely
- Public key can be distributed without security risk
- Private key compromise breaks all security
Typical Workflow¶
- Use asymmetric encryption to securely exchange a symmetric key
- Use symmetric encryption for actual data (much faster)
- This is how TLS/SSL works
7. Signing (Digital Signatures)¶
Concept¶
Digital signing proves: - Authenticity: Message came from claimed sender - Integrity: Message hasn't been modified - Non-repudiation: Sender can't deny sending it
Process¶
- Signing: Hash the message, then encrypt hash with private key
- Verification: Decrypt signature with public key, compare with message hash
Implementation¶
const { createSign, createVerify } = require('crypto');
const { publicKey, privateKey } = require('./keypair');
const data = 'this data must be signed';
// === SIGN (by sender) ===
const signer = createSign('rsa-sha256');
signer.update(data);
const signature = signer.sign(privateKey, 'hex');
console.log('Signature:', signature);
// === VERIFY (by recipient) ===
const verifier = createVerify('rsa-sha256');
verifier.update(data);
const isVerified = verifier.verify(publicKey, signature, 'hex');
console.log('Verified:', isVerified); // true if authentic
Use Cases¶
- Code Signing: Verify software hasn't been tampered with
- SSL Certificates: Prove website identity
- Git Commits: Verify commit author
- JWT Tokens: Authenticate API requests
- Blockchain: Transaction verification
Why It Works¶
- Only the private key holder can create valid signatures
- Anyone with the public key can verify
- Tampering with the message invalidates the signature
Encryption vs Signing¶
| Operation | Encryption | Signing |
|---|---|---|
| Purpose | Confidentiality | Authenticity |
| Encrypt with | Public Key | Private Key |
| Decrypt/Verify with | Private Key | Public Key |
| Protects against | Eavesdropping | Tampering/Forgery |
Key Takeaways¶
When to Use Each¶
- Hash: Storing passwords, comparing values, data integrity
- Salt: Always use with password hashing
- HMAC: API authentication, verifying message source
- Symmetric Encryption: Fast encryption when both parties have key
- Keypairs: Foundation for asymmetric operations
- Asymmetric Encryption: Secure key exchange, HTTPS
- Signing: Prove authenticity and integrity
Security Best Practices¶
- Never roll your own crypto - use established libraries
- Keep private keys private - never commit to git
- Use timing-safe comparisons - prevent timing attacks
- Generate strong random values - use cryptographically secure RNG
- Keep algorithms updated - avoid deprecated algorithms (MD5, SHA-1)
- Use appropriate key lengths - RSA 2048+, AES 256
- Salt all password hashes - use unique salt per password
Common Pitfalls¶
- ❌ Using hash without salt for passwords
- ❌ Reusing the same IV for symmetric encryption
- ❌ Sharing private keys
- ❌ Using weak algorithms (MD5, DES)
- ❌ Storing keys in source code
- ❌ Not validating signatures before trusting data
Additional Resources¶
- Node.js Crypto Documentation: https://nodejs.org/api/crypto.html
- Source Code: https://github.com/fireship-io/node-crypto-examples
- Further Learning:
- Try the hacking challenge in
src/hack.js - Implement authentication system using these concepts
- Study TLS/SSL handshake process
- Learn about bcrypt, argon2 for password hashing