Hashing with MessageDigest in Java
A cryptographic hash function takes data of any size and produces a short, fixed-size value called a hash or digest, which acts as a fingerprint of the data. The same input always gives the same hash; changing even one character gives a completely different one; and there is no way to work backward from the hash to the data. In Java, hashing is done with the class java.security.MessageDigest, and the standard algorithm today is SHA-256. In this lesson, you will learn how to compute a hash, the properties that make hashes useful, where they are used, and why a plain hash is not the right way to store passwords.
Properties of a Cryptographic Hash
| Property | Meaning |
|---|---|
| Deterministic | The same input always gives the same hash |
| Fixed size | SHA-256 always produces 256 bits (32 bytes), for one byte or one gigabyte |
| One-way | You can't compute the input from the hash |
| Avalanche effect | A tiny change in the input changes about half the bits of the hash |
| Collision-resistant | It isn't feasible to find two different inputs with the same hash |
Methods of MessageDigest
| Method | Description |
|---|---|
MessageDigest.getInstance("SHA-256") | It is used to get a digest object for an algorithm. |
digest(bytes) | It hashes the bytes and returns the hash. |
update(bytes) | It adds data in pieces, for large inputs or streams. |
digest() | It finishes after update calls, returns the hash and resets the object. |
MessageDigest.isEqual(a, b) | It compares two hashes in constant time. |
getDigestLength() | It returns the hash size in bytes. |
A hash is bytes. To show or store it as text, convert it to hexadecimal with HexFormat.of().formatHex(bytes) or to Base64.
Example: Computing SHA-256
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
public class Hashing {
static String sha256(String text) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(text.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(hash);
}
public static void main(String[] args) throws Exception {
String a = sha256("Navika scored 91 marks");
String b = sha256("Navika scored 91 marks");
String c = sha256("Navika scored 92 marks");
System.out.println(a);
System.out.println("Same input, same hash? " + a.equals(b));
System.out.println(c);
System.out.println("One digit changed, same? " + a.equals(c));
System.out.println("Hex length: " + a.length()
+ " characters = 256 bits");
System.out.println("Empty text: " + sha256("").substring(0, 16)
+ "...");
System.out.println("Long text: "
+ sha256("x".repeat(1_000_000)).length() + " characters");
// Feeding data in pieces gives the same result as all at once.
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update("Navika scored ".getBytes(StandardCharsets.UTF_8));
md.update("91 marks".getBytes(StandardCharsets.UTF_8));
String pieces = HexFormat.of().formatHex(md.digest());
System.out.println("In two pieces, same? " + a.equals(pieces));
// Comparing two hashes safely.
byte[] x = HexFormat.of().parseHex(a);
byte[] y = HexFormat.of().parseHex(b);
System.out.println("MessageDigest.isEqual: "
+ MessageDigest.isEqual(x, y));
for (String name : new String[] {"MD5", "SHA-1", "SHA-256",
"SHA-512", "SHA3-256"}) {
int bits = MessageDigest.getInstance(name).getDigestLength() * 8;
System.out.println(name + ": " + bits + " bits");
}
}
}618f91c289f43eeadfed8328be71e0c5ee2954c98f0d337ef1999f7d71332ee0
Same input, same hash? true
7275d712c48d67dbb02012b7cccad6a24afdf3671740c786016c64488e9a526f
One digit changed, same? false
Hex length: 64 characters = 256 bits
Empty text: e3b0c44298fc1c14...
Long text: 64 characters
In two pieces, same? true
MessageDigest.isEqual: true
MD5: 128 bits
SHA-1: 160 bits
SHA-256: 256 bits
SHA-512: 512 bits
SHA3-256: 256 bitsExplanation:
- The first hash is 64 hexadecimal characters: 32 bytes, 256 bits.
- The same text hashed again gives the identical value.
- Changing "91" to "92" produces a hash with nothing visibly in common with the first. That is the avalanche effect.
- Size never changes: the empty string and a million-character string both give 64 characters.
updatelets you feed data piece by piece, for example while reading a large file, without holding it all in memory. The result is the same as hashing it at once.MessageDigest.isEqualcompares two hashes without revealing, through timing, how many leading bytes matched.- The last lines list common algorithms and their sizes.
Which Algorithm?
| Algorithm | Size | Status |
|---|---|---|
| MD5 | 128 bits | Broken. Collisions can be made in seconds. Don't use it for security. |
| SHA-1 | 160 bits | Broken. Practical collisions exist. Don't use it for security. |
| SHA-256, SHA-512 (the SHA-2 family) | 256, 512 bits | Secure; the standard choice |
| SHA3-256, SHA3-512 | 256, 512 bits | Secure; a different design, an alternative to SHA-2 |
What Hashing Is Used For
| Use | How |
|---|---|
| Checking a download | Compare the file's SHA-256 with the one the publisher lists |
| Detecting changes | Store a hash; if the data's hash later differs, the data changed |
| Finding duplicates | Equal hashes mean (practically certainly) equal content |
| Digital signatures | The hash of a document is signed, not the whole document |
| Version control and blockchains | Each item is identified by the hash of its content |
What Hashing Is Not
| It is not... | Because |
|---|---|
| Encryption | There is no key and no way to get the data back (see "Encryption Basics in Java") |
| Proof of who sent the data | Anyone can compute a hash. To prove origin, use an HMAC (a hash with a secret key) or a digital signature |
| Enough for storing passwords | SHA-256 is designed to be fast, so an attacker can try billions of guesses. Passwords need a slow, salted function (see "Storing Passwords Safely in Java") |
Common Mistakes
Calling text.getBytes() without a character set. Different machines may produce different bytes, and so different hashes. Always pass StandardCharsets.UTF_8.
Using MD5 or SHA-1 for anything security-related. Both are broken.
Storing passwords as a plain SHA-256 hash. It is far too fast to resist guessing.
Comparing hashes as strings with equals in security checks. Use MessageDigest.isEqual.
Sharing one MessageDigest between threads. It isn't thread-safe; create one per use.
Interview Corner
What is hashing and how do you compute a SHA-256 hash in Java?
Hashing maps data of any size to a fixed-size digest using a one-way function: the same input always yields the same digest, small input changes produce very different digests, and the input can't be recovered from the digest. In Java: MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] hash = md.digest(text.getBytes(StandardCharsets.UTF_8));, then convert to hex with HexFormat. It is used for integrity checks, deduplication and as part of digital signatures. MD5 and SHA-1 are broken and should be avoided.
What is the difference between hashing and encryption?
Hashing is one-way: it produces a fixed-size digest with no key, and the original data can't be recovered. Encryption is two-way: data is transformed with a key and can be restored with the right key. Hashing verifies integrity; encryption protects confidentiality.
Practice
- Easy: Print the SHA-256 and SHA-512 hashes of your name and compare their lengths.
- Harder: Write a method that returns the SHA-256 hash of a file, reading it in 8 KB chunks with
update.
Key Takeaways
Was this tutorial helpful?