Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Fundamentals › Security

Hashing

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

Concepts in this lab

Hash function
A hash function is a calculation that gives a mixed-up value of a fixed length, whatever length of text (data) you put in, and that value is called a hash value or a digest. SHA-256 gives a 256-bit value, that is, 64 hexadecimal characters, whether the input is 1 character or 100 characters. The same input always gives the same value, and if the input changes even a little, a completely different value comes out with very high probability. These terms mean a cryptographic hash function such as SHA-256, which is used for a different purpose from the hash code used in the data structure HashMap.
One-way function
A hash function being one-way means that it is easy to calculate the value from the input, but it is made so that finding the original input backwards from the value that came out is not practically possible. The NIST standard explains that it calls these functions secure because finding an input that matches a given hash value, and finding two different inputs that give the same hash value, is computationally infeasible. This is not proven mathematically, it means no method is known. One-way is different from encryption. With encryption you can turn the value back if you have the key, but a hash has no key to turn it back. Also, a short and common input can be found by hashing the candidates one by one and comparing them.
Password hashing
Password hashing is the method of storing the hash value of a password at sign-up, not the password itself. At login, you put the entered text into the same hash function, and if the value that comes out is the same as the stored value, the input is taken to be the same as the password at sign-up. The server can check that a password is right without knowing the original password. Even if the stored value leaks, the original password is not revealed right away, and even if you put that value in the password field, it is hashed once more and becomes a different value, so you cannot log in. OWASP says to hash passwords, not store them in plaintext, but it says that using a fast hash function such as SHA-256 as it is is not suitable, and to use a slow password-only function such as Argon2id with a salt that is unique to each user.
Hash function and salt
A hash function turns an input into a fixed-length value, and the original input cannot be recovered from the result. For passwords, the hash value is stored instead of the plain text, so the plain text is not exposed even if the data leaks. A salt is a random value that is added to each password and is different for each one. It makes the same password produce a different hash, which blocks attacks that use precomputed lists of hashes.

Try these

Running with the default settings

  1. The String saved = sha256("pass1234"); line runs. At the moment of sign-up, the server puts the password "pass1234" that the user chose into a hash function. A hash function is a calculation that gives a mixed-up value of a fixed length, whatever text you put in, and here it uses SHA-256. sha256 is a helper method that does this calculation (the code is shortened).
  2. The calculation gives a mixed-up value 64 characters long. It is 64 characters of 0-9 and a-f, and the first 12 are bd94dcda26fc. Whether the input is 1 character or 100 characters, the value that comes out is always 64 characters, and if you put in the same text, you get the same value whenever you calculate.
  3. The server stores only this 64-character value in saved. It does not store the original password pass1234. A hash function is made so that finding the original text from the value that came out is not practically possible, so you cannot learn pass1234 by calculating backwards from the stored value.
  4. The String input = "pass1234"; line runs. Now it is login. The user enters pass1234 in the password field.
  5. sha256(input) runs. The server puts the entered text into the same hash function as at sign-up. The server does not have the password from sign-up, so the only way is to hash the input the same way and compare it with the stored value.
  6. The value that came out is bd94dcda26fc…, which is the same as the value that came out of pass1234 at sign-up. This is because the same text always gives the same value.
  7. sha256(input).equals(saved) runs. Comparing the value calculated from the input with the stored value, they are the same, so ok is true. Being the same means that the input was the same as the password at sign-up, so login succeeds. The server did not store the original password, but it could check that the input was right just by comparing hash values.
  8. System.out.println(saved) prints the 64-character stored value, and System.out.println(ok) prints true.

Result of this run

Code

// 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);

Assumptions of this model

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

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these