The labs on this site need JavaScript to run. The same content is written out below.
Fundamentals › Security
What is a hash, and why do we store passwords as hashes?
A hash function is a calculation that gives a mixed-up value of a fixed length, whatever text you put in. The sha256("pass1234") in the code is the line that puts pass1234 into the hash function called SHA-256, and the value that comes out is 64 characters of 0-9 and a-f. A hash function has three properties. If you put in the same text, you always get the same value. If even one character is different, you get a completely different value. And you cannot calculate the original text backwards from the value that came out. So at sign-up, the server stores the hash value, not the password itself, and at login, it hashes the entered text the same way and only checks whether it is the same as the stored value. In this lab, you enter the right password, a password that differs by one character, and the stored value itself, once for a server that hashes and stores the password and once for a server that stores it as it is, and you see what happens to the login. (A real service does not store passwords with SHA-256. It uses a dedicated function. See the explanation below for details.)
Checked against real execution · Compiled and ran 6 combinations on JDK 21 with javax.tools and checked against the results. The SHA-256 values were also checked against the standard examples, Node.js, and openssl
// At sign-up: store the hash value, not the password itself
String saved = sha256("pass1234");
// At login: hash the input the same way and compare it with the stored value
String input = "pass1234";
boolean ok = sha256(input).equals(saved);
System.out.println(saved);
System.out.println(ok);
The compiler and the runtime are JDK 21.0.12.1 (javac 21). For all 6 combinations, we wrapped the code on the screen in the frame of the import lines, the sha256 helper method, and the main method to make a source, compiled it with javax.tools, loaded it with a new class loader, and ran Main.main. The import lines, main, and the sha256 method are shortened on the screen, and the shortened sha256 method calculates SHA-256 with MessageDigest and turns it into 64 lowercase hexadecimal characters. What we checked against is whether the code on the screen is the same text as that part of the source that ran, the saved and ok that the program printed, and the value calculated from the input (we got it by calling the sha256 method of the compiled Main on the input). The values in the result table are shortened to the first 12 characters of the stored value and of the value calculated from the input, followed by "… (64 chars)", and we checked the whole values before shortening in the same way. That no calculation happens on the side that stores the password as it is was confirmed by the fact that there is no sha256 call in that code. That the calculation follows the standard was checked by calculating two standard examples of SHA-256 (the message "abc" and a 56-character message) in Java and comparing them with the values in the documents: the value for "abc" is from the NIST SHA-256 example document and the RFC 6234 test vectors, and the value for the 56-character message is from the NIST example document. We also calculated the same 8 inputs (including empty text, 1 character, and 100 characters) with crypto of Node.js and openssl dgst -sha256 on the PC used to write this lab, and confirmed that the values of the three implementations were all the same. We also measured the facts that the step sentences rely on. Hashing the same input twice gives the same value, the hashes of pass1234 and pass1235 differ in 61 of the 64 characters (a value observed for these inputs), and the result is 64 characters even if the input is 0, 1, or 100 characters. We opened the documents ourselves and checked them. NIST FIPS 180-4 (Secure Hash Standard) says in section 1 that SHA-256 and the others are iterative one-way hash functions that make a compressed value called a message digest from an input (the message), and that if the message changes, a different digest comes out with very high probability, the table in section 1 sets the digest size of SHA-256 to 256 bits (64 hexadecimal characters), and the explanation at the beginning of the document says that these are called "secure" hash algorithms in the sense that finding a message that matches a given digest and finding two messages that give the same digest is computationally infeasible. The OWASP Password Storage Cheat Sheet says not to store passwords in plaintext but to hash them, says that a hash is a one-way function so it cannot be turned back into the original password (it is not encryption, it is hashing), and says that weak and common passwords can be found with lists of common passwords or by trying every candidate one by one. In this lab, "cannot be calculated backwards" does not mean it has been proven mathematically, it means there is no known practical method, and a short and common password can be found by hashing the candidates one by one and comparing them with the stored value. This lab used SHA-256, a fast general-purpose hash function, as it is to show the properties of a hash. When a real service stores passwords, it does not use SHA-256 as it is, it uses password-only functions (BCrypt, Argon2, and so on) that are made slow on purpose and mix in a value that differs for each user (a salt). The OWASP document also says that a fast hash algorithm such as SHA-256 is not suitable for storing passwords, recommends Argon2id first, lists scrypt when that cannot be used, bcrypt for legacy systems, and PBKDF2 when FIPS certification is needed, and tells you to add a salt that is unique to each user. The reason and the behavior are covered in the in-depth lab "Password hashing". The code on the screen writes sign-up and login one after the other in a single run. In a real service, sign-up and login are separate requests, saved is kept in a database, and we assumed that the server receives the password text that the user entered and hashes it. A method that hashes the input beforehand on the user's device, timing attacks when comparing hash values directly, salts and slow hashes, and hash collisions are not covered. The hash function in this lab is a cryptographic hash function, and it is used for a different purpose from the hash code of HashMap.
Previous lab: Tokens · Next lab: Encryption
Struck-through text was removed from the first version of the code; highlighted text was added.