skip to content

How do you compute a cryptographic hash in Java using the MessageDigest API? Walk through the getInstance / update / digest lifecycle.

level: juniorimportance: must knowfreq 70%

answer

  1. getInstance → update* → digest
  2. digest() auto-resets the engine
  3. Always getBytes(StandardCharsets.UTF_8)
  4. Output length fixed by algorithm (SHA-256 = 32 bytes)
  5. Not thread-safe

basics

~10 s

Call MessageDigest.getInstance("SHA-256") to get an engine, feed bytes with update(...), then call digest() to get the final hash as a byte[]. digest() finishes the computation and resets the engine for reuse.

solid answer

~40 s

MessageDigest is the JCA engine for cryptographic hashing. You obtain an instance with the static factory MessageDigest.getInstance("SHA-256"), which throws NoSuchAlgorithmException if no provider supplies that algorithm. You then feed input incrementally with update(byte[]) — useful for streaming large data — and finally call digest(), which returns the fixed-length hash as a byte[] (32 bytes for SHA-256). There is also a convenience digest(byte[]) that does update + digest in one call. After digest() the object is automatically reset, so you can reuse it for the next message. Always specify the charset when hashing a String (e.g. text.getBytes(StandardCharsets.UTF_8)) so the result is platform-independent. MessageDigest is not thread-safe; use one instance per thread or create a fresh one per call.

code

java · 13 lines
java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;

public byte[] sha256(String text) throws NoSuchAlgorithmException {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    // Streaming-friendly: update can be called repeatedly.
    md.update(text.getBytes(StandardCharsets.UTF_8));
    byte[] hash = md.digest();   // 32 bytes; md is now auto-reset
    System.out.println(HexFormat.of().formatHex(hash));
    return hash;
}

go deeper

for a junior

Can name the three steps (getInstance, update, digest) and produce a SHA-256 hash of a string with an explicit UTF-8 charset.

for a middle

Knows digest() auto-resets, can hash streamed data via repeated update, handles NoSuchAlgorithmException, and renders bytes to hex/Base64.

for a senior

Discusses thread-safety, provider selection, fixed output length, and correctly distinguishes integrity hashing from password storage.

for a principal

Reasons about algorithm agility (configurable algorithm names), provider/policy implications across the fleet, and sets organization-wide guidance against MD5/SHA-1 and against MessageDigest-for-passwords.

## What problem this solves A **cryptographic hash function** takes an arbitrary-length input (bytes) and produces a fixed-length output called a **digest** (e.g. 256 bits = 32 bytes for SHA-256). Good cryptographic hashes are *deterministic* (same input → same output), *one-way* (you cannot recover the input from the digest), and *collision-resistant* (it is computationally infeasible to find two inputs with the same digest). They are used for integrity checks (did this file change?), fingerprinting, and as a building block inside signatures and HMACs. ## The JCA and what MessageDigest is Java exposes cryptography through the **JCA (Java Cryptography Architecture)** — a provider-based framework. `java.security.MessageDigest` is the *engine class* for hashing. "Engine class" means it is an abstract façade: you never call a constructor; instead a **provider** (a registered implementation, e.g. the built-in `SUN` provider) supplies the actual algorithm behind the scenes. This indirection lets you swap algorithms or providers without changing your code. ## The lifecycle, step by step 1. **Obtain an instance** with the static factory method: ```java MessageDigest md = MessageDigest.getInstance("SHA-256"); ``` `getInstance` takes the algorithm name as a string. If no installed provider offers that algorithm, it throws the *checked* exception `NoSuchAlgorithmException`. Standard names include `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, and the legacy `MD5` / `SHA-1` (which are broken for security use). There is an overload `getInstance("SHA-256", "SUN")` to pin a specific provider. 2. **Feed input** with `update`: ```java md.update(part1); // byte[] md.update(part2); ``` `update` accumulates bytes into the running computation. You can call it many times, which is how you hash data that arrives in chunks (a file read in blocks, a network stream) without holding it all in memory. 3. **Finish** with `digest`: ```java byte[] hash = md.digest(); // 32 bytes for SHA-256 ``` `digest()` performs the final padding and computation and returns the digest. **Important: after `digest()` the engine is automatically reset** to its initial state, so the same object can immediately hash a new message. There is a convenience overload `md.digest(input)` that is equivalent to `md.update(input); md.digest();` for the common one-shot case. ## Hashing a String correctly A `String` is characters, not bytes, so you must encode it explicitly and *always specify a charset*: ```java byte[] hash = md.digest(text.getBytes(StandardCharsets.UTF_8)); ``` Using the no-arg `getBytes()` relies on the platform default charset, which can differ between machines and silently produce different digests — a classic portability bug. ## Useful facts and edge cases - **Output length is fixed by the algorithm**, regardless of input size: SHA-256 → 32 bytes, SHA-512 → 64 bytes. - **`getDigestLength()`** returns that length in bytes. - **`reset()`** abandons any buffered input and returns the engine to its initial state (useful if you started feeding data and want to start over without calling `digest`). - **Not thread-safe.** A single `MessageDigest` instance holds mutable internal state, so two threads sharing one will corrupt each other's computation. Use a fresh instance per call, a `ThreadLocal`, or per-thread instances. - The raw `byte[]` is usually rendered to **hex** or **Base64** for display/storage (e.g. `HexFormat.of().formatHex(hash)` in Java 17+). ## What this API is NOT for MessageDigest is a *raw, fast, unsalted* hash. **Do not use it to store passwords** — fast hashes are brute-forceable. For passwords use a deliberately slow, salted KDF such as PBKDF2, bcrypt, scrypt, or Argon2. MessageDigest is for *integrity and fingerprinting*, not secret-at-rest protection.

  • What does digest() do to the instance after it returns the hash?
    It automatically resets the engine to its initial state, so the same object can immediately be reused to hash a new message; you do not need to recreate it.
  • Why must you pass a charset when hashing a String?
    A String is characters; getBytes() with no charset uses the platform default, which can vary across machines and yield different digests. Specify UTF-8 for a deterministic, portable result.

saying these in an interview costs you the question

  • Forgetting that digest() resets the instance and thinking you must create a new one each time
  • Using getBytes() without a charset (platform-dependent digests)
  • Sharing one MessageDigest across threads
  • Using MessageDigest to store passwords instead of a slow salted KDF

context