Creo applicazioni web moderne e strumenti digitali personalizzati per aiutare le attività a crescere attraverso l'innovazione tecnologica. La mia passione è unire informatica ed economia per generare valore reale.
La mia passione per l'informatica è nata tra i banchi dell'Istituto Tecnico Commerciale di Maglie, dove ho scoperto il potere della programmazione e il fascino di creare soluzioni digitali. Fin da subito, ho capito che l'informatica non era solo codice, ma uno strumento straordinario per trasformare idee in realtà.
Durante gli studi superiori in Sistemi Informativi Aziendali, ho iniziato a intrecciare informatica ed economia, comprendendo come la tecnologia possa essere il motore della crescita per qualsiasi attività. Questa visione mi ha accompagnato all'Università degli Studi di Bari, dove ho conseguito la Laurea in Informatica, approfondendo le mie competenze tecniche e la mia passione per lo sviluppo software.
Oggi metto questa esperienza al servizio di imprese, professionisti e startup, creando soluzioni digitali su misura che automatizzano processi, ottimizzano risorse e aprono nuove opportunità di business. Perché la vera innovazione inizia quando la tecnologia incontra le esigenze reali delle persone.
Le Mie Competenze
Analisi Dati & Modelli Previsionali
Trasformo i dati in insights strategici con analisi approfondite e modelli predittivi per decisioni informate
Automazione Processi
Creo strumenti personalizzati che automatizzano operazioni ripetitive e liberano tempo per attività a valore aggiunto
Sistemi Custom
Sviluppo sistemi software su misura, dalle integrazioni tra piattaforme alle dashboard personalizzate
Credo fermamente che l'informatica sia lo strumento più potente per trasformare le idee in realtà e migliorare la vita delle persone.
🚀
Democratizzare la Tecnologia
La mia missione è rendere l'informatica accessibile a tutti: dalle piccole imprese locali alle startup innovative, fino ai professionisti che vogliono digitalizzare la propria attività. Ogni realtà merita di sfruttare le potenzialità del digitale.
💡
Unire Informatica ed Economia
Non è solo questione di scrivere codice: è capire come la tecnologia possa generare valore reale. Intrecciando competenze informatiche e visione economica, aiuto le attività a crescere, ottimizzare processi e raggiungere nuovi traguardi di efficienza e redditività.
🎯
Creare Soluzioni su Misura
Ogni attività è unica, e così devono esserlo le soluzioni. Sviluppo strumenti personalizzati che rispondono alle esigenze specifiche di ciascun cliente, automatizzando processi ripetitivi e liberando tempo per ciò che conta davvero: far crescere il business.
Trasforma la Tua Attività con la Tecnologia
Che tu gestisca un negozio, uno studio professionale o un'azienda, posso aiutarti a sfruttare le potenzialità dell'informatica per lavorare meglio, più velocemente e in modo più intelligente.
Il mio percorso accademico e le tecnologie che padroneggio
Certificazioni Professionali
8 certificazioni conseguite
Nuovo
Visualizza
Reinvention With Agentic AI Learning Program
Anthropic
Dicembre 2024
Nuovo
Visualizza
Agentic AI Fluency
Anthropic
Dicembre 2024
Nuovo
Visualizza
AI Fluency for Students
Anthropic
Dicembre 2024
Nuovo
Visualizza
AI Fluency: Framework and Foundations
Anthropic
Dicembre 2024
Nuovo
Visualizza
Claude with the Anthropic API
Anthropic
Dicembre 2024
Visualizza
Master SQL
RoadMap.sh
Novembre 2024
Visualizza
Oracle Certified Foundations Associate
Oracle
Ottobre 2024
Visualizza
People Leadership Credential
Connect
Settembre 2024
💻 Linguaggi & Tecnologie
☕Java
🐍Python
📜JavaScript
🅰️Angular
⚛️React
🔷TypeScript
🗄️SQL
🐘PHP
🎨CSS/SCSS
🔧Node.js
🐳Docker
🌿Git
💼
12/2024 - Presente
Custom Software Engineering Analyst
Accenture
Bari, Puglia, Italia · Ibrida
Analisi e sviluppo di sistemi informatici attraverso l'utilizzo di Java e Quarkus in Health and Public Sector. Formazione continua su tecnologie moderne per la creazione di soluzioni software personalizzate ed efficienti e sugli agenti.
💼
06/2022 - 12/2024
Analista software e Back End Developer Associate Consultant
Links Management and Technology SpA
Esperienza nell'analisi di sistemi software as-is e flussi ETL utilizzando PowerCenter. Formazione completata su Spring Boot per lo sviluppo di applicazioni backend moderne e scalabili. Sviluppatore Backend specializzato in Spring Boot, con esperienza in progettazione di database, analisi, sviluppo e testing dei task assegnati.
💼
02/2021 - 10/2021
Programmatore software
Adesso.it (prima era WebScience srl)
Esperienza nell'analisi AS-IS e TO-BE, evoluzioni SEO ed evoluzioni website per migliorare le performance e l'engagement degli utenti.
🎓
2018 - 2025
Laurea in Informatica
Università degli Studi di Bari Aldo Moro
Bachelor's degree in Computer Science, focusing on software engineering, algorithms, and modern development practices.
📚
2013 - 2018
Diploma - Sistemi Informativi Aziendali
Istituto Tecnico Commerciale di Maglie
Technical diploma specializing in Business Information Systems, combining IT knowledge with business management.
Contattami
Hai un progetto in mente? Parliamone! Compila il form qui sotto e ti risponderò al più presto.
* Campi obbligatori. I tuoi dati saranno utilizzati solo per rispondere alla tua richiesta.
07 - Practical Cryptography for Developers: AES-256-GCM, Hashing and TLS 1.3
Cryptography is the invisible foundation of modern information security. Every password stored
in a database, every sensitive piece of data transmitted over HTTPS, every digital signature
that authenticates a software update - all of it depends on correctly implemented cryptographic
primitives. Yet cryptography is also one of the areas where developers make the most serious
mistakes, often without realizing it.
According to OWASP Top 10:2025, Cryptographic Failures (A02) remain among the
top three most critical vulnerabilities in web applications. These are not exotic attacks
requiring quantum computers: the vast majority of cryptographic breaches happen due to simple
mistakes. MD5 or SHA-1 used for password hashing. AES in ECB mode producing visible patterns
in ciphertext. Reused IVs (Initialization Vectors). Keys hardcoded in source code. Errors that
require no advanced penetration testing to exploit.
This article approaches cryptography from a developer's perspective: not mathematical theory,
but practical patterns in Node.js and the Web Crypto API. You will learn when to use symmetric
vs asymmetric cryptography, how to correctly implement AES-256-GCM, which hashing algorithm to
choose for passwords, how to configure TLS 1.3, and how to prepare for post-quantum
cryptography. Every section includes production-ready code examples with pitfalls to avoid.
What You Will Learn
Symmetric vs asymmetric cryptography: when to use each approach
AES-256-GCM: correct implementation with random IV and authentication tag
RSA vs ECC (ECDSA, Ed25519): performance and security comparison in 2025
Password hashing with Argon2id and bcrypt: OWASP recommended parameters
SHA-256 for data integrity vs password hashing: correct uses of each
TLS 1.3: secure configuration in Node.js with modern cipher suites
Key management: avoiding hardcoded keys, using environment variables and KMS
Web Crypto API: browser-side cryptography without external dependencies
Post-quantum cryptography: NIST FIPS 203 (ML-KEM) and preparation for 2030
Angular checklist: secure patterns for handling sensitive data in the frontend
Symmetric vs Asymmetric Cryptography
The choice between symmetric and asymmetric cryptography is not a matter of preference: it is
an architectural decision that depends on the problem you are solving. Confusing the two
approaches systematically leads to vulnerabilities or unnecessarily degraded performance.
Symmetric cryptography uses the same key to encrypt and decrypt. It is fast
(AES-256-GCM encrypts gigabytes per second on modern hardware) and is suitable for encrypting
large amounts of data. The challenge is key distribution: how do you securely share the key
with whoever needs to decrypt the data?
Asymmetric cryptography uses a mathematically correlated key pair: a public
key (which you can distribute freely) and a private key (which you keep secret). It solves the
key distribution problem but is orders of magnitude slower than symmetric encryption.
RSA-2048 encrypts about 250 bytes per operation per second, while AES-256-GCM reaches
multi-GB/s.
The professional pattern combines both approaches in a hybrid system, exactly as TLS does:
asymmetric cryptography is used only to securely exchange a symmetric session key; then all
data is encrypted with that AES key. This is the foundation of HTTPS, SSH, and every modern
secure protocol.
Fundamental Principle
Symmetric (AES-256-GCM): for encrypting data at-rest and in-transit in bulk
Asymmetric (RSA/ECC): for key exchange, identity authentication, digital signatures
Hybrid: for almost all real-world use cases (TLS, PGP, Signal Protocol)
Hashing (Argon2, SHA-256): one-way function, not reversible
AES-256-GCM: Correct Implementation
AES-256-GCM (Advanced Encryption Standard with 256-bit key in Galois/Counter Mode) is the
de facto standard for authenticated symmetric encryption. GCM mode has two critical properties
that make it superior to alternatives like AES-CBC or AES-ECB:
Confidentiality: data is encrypted and unreadable without the key
Authentication (AEAD): produces an authentication tag that guarantees data
integrity. If someone modifies the ciphertext, decryption fails with an explicit error.
AES-ECB and AES-CBC lack this property: an attacker can modify ciphertext without decryption
noticing (bit-flipping attack).
Critical AES Mistakes to Avoid
Never reuse the IV with the same key: in AES-GCM, reusing IV+key
completely compromises confidentiality. Always generate a random 12-byte IV for each
encryption operation.
Never use AES-ECB: ECB mode produces the same output for the same
input (the classic "ECB penguin"), making data patterns visible.
Never hardcode the key: the key must come from environment variables,
a KMS (Key Management Service), or HSM - never from source code.
Always verify the authentication tag before using decrypted data.
Here is a complete, correct implementation of AES-256-GCM in Node.js using the built-in
crypto module:
// crypto-utils.ts - AES-256-GCM implementation for Node.js
import { randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
// ============================================================
// CONSTANTS - not hardcoded, come from env/KMS in production
// ============================================================
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 12; // 96 bits - recommended for GCM
const AUTH_TAG_LENGTH = 16; // 128 bits - maximum tag length
const KEY_LENGTH = 32; // 256 bits
interface EncryptedData {
iv: string; // base64
ciphertext: string; // base64
authTag: string; // base64
}
// ============================================================
// KEY DERIVATION from password (for password-derived keys)
// In production, prefer keys generated by KMS/HSM
// ============================================================
function deriveKey(password: string, salt: Buffer): Buffer {
// scrypt is memory-hard: resistant to GPU brute-force
return scryptSync(password, salt, KEY_LENGTH, {
N: 32768, // CPU/memory cost (2^15)
r: 8,
p: 1,
});
}
// ============================================================
// ENCRYPTION
// ============================================================
export function encrypt(plaintext: string, keyHex: string): EncryptedData {
const key = Buffer.from(keyHex, 'hex');
if (key.length !== KEY_LENGTH) {
throw new Error(`AES-256 key requires 32 bytes, received
#123;key.length}`);
}
// Random IV unique for each operation - CRITICAL
const iv = randomBytes(IV_LENGTH);
const cipher = createCipheriv(ALGORITHM, key, iv, {
authTagLength: AUTH_TAG_LENGTH,
});
const encryptedBuffer = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final(),
]);
// Extract auth tag AFTER cipher.final()
const authTag = cipher.getAuthTag();
return {
iv: iv.toString('base64'),
ciphertext: encryptedBuffer.toString('base64'),
authTag: authTag.toString('base64'),
};
}
// ============================================================
// DECRYPTION
// ============================================================
export function decrypt(encrypted: EncryptedData, keyHex: string): string {
const key = Buffer.from(keyHex, 'hex');
const iv = Buffer.from(encrypted.iv, 'base64');
const ciphertext = Buffer.from(encrypted.ciphertext, 'base64');
const authTag = Buffer.from(encrypted.authTag, 'base64');
const decipher = createDecipheriv(ALGORITHM, key, iv, {
authTagLength: AUTH_TAG_LENGTH,
});
decipher.setAuthTag(authTag);
try {
// If tag doesn't match, decipher.final() throws an error
const decryptedBuffer = Buffer.concat([
decipher.update(ciphertext),
decipher.final(), // Throws if authTag is invalid
]);
return decryptedBuffer.toString('utf8');
} catch (err) {
throw new Error('Decryption failed: data corrupted or wrong key');
}
}
// ============================================================
// USAGE EXAMPLE
// ============================================================
const AES_KEY = process.env['AES_256_KEY']!; // 64 hex chars = 32 bytes
const sensitiveData = JSON.stringify({
card: '4111111111111111',
expiry: '12/26',
cvv: '123',
});
const encrypted = encrypt(sensitiveData, AES_KEY);
console.log('Encrypted:', encrypted);
const decrypted = decrypt(encrypted, AES_KEY);
console.log('Decrypted:', decrypted);
RSA and ECC: Digital Signatures and Asymmetric Cryptography
In 2025, the choice between RSA and ECC (Elliptic Curve Cryptography) is clear: for new
systems, prefer ECC. At equivalent security levels, ECC keys are drastically smaller and
operations are significantly faster. An ECDSA P-256 key at 256 bits provides the same
security as a 3072-bit RSA key, with signing operations 10-15x faster.
Algorithm
Key size
Security level
Relative performance
Recommended 2025
RSA-2048
2048 bits
112 bits
Baseline (1x)
Legacy compatibility only
RSA-3072
3072 bits
128 bits
0.3x
Acceptable, but prefer ECC
ECDSA P-256
256 bits
128 bits
10x faster than RSA-3072
Yes, for TLS and signatures
Ed25519
256 bits
128 bits
15x faster than RSA-3072
Yes, preferred for digital signatures
Ed25519 (Edwards-curve Digital Signature Algorithm) is the modern choice for
digital signatures: faster than ECDSA, free from nonce-related implementation pitfalls, and
supported by Node.js, OpenSSL 3.x, and all modern browsers. SSH, Signal, and many
high-security applications use it as their default.
Password hashing is one of the most misunderstood aspects of application security. The
fundamental principle is simple: you must never be able to recover a password from the
database. If the database is compromised, the attacker should not be able to recover the
original passwords. This is why password hashing requires specific algorithms, radically
different from those used for data integrity.
SHA-256 Is Not for Passwords
SHA-256 is extremely fast (billions of operations per second on GPU), which is great for
data integrity but catastrophic for passwords. An attacker with an RTX 4090 can test 10+
billion SHA-256 hashes per second, making an entire dictionary of common passwords
crackable in seconds. For passwords you need memory-hard, computationally
expensive algorithms.
SHA-256: for checksums, HMAC, token derivation. NEVER for passwords.
MD5, SHA-1: deprecated even for checksums. Do not use in any new code.
bcrypt: secure, battle-tested, use it if already integrated in your system.
Argon2id: OWASP 2025 gold standard for new applications.
OWASP recommends Argon2id as the first choice for new applications. It is
the winner of the Password Hashing Competition (2015) and is designed to resist GPU and ASIC
attacks through its memory-hard nature: it requires a significant amount of RAM to compute the
hash, making parallel attacks much more expensive.
// password-hashing.ts - Argon2id and bcrypt for Node.js
import argon2 from 'argon2';
import bcrypt from 'bcrypt';
import { createHmac, timingSafeEqual } from 'crypto';
// ============================================================
// ARGON2ID - OWASP 2025 recommended for new applications
// ============================================================
const ARGON2_OPTIONS: argon2.Options = {
type: argon2.argon2id, // id = combination of i (data-independent) and d (data-dependent)
memoryCost: 19456, // 19 MiB - OWASP minimum
timeCost: 2, // 2 iterations - OWASP minimum
parallelism: 1, // 1 thread
// For high-security systems (e.g. admin, banking):
// memoryCost: 65536, // 64 MiB
// timeCost: 3,
};
export async function hashPasswordArgon2(password: string): Promise<string> {
// argon2 automatically handles random salt
return argon2.hash(password, ARGON2_OPTIONS);
}
export async function verifyPasswordArgon2(
hashedPassword: string,
candidatePassword: string
): Promise<boolean> {
try {
return await argon2.verify(hashedPassword, candidatePassword);
} catch {
return false;
}
}
// Check if hash needs rehashing (upgrade parameters without forced reset)
export async function needsRehash(hash: string): Promise<boolean> {
return argon2.needsRehash(hash, ARGON2_OPTIONS);
}
// ============================================================
// BCRYPT - For existing systems (cost factor >= 12)
// ============================================================
const BCRYPT_ROUNDS = 12; // OWASP minimum; 14 for critical systems
export async function hashPasswordBcrypt(password: string): Promise<string> {
// bcrypt truncates at 72 bytes - use pre-hashing for long passwords
if (password.length > 72) {
const preHashed = createHmac('sha256', process.env['BCRYPT_PEPPER']!)
.update(password)
.digest('hex');
return bcrypt.hash(preHashed, BCRYPT_ROUNDS);
}
return bcrypt.hash(password, BCRYPT_ROUNDS);
}
// ============================================================
// SHA-256 for HMAC and integrity checks (NOT passwords)
// ============================================================
export function computeHMAC(data: string, secret: string): string {
return createHmac('sha256', secret).update(data, 'utf8').digest('hex');
}
export function verifyHMAC(data: string, secret: string, expected: string): boolean {
const computed = createHmac('sha256', secret).update(data, 'utf8').digest('hex');
// timingSafeEqual prevents timing attacks
const computedBuffer = Buffer.from(computed, 'hex');
const expectedBuffer = Buffer.from(expected, 'hex');
if (computedBuffer.length !== expectedBuffer.length) return false;
return timingSafeEqual(computedBuffer, expectedBuffer);
}
// ============================================================
// COMPLETE AUTHENTICATION FLOW
// ============================================================
async function authenticationFlow() {
// Registration
const password = 'MyS3cur3P4ss!';
const hash = await hashPasswordArgon2(password);
// Save hash to DB: user.passwordHash = hash
// Login
const isValid = await verifyPasswordArgon2(hash, password);
console.log('Login valid:', isValid); // true
// Automatic rehashing (upgrade params without invalidating sessions)
if (await needsRehash(hash)) {
const newHash = await hashPasswordArgon2(password);
// Update DB with newHash
console.log('Password rehashed with updated parameters');
}
}
TLS 1.3: Secure Configuration in Node.js
TLS 1.3 (RFC 8446, 2018) is the current secure transport protocol. Compared to TLS 1.2 it
introduces significant improvements: faster handshake (1-RTT instead of 2-RTT, with 0-RTT
support for resumed sessions), simplified and more secure cipher suites (eliminates all weak
algorithms including RC4, DES, MD5 for MAC, RSA key exchange), and mandatory Perfect Forward
Secrecy via ECDHE.
TLS 1.3 cipher suites are only 5 (vs dozens in TLS 1.2) and all use AEAD (Authenticated
Encryption with Associated Data):
TLS_AES_256_GCM_SHA384 - Recommended
TLS_AES_128_GCM_SHA256 - Acceptable
TLS_CHACHA20_POLY1305_SHA256 - Preferred on devices without AES hardware acceleration
// tls-config.ts - Secure TLS 1.3 configuration for Node.js HTTPS
import https from 'https';
import fs from 'fs';
import { TLSSocket } from 'tls';
import express from 'express';
import helmet from 'helmet';
// ============================================================
// HTTPS SERVER WITH TLS 1.3
// ============================================================
const tlsOptions: https.ServerOptions = {
cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
key: fs.readFileSync('/etc/ssl/private/server.key'),
// Force TLS 1.3 (disable older versions)
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
// TLS 1.3 cipher suites (order indicates preference)
ciphers: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256',
].join(':'),
sessionTimeout: 300, // 5 minutes max
};
// ============================================================
// EXPRESS + HTTPS + SECURITY HEADERS
// ============================================================
const app = express();
app.use(helmet({
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true, // Include in HSTS preload list
},
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
upgradeInsecureRequests: [], // Force HTTPS for HTTP resources
},
},
}));
const server = https.createServer(tlsOptions, app);
server.listen(443, () => console.log('HTTPS with TLS 1.3 active'));
// ============================================================
// VERIFY TLS VERSION PER REQUEST
// ============================================================
app.use((req, res, next) => {
const socket = req.socket as TLSSocket;
const tlsVersion = socket.getProtocol?.();
if (tlsVersion !== 'TLSv1.3') {
console.warn(`Connection with #123;tlsVersion} from #123;req.ip} - rejected`);
return res.status(426).json({
error: 'TLS 1.3 required',
});
}
next();
});
// ============================================================
// CERTIFICATE ROTATION: Let's Encrypt + Certbot
// ============================================================
// certbot certonly --webroot -w /var/www/html -d example.com
// crontab: 0 2 * * 1 certbot renew --quiet --post-hook "systemctl reload nginx"
Key Management: the Most Common Weak Point
The most robust cryptographic system in the world is useless if keys are managed poorly.
The most common attack vector is not "breaking AES-256" but finding the AES-256 key in an
environment file committed to GitHub, in source code, or in system logs.
In 2024, GitHub Secret Scanning detected over 39 million secrets exposed in public
repositories. Most were API keys, but a significant portion were cryptographic keys. The damage
from exposing a symmetric key is catastrophic: all data encrypted with that key is compromised.
Security Hierarchy for Key Management
Level 1 - Minimum acceptable: environment variables (not in
.env committed to git). Use .env.local and add
*.env* to .gitignore.
Level 2 - Production for smaller apps: secrets manager services like
HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager.
Level 3 - High security: Hardware Security Module (HSM) or KMS (Key
Management Service) that performs cryptographic operations inside tamper-proof hardware
without ever exposing the key in plaintext.
// key-management.ts - Secure patterns for cryptographic key management
// ============================================================
// SECURE KEY LOADING (from environment, never hardcoded)
// ============================================================
function loadCryptoKeys(): { aesKey: string; hmacSecret: string } {
const aesKey = process.env['AES_256_KEY'];
const hmacSecret = process.env['HMAC_SECRET'];
if (!aesKey || aesKey.length !== 64) {
throw new Error('AES_256_KEY missing or invalid (must be 64 hex chars = 32 bytes)');
}
if (!hmacSecret || hmacSecret.length < 32) {
throw new Error('HMAC_SECRET missing or too short (minimum 32 characters)');
}
if (!/^[0-9a-fA-F]{64}$/.test(aesKey)) {
throw new Error('AES_256_KEY is not a valid hex string');
}
return { aesKey, hmacSecret };
}
// ============================================================
// SECURE KEY GENERATION (for initial setup)
// ============================================================
import { randomBytes } from 'crypto';
export function generateSecureKey(lengthBytes: number = 32): string {
return randomBytes(lengthBytes).toString('hex');
}
// Generate a new AES-256 key:
// node -e "const {randomBytes}=require('crypto'); console.log(randomBytes(32).toString('hex'))"
// Save the value in: AWS Secrets Manager / Azure Key Vault / .env.local
// ============================================================
// KEY ROTATION PATTERN
// ============================================================
interface KeyVersion {
version: number;
key: string;
activeFrom: Date;
activeTo?: Date; // undefined = currently active key
}
class KeyRotationManager {
private keys: Map<number, KeyVersion> = new Map();
private currentVersion: number;
constructor(keys: KeyVersion[]) {
keys.forEach(k => this.keys.set(k.version, k));
this.currentVersion = Math.max(...keys.map(k => k.version));
}
// Always encrypt with the latest key
encrypt(data: string): EncryptedPayload {
const currentKey = this.keys.get(this.currentVersion)!;
const encrypted = encrypt(data, currentKey.key);
return {
...encrypted,
keyVersion: this.currentVersion, // Include version in payload
};
}
// Decrypt using the version specified in the payload (supports old keys)
decrypt(payload: EncryptedPayload): string {
const key = this.keys.get(payload.keyVersion);
if (!key) {
throw new Error(`Key version #123;payload.keyVersion} not found`);
}
return decrypt(payload, key.key);
}
}
interface EncryptedPayload {
iv: string;
ciphertext: string;
authTag: string;
keyVersion: number;
}
Web Crypto API: Cryptography in the Browser
The Web Crypto API (available in all modern browsers and in Node.js 15+) brings cryptography
directly to the browser without external JavaScript library dependencies. It is based on
asynchronous operations, uses memory not exposed to JavaScript (non-extractable
CryptoKey objects by default), and is natively implemented by the browser for
optimal performance.
A typical use case is end-to-end encryption in the browser: data is encrypted before being
sent to the server, which never has access to the plaintext. This pattern is used by
password managers, secure notes applications, and encrypted messaging clients.
// web-crypto.service.ts - Angular service for browser-side encryption
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class WebCryptoService {
private readonly subtle = window.crypto.subtle;
// ============================================================
// GENERATE AES-256-GCM KEY in the browser
// ============================================================
async generateAESKey(extractable = false): Promise<CryptoKey> {
return this.subtle.generateKey(
{
name: 'AES-GCM',
length: 256,
},
extractable, // false = non-extractable key (more secure)
['encrypt', 'decrypt']
);
}
// ============================================================
// AES-256-GCM ENCRYPTION in the browser
// ============================================================
async encrypt(
data: string,
key: CryptoKey
): Promise<{ iv: string; ciphertext: string }> {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
// Random 12-byte (96-bit) IV - CRITICAL: unique per encryption
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const cipherBuffer = await this.subtle.encrypt(
{ name: 'AES-GCM', iv, tagLength: 128 },
key,
dataBuffer
);
return {
iv: this.bufferToBase64(iv.buffer),
ciphertext: this.bufferToBase64(cipherBuffer),
// In browser AES-GCM, the auth tag is concatenated to ciphertext
};
}
// ============================================================
// KEY DERIVATION FROM PASSWORD (PBKDF2)
// ============================================================
async deriveKeyFromPassword(
password: string,
salt: Uint8Array,
iterations = 310000 // OWASP minimum 2025 for PBKDF2-SHA-256
): Promise<CryptoKey> {
const encoder = new TextEncoder();
const keyMaterial = await this.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
return this.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations,
hash: 'SHA-256',
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
// ============================================================
// ECDSA P-256 DIGITAL SIGNATURE in the browser
// ============================================================
async generateSigningKeyPair(): Promise<CryptoKeyPair> {
return this.subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'P-256',
},
false,
['sign', 'verify']
);
}
async sign(data: string, privateKey: CryptoKey): Promise<string> {
const encoder = new TextEncoder();
const signature = await this.subtle.sign(
{ name: 'ECDSA', hash: { name: 'SHA-256' } },
privateKey,
encoder.encode(data)
);
return this.bufferToBase64(signature);
}
// ============================================================
// SECURE RANDOM TOKEN (use instead of Math.random())
// ============================================================
generateSecureToken(lengthBytes = 32): string {
const array = new Uint8Array(lengthBytes);
window.crypto.getRandomValues(array);
return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
}
private bufferToBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
return btoa(String.fromCharCode(...bytes));
}
private base64ToBuffer(base64: string): ArrayBuffer {
const binaryStr = atob(base64);
const bytes = new Uint8Array(binaryStr.length);
for (let i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return bytes.buffer;
}
}
Post-Quantum Cryptography: Preparing for 2030
Quantum computers are not yet a practical threat to today's cryptography, but NIST published
in August 2024 the first definitive post-quantum standards (FIPS 203, 204, 205) precisely
because migration timelines are long and the "harvest now, decrypt later" risk is real. A
malicious actor collecting today's RSA-encrypted communications could decrypt them in the
future with a sufficiently powerful quantum computer.
NIST Post-Quantum Standards (August 2024)
FIPS 203 - ML-KEM (Kyber): Key Encapsulation Mechanism for key exchange.
Replaces ECDH and RSA key exchange. Recommended for hybrid TLS 1.3
(X25519+MLKEM-768).
FIPS 204 - ML-DSA (Dilithium): Post-quantum digital signature. Replaces
ECDSA and RSA-PSS. Larger keys but secure against Shor's algorithm.
FIPS 205 - SLH-DSA (SPHINCS+): Hash-based digital signature, more
conservative. Useful as an algorithmic backup.
Recommended 2025-2030 strategy: hybrid approach. Use X25519 + ML-KEM-768
for TLS (classical + post-quantum security simultaneously).
For developers, preparing for post-quantum cryptography today primarily means:
Crypto agility: design systems to be able to change algorithms without
rewriting everything. Do not hardcode "AES-256" or "RSA" in databases; use an
algorithm_version field.
Cryptographic inventory: know where and how RSA/ECC are used in your
application. The first step is visibility.
Hybrid TLS: enable X25519+MLKEM-768 on TLS servers supporting OpenSSL
3.5+ or BoringSSL (Chrome has used it since 2023).
No urgent migration for at-rest data with symmetric AES-256 keys:
AES-256 is already resistant to quantum computers (Grover reduces effective security to
128 bits, still very secure).
The 10 Most Common Cryptographic Mistakes
In cryptography, meta-knowledge is often more valuable than technical details. Knowing what
NOT to do frequently matters more than knowing how to use every API. Here are the mistakes
that appear systematically in security code reviews:
Mistake
Impact
Solution
MD5/SHA-1 for password hashing
Critical - crackable in seconds
Argon2id or bcrypt with cost >= 12
AES-ECB (no IV)
High - patterns visible in ciphertext
AES-256-GCM with random IV
Fixed or incremental IV
Critical - nullifies GCM security
randomBytes(12) per encryption
Hardcoded keys in source code
Critical - exposes all encrypted data
AWS/Azure KMS, environment variables
RSA-PKCS1v1.5 for encryption
High - vulnerable to Bleichenbacher
RSA-OAEP or ECDH for key exchange
JWT without signature verification
Critical - privilege escalation
Always verify signature server-side
Hash comparison with ==
Medium - timing attack
timingSafeEqual() from Node.js crypto
Math.random() for tokens
High - predictable tokens
crypto.randomBytes() or getRandomValues()
TLS 1.0/1.1 enabled
High - BEAST, POODLE, CRIME attacks
minVersion: TLSv1.3
No certificate validation
Critical - MITM attack
Never NODE_TLS_REJECT_UNAUTHORIZED=0 in prod
Conclusion
Practical cryptography for developers does not require being a cryptographer: it requires
knowing the right patterns, avoiding known anti-patterns, and choosing the appropriate tools
for each use case. The fundamental principles summarize to a few key points:
AES-256-GCM for authenticated symmetric encryption: random 12-byte IV,
128-bit authentication tag, key never in source code.
Ed25519 or ECDSA P-256 for digital signatures: prefer ECC over RSA for
new systems, RSA-4096 only for legacy compatibility.
Argon2id for password hashing (OWASP parameters: 19 MiB, 2 iterations),
bcrypt with cost factor 12+ for existing systems. Never SHA-256 or MD5 for passwords.
TLS 1.3 as the minimum for all production services, with HSTS and modern
cipher suites. Disable TLS 1.0/1.1/1.2 where possible.
Key management via KMS or secret manager: the most robust key becomes
useless if it is exposed in the repository.
Crypto agility in new systems: design to be able to migrate algorithms
without rewriting everything, in preparation for the post-quantum transition by 2030.
Cryptography is not a feature added at the end: it is an architectural decision made at the
start. Every sensitive field in the database, every API transmitting personal data, every
authentication token requires a deliberate choice of algorithm, key management strategy, and
rotation policy.
Continue with the Series: Web Security for Developers
Previous article: Supply Chain Security: npm audit and SBOM - how to protect the dependency chain
Next article: DevSecOps for Developers: SAST, DAST in CI/CD - integrating security into the pipeline
Related: API Security: OAuth 2.1, JWT and Rate Limiting - JWT best practices in depth
See also: the DevOps Frontend series for secure deployment configuration