Hashing with MessageDigest in Java

Naveen AnamaneniUpdated · 7 min read

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

PropertyMeaning
DeterministicThe same input always gives the same hash
Fixed sizeSHA-256 always produces 256 bits (32 bytes), for one byte or one gigabyte
One-wayYou can't compute the input from the hash
Avalanche effectA tiny change in the input changes about half the bits of the hash
Collision-resistantIt isn't feasible to find two different inputs with the same hash

Methods of MessageDigest

MethodDescription
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

Hashing.java▶ Try it
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");
        }
    }
}
Output
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 bits

Explanation:

  1. The first hash is 64 hexadecimal characters: 32 bytes, 256 bits.
  2. The same text hashed again gives the identical value.
  3. Changing "91" to "92" produces a hash with nothing visibly in common with the first. That is the avalanche effect.
  4. Size never changes: the empty string and a million-character string both give 64 characters.
  5. update lets 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.
  6. MessageDigest.isEqual compares two hashes without revealing, through timing, how many leading bytes matched.
  7. The last lines list common algorithms and their sizes.

Which Algorithm?

AlgorithmSizeStatus
MD5128 bitsBroken. Collisions can be made in seconds. Don't use it for security.
SHA-1160 bitsBroken. Practical collisions exist. Don't use it for security.
SHA-256, SHA-512 (the SHA-2 family)256, 512 bitsSecure; the standard choice
SHA3-256, SHA3-512256, 512 bitsSecure; a different design, an alternative to SHA-2

What Hashing Is Used For

UseHow
Checking a downloadCompare the file's SHA-256 with the one the publisher lists
Detecting changesStore a hash; if the data's hash later differs, the data changed
Finding duplicatesEqual hashes mean (practically certainly) equal content
Digital signaturesThe hash of a document is signed, not the whole document
Version control and blockchainsEach item is identified by the hash of its content

What Hashing Is Not

It is not...Because
EncryptionThere is no key and no way to get the data back (see "Encryption Basics in Java")
Proof of who sent the dataAnyone can compute a hash. To prove origin, use an HMAC (a hash with a secret key) or a digital signature
Enough for storing passwordsSHA-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

  1. Easy: Print the SHA-256 and SHA-512 hashes of your name and compare their lengths.
  2. Harder: Write a method that returns the SHA-256 hash of a file, reading it in 8 KB chunks with update.

Quick check: Hashing

4 questions
1How long is a SHA-256 hash?
2Can the original data be recovered from its hash?
3Which algorithm should NOT be used for security?
4Why isn't plain SHA-256 suitable for storing passwords?

Key Takeaways

Was this tutorial helpful?

Lesson 509 of 578