package support; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.Map; /** * MOAD-0006: CWE-257 — A Glass Safe. * * Defect: credentials stored in recoverable form — plaintext, Base64 encoding, * or reversible encryption. Anyone with read access to the credential store * (DB dump, log file, backup) can reconstruct the original secret. * * Variant: Mailman 2.x actively emails list passwords to members on a monthly * schedule, because it stores them in recoverable form. * * Fix: one-way hashing with salt (SHA-256 + random salt shown here; bcrypt or * Argon2 preferred in production). The stored value cannot be reversed to * recover the original credential — only verification (hash-and-compare) works. * * Scanner detects: Base64.encode/decode on password-shaped values, or direct * string equality comparison between a stored field and user-supplied input. */ public class Moad0006Algorithm { // ── Defective: Base64 storage (reversible) ──────────────────────────────── /** * Credential store that encodes passwords with Base64. * DEFECT: Base64 is an encoding, not a hash. decode() recovers the original. */ public static final class DefectiveCredentialStore { private final Map store = new HashMap<>(); /** Store password encoded as Base64. DEFECT: reversible. */ public void storePassword(String username, String password) { store.put(username, Base64.getEncoder().encodeToString( password.getBytes(StandardCharsets.UTF_8))); } /** Verify by encoding the candidate and comparing. */ public boolean verify(String username, String password) { String stored = store.get(username); if (stored == null) return false; String candidate = Base64.getEncoder().encodeToString( password.getBytes(StandardCharsets.UTF_8)); return stored.equals(candidate); } /** * DEFECT: attacker with DB read access calls this to recover original password. * Returns the plaintext password — the glass safe opens. */ public String extractPassword(String username) { String stored = store.get(username); if (stored == null) return null; return new String(Base64.getDecoder().decode(stored), StandardCharsets.UTF_8); } /** Exposes raw stored value (what a DB dump would show). */ public String rawStored(String username) { return store.get(username); } } // ── Fixed: SHA-256 + salt (one-way) ────────────────────────────────────── private static final SecureRandom RNG = new SecureRandom(); /** Generate a fresh 16-byte random salt. */ public static byte[] generateSalt() { byte[] salt = new byte[16]; RNG.nextBytes(salt); return salt; } /** Hash password with salt using SHA-256. */ public static byte[] sha256(String password, byte[] salt) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(salt); return md.digest(password.getBytes(StandardCharsets.UTF_8)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); // SHA-256 always available in Java } } /** * Credential store that hashes passwords with SHA-256 + random salt. * FIX: no extraction method exists — stored value cannot be reversed. */ public static final class FixedCredentialStore { private final Map salts = new HashMap<>(); private final Map hashes = new HashMap<>(); /** Store password as SHA-256(salt || password). */ public void storePassword(String username, String password) { byte[] salt = generateSalt(); byte[] hash = sha256(password, salt); salts.put(username, salt); hashes.put(username, hash); } /** Verify by hashing candidate with stored salt and comparing. */ public boolean verify(String username, String password) { byte[] salt = salts.get(username); byte[] hash = hashes.get(username); if (salt == null || hash == null) return false; return Arrays.equals(sha256(password, salt), hash); } /** Exposes raw stored hash (what a DB dump would show — unrecoverable). */ public byte[] rawHash(String username) { return hashes.get(username); } /** Exposes raw salt (what a DB dump would show). */ public byte[] rawSalt(String username) { return salts.get(username); } // FIX: no extractPassword() method. DB access gives hash+salt, not plaintext. } }