Warm-up

Crack your own hashes

10 minjunior110 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • An unsalted hash lets the table itself reveal shared passwords
  • A known hash is a lookup key, not a puzzle
  • Speed is the attacker's budget, and a KDF removes it
  • SecureRandom, not Random, and MessageDigest.isEqual, not equals

Starter

Starter.java
import java.util.*;

/**
 * Warm-up: be the attacker for ten minutes.
 *
 * Everything here is the JDK. PBKDF2 has been in java.base since Java 6, so
 * the correct version needs no dependency — which is worth knowing before
 * someone tells you a fast hash was the only option.
 */
public class Starter {

    static String sha256(String value) throws Exception {
        var md = java.security.MessageDigest.getInstance("SHA-256");
        return HexFormat.of().formatHex(md.digest(value.getBytes("UTF-8")));
    }

    public static void main(String[] args) throws Exception {
        // TODO 1: hash the same password for two different users. Predict
        // whether the stored values differ, then look.
        System.out.println("alice : " + sha256("hunter2"));
        System.out.println("bob   : " + sha256("hunter2"));

        // TODO 2: search the web for the hash printed above. It is the
        // SHA-256 of a well-known password and it is already indexed. Say
        // what that means for a leaked table of bare hashes.

        // TODO 3: measure. Loop for one second hashing "guess" + i and count
        // how many you complete. That number is an attacker's rate per core —
        // and a GPU does several orders of magnitude more.

        // TODO 4: now write the slow version. The JDK gives you:
        //
        //   var spec = new javax.crypto.spec.PBEKeySpec(password, salt, 210_000, 256);
        //   javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
        //       .generateSecret(spec).getEncoded();
        //
        // Measure the same one-second budget against it. Compare the two rates.

        // TODO 5: generate two salts with java.security.SecureRandom and hash
        // "hunter2" with each. Confirm the results differ, and say what that
        // stops an attacker doing.

        // TODO 6: now generate five salts with `new Random(System.currentTimeMillis())`
        // in a loop and print them. Explain the result in one sentence.

        // TODO 7: verify a login — re-hash the supplied password with the
        // STORED salt and compare with MessageDigest.isEqual. Say why not
        // Arrays.equals, and name two other things you would compare the same
        // way.
    }
}

Run it locally:

cd exercises/java/authentication/password-storage/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You produced two identical hashes from two users with one password
  • You measured a guess rate for SHA-256 and for PBKDF2
  • You showed two salts producing different hashes for the same password
  • You can state what the salt fixes and what it does not

← Back to How should a password be stored?