Java: SHA-256 hashing for integrity

Compute a SHA-256 digest with MessageDigest and see that a tiny change produces a completely different value.

Java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

class Main {
  static String hex(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
      sb.append(String.format("%02x", b));
    }
    return sb.toString();
  }

  public static void main(String[] args) throws Exception {
    MessageDigest sha = MessageDigest.getInstance("SHA-256");
    String a = hex(sha.digest("abc".getBytes(StandardCharsets.UTF_8)));
    String b = hex(sha.digest("abd".getBytes(StandardCharsets.UTF_8)));
    System.out.println(a);
    System.out.println(b);
    System.out.println(MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), a.getBytes(StandardCharsets.UTF_8)));
  }
}
Output
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9
true

Explanation

A cryptographic hash function turns any input into a fixed-size digest. It is deterministic, one-way, and designed so that a tiny change in the input changes the digest unpredictably. SHA-256 produces 32 bytes, shown here as 64 hexadecimal characters.

Hashes are used to check integrity: if a downloaded file's digest matches the published one, the file was not corrupted or altered. MessageDigest.isEqual compares two byte arrays in constant time, which avoids leaking how many leading bytes matched. A plain fast hash is not suitable for storing passwords.

Explore the dry run
Guided walkthroughReady
1import java.nio.charset.StandardCharsets;
2import java.security.MessageDigest;
3
4class Main {
5 static String hex(byte[] bytes) {
6 StringBuilder sb = new StringBuilder();
7 for (byte b : bytes) {
8 sb.append(String.format("%02x", b));
9 }
10 return sb.toString();
11 }
12
13 public static void main(String[] args) throws Exception {
14 MessageDigest sha = MessageDigest.getInstance("SHA-256");
15 String a = hex(sha.digest("abc".getBytes(StandardCharsets.UTF_8)));
16 String b = hex(sha.digest("abd".getBytes(StandardCharsets.UTF_8)));
17 System.out.println(a);
18 System.out.println(b);
19 System.out.println(MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), a.getBytes(StandardCharsets.UTF_8)));
20 }
21}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Create a SHA-256 digest engine.—
—
Hash the bytes of abc. This is a standard SHA-256 test vector.32 bytes → 64 hex characters
—
Print the digest of abc.32 bytes → 64 hex characters
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Changing one letter produces a completely different digest.32 bytes → 64 hex characters
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9
A constant-time comparison of equal values returns true.32 bytes → 64 hex characters
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9
true

Cost and common mistakes

O(m) time for an m-byte input and a fixed 32-byte output.

  • Always convert text to bytes with an explicit charset such as UTF-8, otherwise the digest depends on the platform default.
  • Plain SHA-256 is fast, which helps attackers who guess passwords. Use a slow, salted password hash for passwords.
  • MD5 and SHA-1 are broken for collision resistance and must not be used for new security work.

Try it yourself

How can you check a downloaded file against a published digest?

Reveal answer and reasoning

Hash the file's bytes with the same algorithm and compare the hex result with the published value; they must match exactly.

Further reading