Back

C2: Use Cryptography to Protect Data - Code Examples

Each pair below shows an insecure use of cryptography and the secure version in the same language. The examples focus on the mistakes that dominate real findings: reversible or fast password hashing, broken algorithms and modes, predictable randomness, and hardcoded keys.

⚠️ EDUCATIONAL PURPOSE ONLY — the insecure snippets exist to be recognised and removed, never shipped.

Python

Password Storage

Insecure

import hashlib

def store_password(pw: str) -> str:
    # MD5, no salt: cracked in seconds; identical passwords collide
    return hashlib.md5(pw.encode()).hexdigest()

def verify(pw: str, stored: str) -> bool:
    return hashlib.md5(pw.encode()).hexdigest() == stored   # also timing-unsafe

Secure

from argon2 import PasswordHasher          # pip install argon2-cffi
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher(time_cost=3, memory_cost=64 * 1024, parallelism=1)

def store_password(pw: str) -> str:
    # Argon2id: slow + memory-hard; salt generated and embedded automatically
    return ph.hash(pw)

def verify(pw: str, stored: str) -> bool:
    try:
        ph.verify(stored, pw)             # constant-time verify inside the library
        return True
    except VerifyMismatchError:
        return False

Encrypting Data at Rest

Insecure

from Crypto.Cipher import AES             # ECB + hardcoded key: two mistakes at once

KEY = b"1234567890123456"                 # hardcoded, committed to the repo

def encrypt(data: bytes) -> bytes:
    cipher = AES.new(KEY, AES.MODE_ECB)   # ECB leaks plaintext patterns; no integrity
    pad = 16 - len(data) % 16
    return cipher.encrypt(data + bytes([pad]) * pad)

Secure

import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def encrypt(data: bytes, key: bytes, aad: bytes = b"") -> bytes:
    # key comes from the KMS at runtime - never hardcoded
    nonce = os.urandom(12)                        # CSPRNG; unique per message
    return nonce + AESGCM(key).encrypt(nonce, data, aad)   # AES-256-GCM: confidential + authenticated

def decrypt(blob: bytes, key: bytes, aad: bytes = b"") -> bytes:
    nonce, ct = blob[:12], blob[12:]
    return AESGCM(key).decrypt(nonce, ct, aad)    # raises on tamper - fail closed

Generating Tokens

Insecure

import random, time

def reset_token() -> str:
    random.seed(int(time.time()))                 # predictable seed
    return "".join(random.choice("0123456789abcdef") for _ in range(32))  # guessable

Secure

import secrets

def reset_token() -> str:
    return secrets.token_urlsafe(32)              # CSPRNG, ~256 bits of entropy

# Compare tokens in constant time to avoid timing leaks:
import hmac
def token_matches(a: str, b: str) -> bool:
    return hmac.compare_digest(a, b)

Node.js

Password Storage

Insecure

const crypto = require('crypto');

function storePassword(pw) {
  // SHA-1, unsalted: fast hash, billions of guesses/sec on a GPU
  return crypto.createHash('sha1').update(pw).digest('hex');
}

Secure

const argon2 = require('argon2');            // npm i argon2

async function storePassword(pw) {
  // Argon2id with sensible memory/time cost; salt handled internally
  return argon2.hash(pw, { type: argon2.argon2id, memoryCost: 65536, timeCost: 3 });
}

async function verify(hash, pw) {
  return argon2.verify(hash, pw);            // constant-time, returns boolean
}

Encrypting Data at Rest

Insecure

const crypto = require('crypto');

const KEY = 'hardcoded-app-secret-key-123';   // hardcoded secret in source

function encrypt(text) {
  // createCipher (deprecated): weak key derivation, no IV control, no auth tag
  const cipher = crypto.createCipher('aes-256-cbc', KEY);
  return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
}

Secure

const crypto = require('crypto');

// key: 32 random bytes fetched from the KMS at runtime (Buffer), never hardcoded
function encrypt(plaintext, key) {
  const iv = crypto.randomBytes(12);                       // unique nonce per message
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();                         // integrity tag
  return Buffer.concat([iv, tag, ct]);                     // store all three
}

function decrypt(blob, key) {
  const iv = blob.subarray(0, 12), tag = blob.subarray(12, 28), ct = blob.subarray(28);
  const d = crypto.createDecipheriv('aes-256-gcm', key, iv);
  d.setAuthTag(tag);
  return Buffer.concat([d.update(ct), d.final()]).toString('utf8');  // throws on tamper
}

