Avoid MessageDigest Field
ID |
java.avoid_message_digest_field |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Java |
Tags |
reliability |
Rationale
MessageDigest is stateful: every call to update(…) mutates the digest’s internal buffer, and the final digest() resets the state. The class is explicitly documented as not thread-safe.
Storing one in a shared field means concurrent calls to update()/digest() on the same instance interleave their state, producing wrong hashes or, worse, intermittent exceptions under load. The bug is usually invisible during single-threaded testing and only appears in production.
// Bad — shared mutable state; racy under concurrency
public class Hasher {
private final MessageDigest digest = MessageDigest.getInstance("SHA-256");
public byte[] hash(byte[] data) {
digest.update(data);
return digest.digest();
}
}
Remediation
Create a fresh MessageDigest per use, or hold it in a ThreadLocal:
// Good — local instance, no sharing across threads
public byte[] hash(byte[] data) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return digest.digest(data);
}
// Good — one instance per thread
private static final ThreadLocal<MessageDigest> DIGEST =
ThreadLocal.withInitial(() -> {
try { return MessageDigest.getInstance("SHA-256"); }
catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); }
});
For very hot code paths a pool of digests (with explicit checkout/return) is an alternative.