Java: SHA-256 hashing for integrity
Compute a SHA-256 digest with MessageDigest and see that a tiny change produces a completely different value.
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)));
}
}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
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))); }}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
| Step / line | What happens | State after step | Output 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.