Generating Tokens

Insecure

function apiKey() {
  // Math.random is NOT a CSPRNG - predictable, low entropy
  return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
}

Secure

const crypto = require('crypto');

function apiKey() {
  return crypto.randomBytes(32).toString('base64url');     // 256-bit CSPRNG token
}

// Constant-time comparison for secrets:
function safeEqual(a, b) {
  const ba = Buffer.from(a), bb = Buffer.from(b);
  return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
}

Java

Password Storage

Insecure

import java.security.MessageDigest;

String storePassword(String pw) throws Exception {
    // MD5, unsalted, hex - trivially cracked
    byte[] d = MessageDigest.getInstance("MD5").digest(pw.getBytes("UTF-8"));
    StringBuilder sb = new StringBuilder();
    for (byte b : d) sb.append(String.format("%02x", b));
    return sb.toString();
}

Secure

// Spring Security Crypto - Argon2 (or BCryptPasswordEncoder)
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;

Argon2PasswordEncoder enc = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();

String storePassword(String pw) {
    return enc.encode(pw);              // slow, salted, self-describing hash string
}

boolean verify(String pw, String stored) {
    return enc.matches(pw, stored);     // constant-time verification
}

Encrypting Data at Rest

Insecure

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

// Hardcoded key + ECB mode + DES: three broken choices
byte[] KEY = "8bytekey".getBytes();

byte[] encrypt(byte[] data) throws Exception {
    SecretKeySpec k = new SecretKeySpec(KEY, "DES");
    Cipher c = Cipher.getInstance("DES/ECB/PKCS5Padding");   // DES + ECB: broken
    c.init(Cipher.ENCRYPT_MODE, k);
    return c.doFinal(data);
}

Secure

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

// key: 32 bytes retrieved from a KMS/keystore at runtime, never hardcoded
byte[] encrypt(byte[] data, byte[] key) throws Exception {
    byte[] iv = new byte[12];
    SecureRandom.getInstanceStrong().nextBytes(iv);         // CSPRNG nonce, unique per message
    Cipher c = Cipher.getInstance("AES/GCM/NoPadding");     // AES-256-GCM: AEAD
    c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"),
           new GCMParameterSpec(128, iv));                  // 128-bit auth tag
    byte[] ct = c.doFinal(data);
    byte[] out = new byte[iv.length + ct.length];
    System.arraycopy(iv, 0, out, 0, iv.length);
    System.arraycopy(ct, 0, out, iv.length, ct.length);     // store iv || ciphertext+tag
    return out;
}

Generating Tokens

Insecure

import java.util.Random;

String sessionId() {
    // java.util.Random is a linear PRNG - predictable, not for security
    return Long.toHexString(new Random().nextLong());
}

Secure

import java.security.SecureRandom;
import java.util.Base64;

String sessionId() throws Exception {
    byte[] buf = new byte[32];
    SecureRandom.getInstanceStrong().nextBytes(buf);        // CSPRNG, 256-bit token
    return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
}

// Constant-time comparison:
boolean safeEquals(byte[] a, byte[] b) {
    return java.security.MessageDigest.isEqual(a, b);
}

What Changed, and Why

ConcernInsecureSecure
PasswordsMD5/SHA-1, unsalted, fast, timing-unsafeArgon2id/bcrypt, salted, slow, constant-time verify
Encryption modeECB / unauthenticated CBC / DESAES-256-GCM (AEAD): confidential + tamper-evident
Nonce / IVFixed, absent, or library-derivedFresh CSPRNG nonce per message, stored with ciphertext
RandomnessMath.random(), java.util.Random, time-seededCSPRNG (secrets, randomBytes, SecureRandom)
KeysHardcoded in source / committedFetched from KMS/keystore at runtime, rotated
Comparisons== / equals (timing leak)Constant-time compare

Key Takeaways

  1. Hash passwords, don't fast-hash or encrypt them — Argon2id/bcrypt with per-user salt.
  2. Use authenticated encryption — AES-256-GCM, never ECB or bare CBC, never DES/RC4.
  3. Never reuse a nonce, never hardcode a key — CSPRNG nonces and KMS-managed keys.
  4. All security randomness comes from a CSPRNG — not Math.random() or java.util.Random.
  5. Let vetted libraries make the safe choice — and compare secrets in constant time.

Next Steps