SHA-256 in Python
SHA-256 is built into Python. Python's hashlib module, part of the standard library since Python 2.5, provides access to every major digest algorithm through a single consistent interface.1 The module wraps native OpenSSL implementations on most platforms, so performance matches C-level code. Because hashlib operates on bytes rather than strings, you encode text to UTF-8 before hashing - the .encode('utf-8') call is essentially required for any string input.2
Integrating hashlib SHA-256 into Django and Flask applications
Integrating SHA-256 into a Python web application covers several common patterns: generating ETag headers for HTTP caching, computing idempotency keys for POST requests, and creating content-addressed storage keys before writing files to disk. For Django views, compute hashlib.sha256(response_content).hexdigest() after generating the response body and set it as the ETag header. The browser sends this value back as If-None-Match on subsequent requests; compare it to the current content hash and return 304 Not Modified when they match, saving bandwidth without additional caching infrastructure.
Flask applications use the same pattern in a make_response() wrapper. Pass the response data bytes directly to hashlib.sha256(): Flask response data is always bytes at the point where you construct the response object. For binary responses like generated PDFs or images, skip the .encode() call that text hashing requires; the bytes are already in the form hashlib expects.
Choosing SHA-256 for content fingerprints
In Django or Flask, SHA-256 works best when the fingerprint has one clear job: proving that the bytes you compare are the same bytes you stored earlier. It should not be treated as a content identifier across untrusted parties unless you also store the algorithm, because MD5, SHA-1, and SHA-256 can all appear as 32, 40, or 64-character strings in logs. CapyToolkit's Hash Generator keeps the output format explicit for this reason, so the generated value is easier to document beside the system that consumes it.
You should record the exact algorithm next to every fingerprint you store, because a bare hex string gives no hint about how it was computed. When a teammate encounters a 64-character digest months later, the algorithm name turns an opaque value into a verifiable record of intent. CapyToolkit surfaces the algorithm label for that reason, so the person maintaining the integration can confirm the format without reverse-engineering the surrounding code.
When your CI pipeline downloads external binaries, pin their SHA-256
When your CI pipeline downloads a binary tool, model weights, or a data archive from an external URL, verifying the SHA-256 hash before using the download protects against a corrupted transfer and some supply chain substitution attacks. Download the file, compute its hash using the chunked pattern from the examples above, and compare the result against a pinned expected value using hmac.compare_digest().3 Abort the build immediately if the hashes do not match rather than logging a warning and continuing.
Pinning expected hashes in infrastructure-as-code
For infrastructure-as-code repositories, store the expected SHA-256 of each external binary as a named constant alongside the download URL. This makes the expectation auditable: a reviewer can fetch the same URL independently and confirm the hash matches the constant. When the upstream binary changes and the hash no longer matches, the CI job fails. This failure requires an explicit update to the pinned constant, which forces a deliberate review of the new binary before it enters your build pipeline.
In pytest, assert on known SHA-256 vectors to catch encoding bugs
In a pytest test suite, assert on the known SHA-256 hash of the empty string as a sanity check that runs on every CI invocation. The SHA-256 of an empty byte string is e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.4 This single assertion catches algorithm selection bugs, incorrect encoding steps, and library replacement issues in one line. Beyond the empty-string vector, include a test for a UTF-8 string that contains multi-byte characters to confirm the .encode('utf-8') step works correctly on your platform.
For file hashing tests, create a temporary file with tmp_path (a pytest built-in fixture) and write known content into it, then assert the computed hash matches a precomputed expected value. Because SHA-256 is deterministic, the same bytes always produce the same digest on every platform and Python version. Any platform-specific deviation traces directly to a charset or byte-order inconsistency in input preparation, not in hashlib itself.
Checking file hashes in security-sensitive tests
Add one test that writes binary content with known bytes, including a non-ASCII marker if your application handles international paths or metadata. That exercise proves the file path is opened in binary mode and that the same chunked loop used in production code also works in tests. Keep the expected hash literal beside the test so a reviewer can see the exact byte contract without running the suite first.
Notes
Call hashlib.sha256(data) with a bytes object to get a hash instance. The instance exposes .hexdigest() for a lowercase 64-character hex string, .digest() for the raw 32 bytes, and .update() for incremental hashing.
One common gotcha: hashlib.sha256() with no arguments creates an empty-digest object that returns the hash of zero bytes. Pass data directly, or call .update(data) before .hexdigest(). For large files, feed data in 65536-byte chunks rather than reading the entire file into memory - update() accumulates bytes across multiple calls, so memory usage stays constant regardless of file size.
Examples
Hash a string
import hashlib digest = hashlib.sha256(b"Hello, World!").hexdigest() print(digest) # 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3
Hash a UTF-8 string
import hashlib
text = "café au lait"
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
print(digest) # 64 lowercase hex characters Hash a file in chunks
import hashlib
h = hashlib.sha256()
with open("archive.zip", "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
print(h.hexdigest()) SHA-256 as HMAC digestmod
import hmac, hashlib mac = hmac.new(b"secret_key", b"payload", hashlib.sha256) print(mac.hexdigest()) # 64-char hex HMAC-SHA256
Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
import hashlib digest = hashlib.sha256(b"Hello, World!").hexdigest() print(digest) # 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3
- 1.
Python Software Foundation, "10.1 hashlib - Secure hashes and message digests," docs.python.org, December 2008. https://docs.python.org/2.5/lib/module-hashlib.html
- 2.
Python Software Foundation, "hashlib - Secure hashes and message digests," docs.python.org, accessed June 2026. https://docs.python.org/3/library/hashlib.html
- 3.
Python Software Foundation, "hmac - Keyed-Hashing for Message Authentication," github.com, accessed June 2026. https://github.com/python/cpython/blob/main/Doc/library/hmac.rst
- 4.
NIST, "Secure Hash Standard (SHS) — Example Algorithms," csrc.nist.gov, August 2015. https://csrc.nist.gov/groups/ST/toolkit/examples
No. hashlib is part of Python's standard library and is available in every Python environment from version 2.5 onward. You do not need pip or any external package. For local browser checks, CapyToolkit can show a Python hashlib-style result without asking you to install a dependency or send your input anywhere. That keeps the example close to the standard-library pattern while the tool handles the encoding and output formatting.
hashlib always works on bytes, not str objects. Passing a str raises TypeError. Calling .encode("utf-8") converts the string to bytes in the most portable encoding, which is the right default for any text-based content.
.hexdigest() returns a 64-character lowercase hex string - the format most tools and documentation expect. .digest() returns the raw 32 bytes, which is smaller and useful when feeding the hash into another algorithm or writing binary output.
Hash the downloaded file with the chunked pattern shown in the examples above, then compare the result to the published value using a constant-time comparison: hmac.compare_digest(computed, expected). This avoids timing attacks even though SHA-256 checksums are not security-critical in this context.
SHA-256 is secure for integrity verification, digital signatures, and HMAC. It is not appropriate for password storage - use bcrypt, scrypt, or Argon2 for passwords, since those algorithms are intentionally slow and include a random salt.
SHA-256 in JavaScript (Web Crypto API)
In the browser, SHA-256 is a native operation rather than a package you pull into a bundle. The Web Crypto API's crypto.subtle.digest method handles SHA-256, SHA-384, and SHA-512 without library dependencies, then returns a Promise so the runtime can keep the page responsive.1 You still need to prepare the bytes yourself: TextEncoder converts strings into the Uint8Array that digest expects.2
The Web Crypto API async design creates specific patterns in React and Vue
The Web Crypto API's async design creates specific patterns in React and Vue component code. In React, compute a SHA-256 hash inside a useEffect hook when the input changes and store the result in state with useState. Call crypto.subtle.digest('SHA-256', data) inside the effect and resolve the Promise with await. The async computation does not block rendering because React defers effect execution until after the browser paints the frame.
For Vue 3 applications, the watchEffect composable handles the same pattern: call crypto.subtle.digest() inside watchEffect, update a reactive ref with the resolved result, and the template re-renders automatically. SubtleCrypto is only available in secure contexts.3 Include a runtime check before calling it: if window.crypto?.subtle is undefined, display an error state or fall back to a library that handles the insecure context gracefully rather than throwing a runtime exception.
Keeping hash results stable in client state
Store the computed hash as derived state, not as the source of truth for the original input. If the user edits the text after the hash resolves, your effect should run again and replace the previous result, otherwise the UI can display a digest for stale content. CapyToolkit follows the same model in the Hash Generator: the input remains editable, and the displayed SHA-256 result updates only after the browser computes a fresh digest from the current bytes.
You keep control of the input even after the result appears, because the hash never locks the text field it was computed from. A user who edits a character should see the digest change on the next cycle rather than a value that quietly describes an older input. CapyToolkit follows the same model, so the displayed hash always reflects the bytes you most recently entered into the field.
Handling non-secure contexts where crypto.subtle is unavailable
Handling the case where crypto.subtle is undefined makes your code robust across HTTP development environments and browser extensions with restrictive content security policies. Check for availability with if (typeof window !== 'undefined' && window.crypto?.subtle) before any crypto.subtle call. In non-secure HTTP contexts, window.crypto.subtle is undefined by specification, so the optional chaining check returns undefined and your fallback branch runs instead of throwing a runtime exception that would break the page.
Polyfilling SubtleCrypto for local development over HTTP
For development environments running over plain HTTP, the @peculiar/webcrypto package provides a SubtleCrypto-compatible implementation that works in any JavaScript environment regardless of security context. Add it as a devDependency and import it only in environments where crypto.subtle is unavailable. Never ship the polyfill to production clients over HTTPS: those environments have native SubtleCrypto, and the polyfill adds unnecessary bundle weight and removes the security guarantees of the native browser implementation.
For converting ArrayBuffer to hex, the Uint8Array map pattern handles all hash sizes
For React and Vue components that display a SHA-256 hash to the user, converting the ArrayBuffer result to a readable string takes two steps. For hex output: Array.from(new Uint8Array(buffer)).map(b => b.toString(16).padStart(2, '0')).join(''). For Base64 output: btoa(String.fromCharCode(...new Uint8Array(buffer))). Both patterns are safe for all modern browsers and appear throughout the SubtleCrypto documentation.
For large ArrayBuffer values, the spread operator in btoa(String.fromCharCode(...new Uint8Array(buffer))) can hit a JavaScript engine maximum argument count, so chunking is safer for large binary files.4 SHA-256 produces exactly 32 bytes, so this limit never applies to hash digests. For hash output specifically, the direct spread conversion is the most readable approach available without an additional encoding library.
Keeping output conversion separate from hashing
Treat hex or Base64 conversion as presentation code, not as part of the digest calculation. The digest is the 32-byte ArrayBuffer returned by crypto.subtle.digest(); the string you display is only a readable encoding of those same bytes. That separation makes it easier to change the output format without altering the hash value, because the encoding step never feeds back into the cryptographic operation. When you need to compare two digests, compare the raw ArrayBuffer bytes or a consistently encoded string rather than mixing hex from one source with Base64 from another.
Notes
The canonical pattern is three steps: encode the string with new TextEncoder().encode(text), pass the resulting Uint8Array to crypto.subtle.digest('SHA-256', data), and convert the returned ArrayBuffer to a hex string via Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('').
One gotcha to know: crypto.subtle is only available in secure contexts - https://, localhost, and browser extensions. In non-secure HTTP contexts the API is undefined, which causes a runtime error at the call site rather than at import time. For Node.js 18+, crypto.subtle is available globally; for earlier Node versions use the crypto module's createHash API instead.
Examples
Hash a string
async function sha256(text) {
const data = new TextEncoder().encode(text);
const buf = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
const hash = await sha256('Hello, World!');
console.log(hash); // 64-char hex string Hash a File object (browser)
async function sha256File(file) {
const buf = await file.arrayBuffer();
const hashBuf = await crypto.subtle.digest('SHA-256', buf);
return Array.from(new Uint8Array(hashBuf))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// Usage with <input type="file">
input.addEventListener('change', async () => {
const hash = await sha256File(input.files[0]);
console.log(hash);
}); Hex to Base64 (alternative output)
async function sha256Base64(text) {
const data = new TextEncoder().encode(text);
const buf = await crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode(...new Uint8Array(buf)));
} btoa() only works reliably when the Uint8Array values stay in the 0–255 range, which hash bytes always satisfy.
Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
async function sha256(text) {
const data = new TextEncoder().encode(text);
const buf = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
const hash = await sha256('Hello, World!');
console.log(hash); // 64-char hex string - 1.
MDN Contributors, "SubtleCrypto: digest() method," developer.mozilla.org, December 2025. https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
- 2.
WHATWG, "Encoding Standard," encoding.spec.whatwg.org, May 2026. https://encoding.spec.whatwg.org/#interface-textencoder
- 3.
MDN Contributors, "Secure contexts," developer.mozilla.org, November 2025. https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts
- 4.
Peter Marshall, "Reduce spread/apply call max arguments," chromium.googlesource.com, November 2018. https://chromium.googlesource.com/v8/v8/+/4e3a17d0408627517d4a81b3bf5daf85e416e9ac
The Web Crypto API was designed to support hardware-backed key operations that are genuinely asynchronous. Even fast operations like SHA-256 hashing use the same async interface for consistency. CapyToolkit's browser-only generator uses that same async path, so the input stays local while the page waits for the digest. Await the result inside an async function or handle the Promise with .then().
Yes, in Node.js 18 and later. Earlier versions expose crypto.subtle via require("crypto").webcrypto.subtle. For Node.js 14–17, use the crypto.createHash("sha256") API instead, which is synchronous and does not require the webcrypto flag.
file.arrayBuffer() loads the entire file into memory. For files larger than a few hundred MB, use a streaming approach: read the file in slices with Blob.prototype.slice(), hash each slice, and feed the results through a running hash update. The Web Crypto API does not natively support streaming digest updates - use a library like @noble/hashes for that pattern.
Check for window.crypto?.subtle before hashing. If it is missing, show a clear error or load a vetted fallback only for the specific workflow that needs it. Do not silently switch algorithms, because the receiving system may expect a SHA-256 digest.
No. The W3C Web Crypto specification explicitly excludes MD5 because it is cryptographically broken. Use a third-party library like js-md5 if MD5 output is required for legacy system compatibility.
SHA-256 in Java
For Java developers, SHA-256 is already part of the platform. The java.security.MessageDigest API has been available since Java 1.1, and every modern JDK includes a built-in SHA-256 provider without an external library.1 The API works with byte arrays, so String inputs require explicit encoding. Always choose StandardCharsets.UTF_8 unless you are deliberately matching a legacy charset contract.2 For Java 17 and newer, HexFormat.of().formatHex(bytes) is the standard-library helper for byte-array hex output.3
In concurrent Java services, create one MessageDigest instance per operation
In multi-threaded Java services, create a fresh MessageDigest instance for each hashing operation. This keeps the stateful update() and digest() cycle isolated, and it avoids accidental sharing between request handlers or worker tasks that could corrupt the digest computation. Because MessageDigest holds internal state that changes with every .update() call, sharing a single instance across threads without synchronization produces silently wrong results that are extremely difficult to reproduce in testing.
If your service hashes frequently inside a fixed thread pool, keep that isolation explicit at the call site: obtain MessageDigest.getInstance("SHA-256"), feed the bytes for that operation, call .digest(), and discard the instance. Avoid helper objects that stash a digest between calls unless the object is documented as single-use or protected by clear ownership rules. For high-throughput services where allocation overhead matters, a ThreadLocal<MessageDigest> gives each thread its own instance without contention, though you should measure the cost before optimizing.
Documenting the byte contract in service boundaries
Write the expected encoding and hash length into the API contract that owns the value. A JSON field called sha256 should mean UTF-8 text hashed to SHA-256 and represented as lowercase hex unless the contract says otherwise. That small documentation detail prevents a downstream service from comparing a Java UTF-8 digest with a JavaScript UTF-16 guess, a Base64 digest, or a truncated identifier. CapyToolkit's Hash Generator keeps the algorithm and output format visible for the same reason: the value is easier to verify when the format is explicit.
You protect downstream services by writing the encoding decision into the contract that owns the value, not into a comment that a future reader might miss. A JSON field that names both the algorithm and the representation removes the guesswork when another team consumes the data. CapyToolkit keeps the algorithm and output format visible for this reason, so a copied value still carries its own context.
Validating SHA-256 before caching computed results
Before you cache a computed SHA-256 value in Java, validate the same bytes that later code will consume. If a cache key is built from request text, normalize the charset, trim policy, and payload boundary before hashing, then store the digest with the algorithm name. That prevents a later migration from comparing UTF-8, UTF-16, or Base64 representations as if they were identical inputs.
When hashing large files in Java, DigestInputStream avoids changing existing code
When your Java application needs to hash a file too large to load into memory, use a chunked MessageDigest.update() loop that reads the file in small blocks and feeds each block to the digest without ever holding the entire file in memory. Open the file with try (InputStream is = Files.newInputStream(path)), allocate a 64 KB buffer once outside the loop, and call md.update(buf, 0, bytesRead) for each read. Call md.digest() after the loop to retrieve the final hash. This approach keeps memory usage constant regardless of file size, which is essential when processing multi-gigabyte files on a server with limited heap.
DigestInputStream as a transparent wrapper
Java's DigestInputStream wraps any InputStream and accumulates a hash transparently as data flows through it.4 Construct it with new DigestInputStream(existingInputStream, md) and read from it exactly as you would the original stream. Existing code that processes an InputStream can also produce a SHA-256 hash by wrapping the stream at the call site, without modifying any of the inner processing logic that consumes the data. This pattern is especially useful when the same code path that reads the file for business logic can simultaneously accumulate the digest without a second pass over the bytes.
Comparing digest byte arrays with MessageDigest.isEqual prevents timing attacks
Comparing two SHA-256 digests in Java uses MessageDigest.isEqual(digest1, digest2), which performs a constant-time byte array comparison that has been part of the standard library since Java 1.1.1 This method prevents timing attacks on hash comparisons derived from untrusted input. Avoid Arrays.equals() and String.equals() for hash comparison: both short-circuit on the first mismatched byte, leaking timing information that an attacker can exploit to reconstruct the expected value over many requests.
For password-adjacent verification where you compare a computed hash against a stored trusted hash, MessageDigest.isEqual() is the correct tool. For HMAC verification specifically, prefer the javax.crypto.Mac API with HmacSHA256 rather than computing a raw SHA-256 and comparing manually, because the HMAC construction provides stronger authentication guarantees than a plain hash for any keyed authentication scenario.
Notes
Obtain an instance with MessageDigest.getInstance("SHA-256"), pass bytes via .digest(data) or the two-step .update(data); .digest() pattern, and convert the resulting 32-byte array to hex. For older Java versions, build hex with String.format("%02x", b) in a loop; for Java 17 and newer, HexFormat.of().formatHex(bytes) is the standard-library helper.
One thread-safety warning: MessageDigest instances are stateful and should not be shared across threads. Either create a new instance per operation with MessageDigest.getInstance("SHA-256") each time, or use a ThreadLocal<MessageDigest> if you need per-thread reuse in a high-throughput context.
Examples
Hash a string
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest("Hello, World!".getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : hash) sb.append(String.format("%02x", b));
String hex = sb.toString(); // 64-char lowercase hex Java 17+ hex format
import java.security.MessageDigest;
import java.util.HexFormat;
import java.nio.charset.StandardCharsets;
byte[] hash = MessageDigest.getInstance("SHA-256")
.digest("Hello, World!".getBytes(StandardCharsets.UTF_8));
String hex = HexFormat.of().formatHex(hash); HexFormat was added in Java 17 and is the cleanest approach for new code.
Hash a file with streaming
import java.security.MessageDigest;
import java.io.*;
MessageDigest md = MessageDigest.getInstance("SHA-256");
try (InputStream is = new FileInputStream("archive.zip")) {
byte[] buf = new byte[65536];
int n;
while ((n = is.read(buf)) != -1) md.update(buf, 0, n);
}
byte[] hash = md.digest(); Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest("Hello, World!".getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : hash) sb.append(String.format("%02x", b));
String hex = sb.toString(); // 64-char lowercase hex - 1.
Oracle, "MessageDigest (Java SE 26 & JDK 26)," docs.oracle.com, accessed June 2026. https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/security/MessageDigest.html
- 2.
OpenJDK, "StandardCharsets.java," github.com, accessed June 2026. https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/nio/charset/StandardCharsets.java
- 3.
OpenJDK, "HexFormat.java," github.com, accessed June 2026. https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/util/HexFormat.java
- 4.
Oracle, "DigestInputStream (Java SE 26 & JDK 26)," docs.oracle.com, accessed June 2026. https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/security/DigestInputStream.html
It throws NoSuchAlgorithmException, a checked exception. In practice, every standard JDK guarantees SHA-256 is available, so you can either catch the exception and rethrow as RuntimeException or annotate the method with throws NoSuchAlgorithmException. MessageDigest.isEqual() provides constant-time comparison for hash verification.
.digest(data) is a one-shot convenience that processes all data and returns the hash. .update(data) accumulates data incrementally, which is essential for streaming large files. Call .digest() with no argument at the end to retrieve the accumulated hash. After calling .digest(), the MessageDigest instance resets automatically.
String.getBytes() with no argument uses the JVM's default charset, which varies by operating system and JVM configuration. Always pass StandardCharsets.UTF_8 explicitly to produce the same hash on every platform regardless of locale settings. CapyToolkit's generated Java examples use this explicit charset so your result matches the same bytes across systems.
Yes. Android includes the same MessageDigest API. Use MessageDigest.getInstance("SHA-256") on any Android API level. For HMAC, use javax.crypto.Mac with "HmacSHA256" as the algorithm.
Store the digest with the algorithm and encoding, such as SHA-256 lowercase hex, so future services do not guess the format. If you keep raw bytes, document column length and byte order. If you keep text, lowercase hex is usually the easiest value to inspect in logs and databases.
SHA-256 in PHP
PHP gives you SHA-256 through the same hash extension that powers checksums, HMAC signatures, and file digests. The hash() function takes an algorithm name and a string and returns a hex-encoded digest, while hash_file() applies the same algorithm to an on-disk file.1 PHP's hash extension supports over sixty algorithms through one interface, so switching between MD5, SHA-1, and SHA-256 can be as simple as changing the first argument.1
Use the global hash() functions for request and file data
In PHP request handlers, SHA-256 most often appears in three places: hashing request payloads for idempotency detection, computing ETags for HTTP cache validation, and verifying incoming webhook signatures. For idempotency, hash the full raw request payload with hash('sha256', $rawBody) and store the result as the idempotency key before processing the request. Identical payloads produce the same key, so you can detect and skip duplicate submissions without examining the payload content again.
Use PHP's global hash() and hash_hmac() functions for non-password hashing tasks such as webhook signatures, ETags, and content fingerprints. These functions accept an algorithm name and arbitrary string data, while password helpers like password_hash() are intentionally slow and include salting, which makes them unsuitable for fast integrity checks over request data.
Separating checksums from authentication
A plain SHA-256 checksum tells you that two byte strings match, but it does not prove who produced them. Anyone who sees the same input can compute the same digest. For webhook verification, API request signing, or callback authentication, pair SHA-256 with a shared secret through hash_hmac() and compare the result with hash_equals(). CapyToolkit separates these paths in the Hash Generator so you can choose a plain digest for file integrity or HMAC when the value must authenticate a sender.
You decide which guarantee a given value needs before you write the code, because a checksum and an HMAC solve different problems and look deceptively similar in logs. A plain SHA-256 proves the bytes are unchanged, while only HMAC proves the bytes came from someone holding the secret. CapyToolkit separates these paths so you can pick the weaker primitive only where the stronger one is unnecessary.
Choosing the right comparison helper
Use hash_equals() when the comparison can influence access, payment status, or deployment decisions. The helper is most valuable when the expected value is trusted and the computed value comes from an external request. For local file manifests, strict equality can still be fine, but timing-safe comparison is the safer default when the value has security meaning. CapyToolkit's PHP examples use hash_equals() for every comparison that involves a value derived from network input, which keeps the habit consistent across webhook verification, API signature checking, and file checksum validation.
Verifying webhook payloads with hash_hmac and hash_equals
Verifying webhook signatures in PHP requires three values: the raw request body as a string, the shared secret, and the signature from the request header. Strip any algorithm prefix from the header value before comparison: GitHub sends sha256=<hex>, so remove the sha256= prefix before comparing the hex portion.2
Reading the raw body before framework parsing
Most PHP frameworks parse the request body into $_POST or a request object early in the lifecycle. This parsing normalizes and re-encodes the content, changing the byte sequence and invalidating any HMAC computed over the re-encoded version. To access the original bytes, read file_get_contents('php://input') before any framework middleware processes the request.3 Laravel and Symfony both consume the request stream on first access, so calling php://input after the framework has already read the body returns an empty string. Register your verification middleware before the framework's body-parsing middleware to guarantee the raw bytes are still available when your code runs.
Computing and comparing the signature
Compute an HMAC-SHA256 value over the raw body with the shared secret.2 Compare the result with hash_equals($expectedHex, $computedHex) so the comparison does not reveal timing information.4 The function returns a boolean and always compares the full string length, which prevents an attacker from learning how many leading bytes matched through response-time analysis. Wrap the entire verify-and-compare step in a single helper that accepts the raw body, secret, and signature header, then returns a single true or false without exposing which sub-step failed.
SHA-256 file integrity checking protects both uploads and downloads
PHP's hash_file('sha256', $path) computes a hash over an on-disk file using the same algorithm argument as hash()).<sup id="sha256-php--fnref-5"><a href="#sha256-php--fn-5">5</a></sup> For uploaded files, $_FILES['file']['tmp_name'] points to the temporary file on the server. Call hash_file('sha256', $_FILES['file']['tmp_name'])` before moving the file to permanent storage and save the result in the database alongside the file metadata. Re-compute and compare the hash on demand to detect silent storage corruption.
For download integrity verification in PHP deployment scripts, download the archive, save it to a temporary path, compute its SHA-256 with hash_file(), and compare against a pinned expected value. Abort with an exception if the hashes differ. This pattern protects deployments against corrupted downloads or unexpected changes to the source artifact between the time it was pinned and the time your deployment script fetches it.
Notes
Call hash('sha256', $data) to get a 64-character lowercase hex string. For the raw 32-byte binary output, pass true as the third argument: hash('sha256', $data, true). Use hash_file('sha256', $path) to hash a file without loading it into memory - PHP reads the file in chunks internally.
For timing-safe hash comparison, use hash_equals($expected, $computed) rather than === or strcmp(). String comparison operators short-circuit on the first mismatched character, creating a timing side-channel that an attacker could exploit to infer whether two hashes share a common prefix. hash_equals() always takes the same time regardless of the comparison result.
Examples
Hash a string
<?php
$hash = hash('sha256', 'Hello, World!');
echo $hash; // 64-char lowercase hex string Hash a file
<?php
$hash = hash_file('sha256', '/path/to/archive.zip');
echo $hash; // 64-char hex without loading file into memory HMAC-SHA256
<?php
$mac = hash_hmac('sha256', 'payload_data', 'secret_key');
echo $mac; // 64-char hex HMAC Timing-safe comparison
<?php
$expected = hash('sha256', $canonical_input);
$computed = hash('sha256', $user_input);
if (hash_equals($expected, $computed)) {
// Inputs match
} Always use hash_equals() when comparing hashes derived from user-controlled input.
Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
<?php
$hash = hash('sha256', 'Hello, World!');
echo $hash; // 64-char lowercase hex string - 1.
PHP, “Hash,” php.net, accessed June 2026 https://www.php.net/manual/en/book.hash.php
- 2.
GitHub Docs, “Validating webhook deliveries,” docs.github.com, accessed June 2026 https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
- 3.
PHP, “php://input,” github.com, accessed June 2026 https://raw.githubusercontent.com/php/doc-en/master/language/wrappers/php.xml
- 4.
PHP, “hash_equals,” php.net, accessed June 2026 https://www.php.net/manual/en/function.hash-equals.php
- 5.
PHP, “hash_file,” github.com, accessed June 2026 https://raw.githubusercontent.com/php/doc-en/master/reference/hash/functions/hash-file.xml
Run hash_algos() to get the full list for your PHP build. Every standard PHP installation includes md5, sha1, sha256, sha512, and ripemd160. PHP 8.1 added sha3-256 and blake2b variants. CapyToolkit's PHP examples focus on SHA-256 and HMAC-SHA256 because those choices appear often in modern integrity and webhook checks. The example page keeps those paths separate so you can see when a plain digest is enough and when an HMAC is required.
md5() and sha1() are legacy functions that remain for backward compatibility. hash("md5", $str) and md5($str) produce the same output. Prefer hash() for new code because it uses a consistent interface across algorithms and is easier to change later.
Pass true as the third argument when feeding the hash into another operation - for example, as input to a second hash, a base64 encoding, or a binary storage column. The raw bytes are half the size of the hex string, which matters in tight storage or bandwidth contexts.
Yes. Most webhook providers (Stripe, GitHub, Shopify) use HMAC-SHA256 and publish the algorithm and header name in their documentation. Compute hash_hmac("sha256", $raw_body, $secret), then compare with hash_equals() against the value from the X-Hub-Signature-256 or equivalent header.
Yes. hash_file() lets PHP read the file internally instead of forcing your script to load it all at once. For uploads, compute the hash after the file lands in a temporary path, store the digest with the metadata, and recompute it later if you need to prove the stored bytes still match.
SHA-256 in Go
For Go developers, SHA-256 is a standard-library decision. The crypto/sha256 package gives you the one-shot sha256.Sum256() function for fixed-size byte arrays and the streaming sha256.New() hash for readers that arrive in chunks.1 Both approaches avoid npm-style dependencies, and the streaming form implements hash.Hash, which embeds io.Writer and fits naturally into Go's file and network APIs.
When your Go application downloads artifacts, verify their SHA-256
When your Go application downloads a binary or archive during a build or setup phase, verifying the SHA-256 hash before executing or extracting it prevents installation of corrupted or unexpected files that could compromise your build pipeline. The pattern combines sha256.New() and io.Copy(): open the downloaded file, create a sha256.New() hash, and call io.Copy(h, file) to stream all bytes through the hasher. Compare the resulting digest with a constant-time comparison helper so the check does not reveal where the first byte differs.2 SHA-256 is one of the Secure Hash Algorithms specified for computing message digests.3
For Go tools that fetch binaries from the internet during go generate or build phases, pin the expected SHA-256 value as a named constant in your source file so that anyone reviewing the code can look up the same binary independently and verify that the constant matches the artifact you actually downloaded.
Treating hashes as build policy
A pinned hash is only useful when the policy is visible next to the download it protects. Put the expected digest, source URL, and reason for the dependency in the same module or script so a reviewer can confirm that the artifact was not swapped after testing. CapyToolkit's Hash Generator is useful in that workflow because it lets you compute the digest for a downloaded file before you paste the value into your Go build policy.
Using the hash.Hash interface for algorithm-agnostic code
Go's hash.Hash interface lets you write functions that accept any hash algorithm without committing to a specific implementation, which keeps your storage and verification code flexible as requirements evolve.4 Pass a hash.Hash as a parameter instead of a concrete type: func computeDigest(h hash.Hash, r io.Reader) []byte. The caller supplies sha256.New() or sha512.New() at the call site, and the function works for either without modification. This pattern is common in storage systems, backup tools, and any application where the hash algorithm is externally configurable.
Choosing hash.Hash for reusable storage code
If your code stores digests for backups, deduplication, or replication, accepting a hash.Hash lets you test SHA-256 and SHA-512 paths with the same helper function without duplicating logic. The helper stays small, while the caller decides the algorithm and documents the policy beside the job that needs it. This pattern also simplifies testing: pass a mock hasher in unit tests to verify that your storage layer calls Write and Sum in the correct order without needing real cryptographic operations.
You make storage code simpler when the algorithm decision lives at the call site instead of scattered through every handler that writes or verifies a digest. A helper that accepts hash.Hash can serve backups, replication, and de-duplication with one implementation rather than a copy per algorithm. CapyToolkit shows SHA-256 output locally so you can confirm the format before wiring that helper into a storage layer.
Registering hash algorithms with the crypto package
Go's standard library maintains a global registry of hash constructors accessible through the crypto.Hash type.1 Call crypto.SHA256.New() to get a SHA-256 hasher through the registry rather than importing crypto/sha256 directly. This indirection is useful in systems that read algorithm identifiers from configuration files or network protocols and need to instantiate the correct hasher at runtime without a large switch statement in the calling code.
For hash comparison, constant-time helpers prevent timing attacks
In Go, comparing two SHA-256 hash values should use a constant-time comparison helper rather than bytes.Equal().2 A constant-time helper returns equality without short-circuiting on the first mismatched byte, which prevents timing side-channel attacks where an attacker sends many requests, measures response times, and reconstructs the expected hash value one byte at a time from the timing difference.
For file integrity checking where the comparison is between your computed hash and a trusted reference value stored in a config file, timing attacks are not a realistic threat model. Constant-time comparison matters most for HMAC verification, where a secret key authenticates the message,5 and scenarios where the expected value derives from a secret. Using a constant-time helper consistently throughout your codebase establishes the correct habit and avoids a future refactor accidentally moving sensitive comparisons to the timing-leaky code path.
Notes
For simple string or byte-slice hashing, sha256.Sum256([]byte(input)) is the shortest path. The return type is [32]byte, a fixed-size array - not a slice. Convert it to hex before displaying or storing it as text.
For streaming large files, use the hash.Hash interface: h := sha256.New(), write data with io.Copy(h, reader), then retrieve the digest with h.Sum(nil). Passing nil to Sum() appends the current hash to an empty slice and returns it. The hash instance can be reset with .Reset() for reuse without allocation.
Examples
Hash a string (one-shot)
import (
"crypto/sha256"
"fmt"
)
sum := sha256.Sum256([]byte("Hello, World!"))
fmt.Printf("%x\n", sum) // 64-char lowercase hex Hash a file with io.Copy
import (
"crypto/sha256"
"encoding/hex"
"io"
"os"
)
f, _ := os.Open("archive.zip")
defer f.Close()
h := sha256.New()
io.Copy(h, f)
digest := hex.EncodeToString(h.Sum(nil)) HMAC-SHA256
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
mac := hmac.New(sha256.New, []byte("secret_key"))
mac.Write([]byte("payload"))
sig := hex.EncodeToString(mac.Sum(nil)) Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string (one-shot)
import (
"crypto/sha256"
"fmt"
)
sum := sha256.Sum256([]byte("Hello, World!"))
fmt.Printf("%x\n", sum) // 64-char lowercase hex - 1.
The Go Authors, “crypto/sha256 package,” github.com, accessed June 2026 https://raw.githubusercontent.com/golang/go/master/src/crypto/sha256/sha256.go
- 2.
OWASP Foundation, “Authentication Cheat Sheet,” cheatsheetseries.owasp.org, accessed June 2026 https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet
- 3.
D. Eastlake 3rd and T. Hansen, “RFC 6234: US Secure Hash Algorithms,” rfc-editor.org, May 2011 https://www.rfc-editor.org/info/rfc6234/
- 4.
The Go Authors, “hash.Hash interface,” github.com, accessed June 2026 https://raw.githubusercontent.com/golang/go/master/src/hash/hash.go
- 5.
H. Krawczyk, M. Bellare, and R. Canetti, “RFC 2104: HMAC,” rfc-editor.org, February 1997 https://www.rfc-editor.org/info/rfc2104/
Fixed-size arrays in Go are value types that live on the stack when possible, avoiding heap allocation. For a SHA-256 digest that is always exactly 32 bytes, a [32]byte is more efficient than a slice. CapyToolkit's generated Go output keeps the digest in hex form because most build policies, logs, and documentation expect a printable SHA-256 string. Use sum[:] to get a []byte slice when you need to pass the digest to a function expecting []byte.
Use sha256.New() whenever you need to hash data incrementally - streaming files, network data arriving in chunks, or any case where the full input is not in memory at once. io.Copy(h, reader) is the idiomatic pattern. For small in-memory data, sha256.Sum256() is simpler and involves one fewer allocation.
Use hmac.Equal(mac1, mac2) from the crypto/hmac package. This function performs a constant-time comparison, preventing timing attacks. Do not use bytes.Equal() or reflect.DeepEqual() for MAC comparison.
Go's standard library is not FIPS certified by default. For FIPS-compliant applications, use the golang.org/x/crypto module's fips140 sub-package, or build with a FIPS-validated crypto provider like BoringCrypto via the GOEXPERIMENT=boringcrypto build tag.
Usually no. The raw array is useful inside Go code, but logs, databases, and documentation expect printable text. Convert it with fmt.Printf("%x", sum) or hex.EncodeToString(sum[:]) before storing or showing the value.
SHA-256 in Node.js
In server-side JavaScript, SHA-256 usually starts with the built-in crypto module, not a package from npm. That module wraps OpenSSL and exposes the synchronous crypto.createHash('sha256') pattern for in-memory data, plus stream integration for files and request bodies.1 Node.js also exposes the Web Crypto API through globalThis.crypto and require('node:crypto').webcrypto, but the crypto module remains the idiomatic server-side choice.2
Computing SHA-256 for request body integrity in Express middleware
Computing SHA-256 over an HTTP request body in Express middleware captures a content fingerprint before any JSON parsing alters it. Set up a middleware early in the chain that reads the raw body as a Buffer, computes crypto.createHash('sha256').update(rawBody).digest('hex'), and attaches the result to req.contentHash. Later middleware and route handlers read this property instead of recomputing the hash. This pattern is useful for idempotency keys, content-addressed caching, and audit logging of request content.
For routes that also verify a webhook HMAC, the same raw body Buffer serves both purposes: compute the content SHA-256 for logging and the HMAC for signature verification from the same Buffer without reading the stream twice. Configure express.raw({ type: '*/*' }) before the hashing middleware to buffer the body as bytes. If express.json() runs first, it consumes the body stream and produces a different byte sequence when re-serialized, causing both the SHA-256 and the HMAC to differ from the sender's computed values.3
Preserving the raw request body for verification
Read and hash the raw body before JSON parsing, URL decoding, or form normalization changes it. Save that Buffer only for the short time needed to compute the digest and HMAC, then let later middleware work with parsed data. This keeps the fingerprint tied to the exact bytes the sender transmitted. A common Express pattern stores the raw Buffer on req.rawBody after computing the hash, so downstream middleware can still access parsed JSON through req.body while the verification layer retains the original byte sequence for signature comparison.
You preserve correctness by computing the digest from the same bytes the client actually transmitted, before any middleware reshapes them into parsed data. A JSON parser can reorder keys or adjust whitespace, and either change produces a digest that never matches the sender. CapyToolkit computes hashes locally from the exact input you provide, so the value you see reflects the bytes rather than a re-encoded copy.
Logging hashes without logging payloads
A content hash can make audit logs useful without storing sensitive request bodies. Save the SHA-256 value, timestamp, route, and request identifier, then retain the raw payload only when a legal or debugging policy requires it. CapyToolkit's Hash Generator follows the same privacy-friendly pattern: it computes hashes locally in the browser and does not upload the text or file bytes you enter.
In streaming pipelines, stream.pipeline propagates errors cleanly
In Node.js applications that process files from cloud storage or the local filesystem, the stream.pipeline() function composes a readable stream, a SHA-256 hash transform, and an optional writable destination in a single call that handles the entire lifecycle of the operation. Import pipeline from node:stream/promises for the async version: await pipeline(readableStream, hash, writableStream). The hash accumulates bytes as they pass through, and you call hash.digest('hex') after the pipeline() call resolves.3
Why pipeline beats bare .pipe() for file hashing
The promisified pipeline rejects its returned Promise when any stream in the chain emits an error, destroying all other streams automatically and ensuring that no partial or corrupted digest is produced from a failed read. A bare .pipe() call does not propagate read errors to the hash destination; a file-not-found error on the source stream leaves the hash in an undefined state and silently produces an incorrect digest. Await the pipeline call inside a try/catch block to handle permission errors, file-not-found errors, and premature end-of-stream conditions distinctly from hash computation results.
Testing crypto.createHash output with known SHA-256 vectors
Testing crypto.createHash('sha256') in Jest or Vitest follows the same approach as any deterministic function: assert on known input/output pairs. The SHA-256 of the empty string is e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855; include this as a baseline assertion that confirms the algorithm and hex encoding are both correct.4 For HMAC tests, use the RFC 4231 test vectors, which provide publicly documented input, key, and expected output triples for HMAC-SHA256.5
For async hash functions that use crypto.subtle, wrap the test in async/await and assert on the resolved value. Vitest and Jest both support async test functions natively without additional configuration. Mock window.crypto.subtle only when testing code that handles the undefined case in non-secure contexts; for correctness testing of hash output, use the real implementation and assert on specific known byte sequences that the SHA-256 specification guarantees.
Notes
Create a hash instance with crypto.createHash('sha256'), feed data with .update(data) (accepts strings, Buffers, or TypedArrays), and retrieve the hex digest with .digest('hex'). Calling .digest() finalises the hash - the instance cannot be updated after that. Create a new instance for each independent hash operation.
For file hashing, pipe a readable stream through the hash: fs.createReadStream(path).pipe(hash). Because Hash extends Transform, it works directly as a pipe destination. Listen for the 'finish' event, then call .digest('hex') - or use the async iterator pattern with for await (const chunk of stream) and .update(chunk) in a loop, which gives more control over error handling.
Examples
Hash a string
const crypto = require('crypto');
const hash = crypto.createHash('sha256')
.update('Hello, World!')
.digest('hex');
console.log(hash); // 64-char lowercase hex Hash a file with streams
const crypto = require('crypto');
const fs = require('fs');
const hash = crypto.createHash('sha256');
const stream = fs.createReadStream('archive.zip');
stream.pipe(hash);
stream.on('end', () => {
console.log(hash.digest('hex'));
}); HMAC-SHA256
const crypto = require('crypto');
const mac = crypto.createHmac('sha256', 'secret_key')
.update('payload')
.digest('hex');
console.log(mac); // 64-char hex HMAC Async file hash (await)
const crypto = require('crypto');
const fs = require('fs');
async function sha256File(path) {
const h = crypto.createHash('sha256');
for await (const chunk of fs.createReadStream(path)) h.update(chunk);
return h.digest('hex');
} Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
const crypto = require('crypto');
const hash = crypto.createHash('sha256')
.update('Hello, World!')
.digest('hex');
console.log(hash); // 64-char lowercase hex - 1.
Node.js Foundation, “Crypto,” nodejs.org, accessed June 2026 https://nodejs.org/api/crypto.html
- 2.
expressjs, “body-parser,” github.com, accessed June 2026 https://github.com/expressjs/body-parser
- 3.
Node.js Foundation, “Stream,” nodejs.org, accessed June 2026 https://nodejs.org/api/stream.html
- 4.
D. Eastlake 3rd and T. Hansen, “RFC 6234: US Secure Hash Algorithms,” rfc-editor.org, May 2011 https://www.rfc-editor.org/info/rfc6234/
- 5.
M. Nystrom, “RFC 4231: HMAC-SHA Test Vectors,” rfc-editor.org, December 2005 https://www.rfc-editor.org/rfc/rfc4231.txt
No. The built-in crypto module handles SHA-256, SHA-512, MD5, HMAC, and dozens of other algorithms. Import it with require("crypto") or import { createHash } from "crypto" in ESM. CapyToolkit's Node.js examples use that same built-in path so you can verify output without adding a dependency.
crypto.createHash is synchronous, stream-compatible, and follows the Node.js idiom. crypto.subtle.digest is asynchronous, returns a Promise, and follows the Web Crypto standard. Both produce identical SHA-256 output. Use createHash for server-side code; use crypto.subtle for code that also runs in the browser.
Yes. Call .update() as many times as needed with different chunks of data before calling .digest(). This is the streaming pattern. After .digest() is called the hash is finalised and cannot be updated again.
Use crypto.timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex")) to prevent timing attacks. Both Buffers must be the same length - HMAC-SHA256 always produces 32 bytes (64 hex chars), so they always match in length.
Store the hex string for logs and databases because it is readable and easy to compare. Keep raw bytes only inside a tight code path that immediately encodes them, stores them in a binary column, or passes them to another cryptographic function.
SHA-256 in C#
For C# developers, SHA-256 is part of the cryptographic baseline. The System.Security.Cryptography.SHA256 class is available across .NET environments, including .NET Framework and modern .NET versions, and SHA-256 itself is a 256-bit Secure Hash Standard algorithm.12 Starting in .NET 5, one-shot APIs such as SHA256.HashData(data) are static, thread-safe, and simpler for in-memory data.3 The older SHA256.Create() pattern still computes the same digest when you need streaming or .NET Framework compatibility.
Because SHA256.HashData is static and thread-safe, it works directly in ASP.NET Core controllers
Because SHA256.HashData() is static and thread-safe, you call it directly inside ASP.NET Core controllers and middleware without dependency injection or instance management.3 For an ETag implementation, compute SHA256.HashData(Encoding.UTF8.GetBytes(responseContent)) and format the result as a quoted hex string using Convert.ToHexString(hash).ToLower(). Set the formatted string as the ETag header and return 304 Not Modified when the client sends a matching If-None-Match value.
For idempotency key storage in an API endpoint, hash the raw request body bytes rather than the parsed object. Read the body with a buffering middleware that stores the raw bytes in HttpContext.Items before the controller runs. Hashing a re-serialized version of the parsed JSON object produces a different hash each time if the serializer changes key ordering or whitespace, which breaks idempotency detection silently in ways that are difficult to reproduce.
Keeping raw bytes available for idempotency
If later middleware needs the parsed body, buffer the original bytes once and attach them to HttpContext.Items. That avoids reading the request stream twice while preserving the exact byte sequence used for the digest. The policy is simple: parse for application logic, hash the original bytes for identity. In ASP.NET Core, enable request buffering with HttpContext.Request.EnableBuffering() at the start of your middleware, then reset the stream position to zero after hashing so the next middleware can read the same bytes without hitting a stream-exhausted error.
You avoid subtle bugs when the hashing step reads the same bytes that your controller will later deserialize for business logic. Buffering the request once and resetting the stream position lets every downstream layer see identical content without a second read. CapyToolkit keeps the algorithm next to the result for this reason, so a copied digest still tells the reader which input produced it.
Naming the format in API contracts
When you store a SHA-256 value in a database or return it from an API, include the algorithm and encoding in the field name or documentation. A field called content_sha256_hex is clearer than a generic hash, because readers immediately know the value is a SHA-256 digest formatted as hex. CapyToolkit's Hash Generator displays the algorithm next to the result so copied values do not lose that context.
Using Span<byte> for allocation-free SHA-256 in .NET 5+
Using Span<byte> avoids heap allocations when computing SHA-256 in hot code paths.2 The overload SHA256.HashData(ReadOnlySpan<byte> source, Span<byte> destination) writes the hash directly into a caller-provided 32-byte buffer: Span<byte> hash = stackalloc byte[32]; SHA256.HashData(data.AsSpan(), hash). This computes the hash with zero heap allocation on both the input and output sides when the input also fits in a stack-allocated span.
TryHashData for tight loops that avoid exceptions
For code that computes thousands of hashes per second, the SHA256.TryHashData(source, destination, out int bytesWritten) overload avoids both allocation and exception overhead. It returns false if the destination buffer is too small, which never happens when destination is exactly 32 bytes for SHA-256.4 Combining stackalloc, ReadOnlySpan, and the TryHashData overload gives you the most allocation-free path available in the .NET 5+ API surface for per-request hashing operations.
For timing-safe comparison in C#, use CryptographicOperations.FixedTimeEquals
For hash comparison in C# services that verify HMAC values or content hashes derived from user-controlled inputs, CryptographicOperations.FixedTimeEquals(span1, span2) from System.Security.Cryptography ensures the comparison takes the same time regardless of where the first byte difference occurs.4 Available since .NET Core 2.1, it accepts two ReadOnlySpan<byte> arguments and returns a boolean without any branching that leaks timing information through measurable response time differences.
Standard C# comparison methods including ==, SequenceEqual(), and Array.Equals() all short-circuit on the first mismatched byte. For HMAC verification or any scenario where the expected value derives from a secret key, this timing difference reveals how many bytes the attacker guessed correctly, enabling a byte-by-byte reconstruction attack over many requests. FixedTimeEquals prevents this by processing all bytes regardless of early mismatch, at the cost of a constant small time per comparison.
Notes
For .NET 5 and later, SHA256.HashData(ReadOnlySpan<byte> data) is the simplest approach - it creates, uses, and disposes the algorithm instance in one call. For .NET Framework or when you need streaming, use SHA256.Create() inside a using statement to ensure the instance is disposed after use.
Converting the resulting byte array to hex requires Convert.ToHexString(hash) (net5+) or BitConverter.ToString(hash).Replace("-", "").ToLower() on older targets. Convert.ToHexString returns uppercase hex; call .ToLower() if the lowercase convention is required. For HMAC, use System.Security.Cryptography.HMACSHA256 rather than manually combining SHA256 and a key.
Examples
Hash a string (.NET 5+)
using System.Security.Cryptography;
using System.Text;
byte[] data = Encoding.UTF8.GetBytes("Hello, World!");
byte[] hash = SHA256.HashData(data);
string hex = Convert.ToHexString(hash).ToLower();
// 64-char lowercase hex Hash a string (.NET Framework)
using System.Security.Cryptography;
using System.Text;
using SHA256 sha = SHA256.Create();
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes("Hello, World!"));
string hex = BitConverter.ToString(hash).Replace("-", "").ToLower(); Hash a file stream
using System.Security.Cryptography;
await using var stream = File.OpenRead("archive.zip");
byte[] hash = await SHA256.HashDataAsync(stream);
string hex = Convert.ToHexString(hash).ToLower(); SHA256.HashDataAsync is available in .NET 7+.
HMAC-SHA256
using System.Security.Cryptography;
using System.Text;
byte[] key = Encoding.UTF8.GetBytes("secret_key");
byte[] msg = Encoding.UTF8.GetBytes("payload");
byte[] mac = HMACSHA256.HashData(key, msg);
string hex = Convert.ToHexString(mac).ToLower(); HMACSHA256.HashData is available in .NET 7+.
Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string (.NET 5+)
using System.Security.Cryptography;
using System.Text;
byte[] data = Encoding.UTF8.GetBytes("Hello, World!");
byte[] hash = SHA256.HashData(data);
string hex = Convert.ToHexString(hash).ToLower();
// 64-char lowercase hex - 1.
National Institute of Standards and Technology, "FIPS 180-4, Secure Hash Standard (SHS)," csrc.nist.gov, August 2015. https://csrc.nist.gov/pubs/fips/180-4/upd1/final
- 2.
Microsoft, "SHA256 Class (System.Security.Cryptography)," learn.microsoft.com, accessed June 2026. https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha256?view=net-9.0
- 3.
Microsoft, ".NET cryptography model," learn.microsoft.com, February 2024. https://learn.microsoft.com/en-us/dotnet/standard/security/cryptography-model
- 4.
dotnet runtime contributors, "CryptographicOperations.cs," github.com, accessed June 2026. https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptographicOperations.cs
.NET 5+ code should prefer SHA256.HashData - it is shorter, internally handles lifetime, and is the modern API. CapyToolkit's C# examples label the target so you can choose the API that matches your project. SHA256.Create() returns an IDisposable and must be wrapped in a using statement to avoid resource leaks. For streaming input (files, network), SHA256.Create() is still needed since HashData requires the full data in memory.
Use SHA256.HashData(Encoding.UTF8.GetBytes(input)) - the static method is thread-safe and does not require dependency injection. Avoid storing SHA256 instances in fields; create them per-call or use the static API.
SHA256Managed is a legacy class from .NET Framework that provided a managed (pure C#) implementation. Since .NET 5, SHA256.Create() always returns a SHA256 instance backed by the most appropriate implementation for the current platform, including hardware acceleration. SHA256Managed is marked obsolete in newer .NET versions.
Use CryptographicOperations.FixedTimeEquals(span1, span2) from System.Security.Cryptography, available in .NET Core 2.1+. This method performs a constant-time comparison to prevent timing side-channel attacks.
Pick one convention and document it. Lowercase hex is common in logs and security examples, while Convert.ToHexString returns uppercase by default. Call ToLowerInvariant() when the receiving system expects lowercase output.
MD5 in Python
MD5 still appears in Python code, but it should be treated as a legacy checksum, not a security control. The algorithm produces a 128-bit message digest and remains common in package metadata, old database identifiers, and compatibility paths for systems that predate SHA-2.12 Python's hashlib exposes MD5 through the same interface as SHA-256, which makes it easy to use where required, but you should make the non-security purpose explicit in code.3
In Python data pipelines, MD5 serves as a fast de-duplication key
In Python data pipelines where files arrive from trusted internal sources, MD5 checksums serve as fast duplicate-detection keys without the security risk that makes them unsuitable for adversarial contexts.2 A common pattern reads a file, computes hashlib.md5(content, usedforsecurity=False).hexdigest(), and checks a dictionary or database table for the resulting 32-character key. If the key exists, the file is a duplicate and the pipeline skips further processing, saving CPU and storage cost when the same source file arrives multiple times through an automated ingestion feed.3
For Python 3.9 and later, always pass usedforsecurity=False when computing MD5 for non-security purposes. The keyword argument signals intent clearly to code reviewers and prevents the call from failing in FIPS-mode Python environments, which restrict MD5 by default. On standard systems, the argument has no behavioral effect and only serves as documentation of intent.4
Making the non-security intent visible
Pair the flag with code review, not with a broad exception. Name the variable after its purpose, such as cache_key or legacy_checksum, and avoid passing attacker-controlled data into paths that later treat the digest as proof of authenticity. The flag is a compatibility switch, not a safety guarantee. When a code reviewer sees usedforsecurity=False, the surrounding context should make the non-security purpose obvious: the variable name, the function name, and a short comment naming the specific legacy integration or internal workflow that calls for MD5 instead of SHA-256.
You make the boundary clear by naming the variable after its job, such as cache_key or legacy_checksum, rather than a generic hash that hides the risk. A reviewer who sees the intent in the name can approve the call site without tracing the whole integration. CapyToolkit includes MD5 for compatibility while warning that SHA-256 is the safer default once a value affects trust.
Keeping MD5 out of adversarial paths
Draw a hard line between accidental-corruption checks and attacker-controlled verification. A package mirror can use MD5 to detect a damaged download, but a login system, signature check, or public file comparison should not trust MD5 as a security boundary. CapyToolkit's Hash Generator includes MD5 for legacy compatibility, while still warning that SHA-256 is the safer default when the result affects trust.
De-duplicating large file sets with MD5 hash indexing
De-duplicating a large collection of files with MD5 hash indexing scans each file, computes its digest, and records the result alongside the file path in a dictionary mapping digests to lists of matching paths. Any digest that maps to more than one path identifies a set of duplicate candidates. For large collections, compute hashes incrementally using the chunked update() pattern to keep memory use constant regardless of file size.5
First-pass filtering by file size
Before computing MD5, filter by file size: two files with different sizes cannot be identical. Building a {size: [paths]} index first means you only compute MD5 for files that share a size with at least one other file, which can reduce unnecessary hashing in photo or document collections. Combine size filtering, MD5 pre-screening, and a final byte-for-byte comparison for any pairs where both size and MD5 match to guarantee correctness without relying on MD5 collision resistance.
Migrating from MD5 to SHA-256 in Python applications
Migrating a Python application from MD5 to SHA-256 for non-security checksums requires updating three things: the hash call, the storage column width, and any comparison logic. The hash call changes from hashlib.md5(data).hexdigest() to hashlib.sha256(data).hexdigest(). The storage column widens from 32 to 64 characters. Any string comparison that relied on the 32-character fixed length breaks silently if the column width is not also updated in the same migration.4
For databases that store MD5 hashes in indexed columns, a live migration computes SHA-256 values for new writes immediately, then backfills existing rows in a batch process running outside peak hours. During the transition window, maintain both columns and serve both values to consumers. Once all consumers confirm they are reading from the SHA-256 column, drop the MD5 column. This dual-write approach keeps the migration reversible until the cutover is explicitly confirmed.
Notes
Call hashlib.md5(data) with a bytes object to get a hash instance. The API is identical to SHA-256: .hexdigest() returns a 32-character hex string (half the length of SHA-256), .digest() returns the raw 16 bytes. For FIPS-mode Python environments, calling hashlib.md5() raises ValueError because MD5 is disabled. Python 3.9 added a usedforsecurity=False keyword argument specifically to opt out of the FIPS restriction: hashlib.md5(data, usedforsecurity=False).
Use MD5 only for checksums where collision resistance is not a security requirement - for example, a quick file de-duplication key or a cache key where a collision merely causes a cache miss. Never use MD5 for password hashing, digital signatures, or any context where an attacker could benefit from producing a collision.
Examples
Hash a string
import hashlib digest = hashlib.md5(b"Hello, World!").hexdigest() print(digest) # 32-char lowercase hex
FIPS-mode compatible (Python 3.9+)
import hashlib # usedforsecurity=False bypasses FIPS restrictions digest = hashlib.md5(b"cache_key_data", usedforsecurity=False).hexdigest()
File checksum
import hashlib
h = hashlib.md5()
with open("package.tar.gz", "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
print(h.hexdigest()) # 32-char hex Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
import hashlib digest = hashlib.md5(b"Hello, World!").hexdigest() print(digest) # 32-char lowercase hex
- 1.
Rivest, "The MD5 Message-Digest Algorithm," RFC 1321, April 1992. https://www.rfc-editor.org/rfc/rfc1321
- 2.
Turner and Chen, "Updated Security Considerations for the MD5 Message-Digest and the HMAC-MD5 Algorithms," RFC 6151, March 2011. https://www.rfc-editor.org/rfc/rfc6151
- 3.
CPython contributors, "hashlib.rst," github.com, Python documentation source, accessed June 2026. https://github.com/python/cpython/blob/main/Doc/library/hashlib.rst
- 4.
Python Software Foundation, "hashlib - Secure hashes and message digests," docs.python.org, accessed June 2026. https://docs.python.org/3/library/hashlib.html
- 5.
CPython contributors, "hashlib.rst," github.com, Python 3.9 documentation source, accessed June 2026. https://github.com/python/cpython/blob/3.9/Doc/library/hashlib.rst
MD5 is safe for non-security checksums - detecting accidental corruption in a file download, for example. CapyToolkit keeps MD5 examples separate from SHA-256 examples for that reason. It is not safe when an attacker could craft a collision: two files with the same MD5 hash have been demonstrated since 2004. For any security-sensitive use, switch to SHA-256.
It tells Python's hashlib that you are using MD5 for a non-security purpose, such as a cache key or legacy checksum. In FIPS-mode environments (common in US government infrastructure), MD5 is disabled by default. Passing usedforsecurity=False re-enables it for legitimate non-security uses.
MD5 produces a 128-bit (16-byte) digest. In hex, that is 32 lowercase characters. In Base64, it encodes to 24 characters (including two padding = characters).
Yes, for practical de-duplication. Two different files producing the same MD5 is theoretically possible but extraordinarily unlikely for non-adversarial inputs. For a trusted file corpus with no adversarial risk, MD5 is acceptable. If files come from external untrusted sources, use SHA-256 instead.
Use SHA-256 for new integrity checks and HMAC-SHA256 for keyed authentication. Keep the same hashlib pattern, but widen stored values from 32 characters to 64 characters and update any validation that assumed the shorter MD5 length.
MD5 in PHP
In PHP, MD5 support is easy to find, but it should be easy to classify. The legacy md5() function predates PHP's general-purpose hash extension and remains for backward compatibility.1 The unified hash('md5', $data) call does the same thing through the same interface as every other supported algorithm.2 For new code, hash() is preferable because it makes the algorithm explicit and makes a later migration to SHA-256 a smaller change.
Auditing legacy MD5 call sites in PHP codebases
If you see md5() in legacy PHP code, do not assume every call site has the same risk. Most legacy uses fall into three categories: session token generation, cache key derivation, and database checksum columns. Session token generation is the most dangerous category: any code that calls md5(session_id()) or md5(uniqid()) for authentication tokens must be replaced with bin2hex(random_bytes(32)), because session identifiers should come from a cryptographically secure random source rather than a fast, deterministic hash.3
Cache keys and database checksums are the safe uses. A cache key computed with md5($url . $params) only loses performance data when it collides, not user security. RFC 6151 still treats MD5 as acceptable where the checksum is used only to protect against accidental errors and the expected security service is stated clearly.4 Replacing those call sites with hash('sha256', $url . $params) is a straightforward swap that improves the collision margin without changing the architecture. When auditing a legacy codebase, separate the security-critical uses from the non-security uses before prioritizing which call sites to migrate first.
Using hash_equals even for public checksums
If your PHP code compares an MD5 checksum from a request, use hash_equals() only when the expected value is secret or derived from a secret. For public file checksums, a direct string comparison is fine. The important part is not to confuse a public integrity check with authentication. CapyToolkit's PHP examples separate MD5 checksums from HMAC-SHA256 verification so the trust boundary stays obvious.
Identifying MD5-hashed passwords in existing databases
Password columns in legacy PHP databases often contain 32-character hex strings from md5($password) or md5(md5($password)). You can identify these by checking the column length: MD5 hashes are always exactly 32 lowercase hex characters, while PHP bcrypt hashes from password_hash() are 60 characters and Argon2id hashes are longer still. A SQL query like SELECT COUNT(*) FROM users WHERE LENGTH(password_hash) = 32 identifies accounts with MD5 hashes in any MySQL or PostgreSQL database.
Migrating hashed passwords without forcing resets
You can migrate users silently by hashing on verified login: when a user logs in successfully against the old MD5 hash, immediately rehash the plaintext password with password_hash($password, PASSWORD_ARGON2ID) and update the stored record. OWASP recommends slow, adaptive password hashing such as Argon2id or bcrypt instead of fast hashes, because attackers can test many guesses against fast password hashes.5 After a migration window of 30 to 90 days, accounts that still hold MD5 hashes have not logged in during the period. At that point, force a password reset for those remaining accounts. This approach avoids disrupting active users while eliminating the vulnerable hashes from the database.
Keeping password migration auditable
Record which accounts still carry legacy MD5 hashes after the migration window, then force resets only for those remaining users. That targeted reset path keeps the database clean without penalizing active users who already moved to the stronger password hash. Track the migration progress in a dashboard or scheduled report that counts remaining MD5 rows daily, so the operations team can see the backlog shrink and confirm when the final forced-reset batch is safe to run.
You keep the migration honest by tracking which accounts still hold legacy hashes after the reset window closes, then forcing resets only for those remaining users. A dashboard that counts remaining MD5 rows daily turns an invisible debt into a number the team can watch fall. CapyToolkit separates MD5 checksums from HMAC verification so the trust boundary stays obvious while you clean up the old hashes.
For file upload integrity, hash_file covers most needs
For uploaded files, PHP's hash_file('sha256', $tmp_path) hashes the file contents directly, so the upload path can be passed without first reading the whole file into application memory.1 This pattern works for MD5 and SHA-256 equally by changing only the algorithm argument. Before saving a user-uploaded file to permanent storage, compute its SHA-256 with hash_file('sha256', $_FILES['file']['tmp_name']) and store the result alongside the file metadata. Subsequent access requests can re-verify the stored hash against the on-disk file to detect silent storage corruption.
For de-duplication across a large file store, a SHA-256 hash of each file serves as a reliable lookup key. Two files with the same hash are almost certainly identical; add a byte-for-byte comparison step only when storage savings justify the extra disk reads. In practice, SHA-256 collision probability is low enough that hash-based de-duplication without byte comparison is acceptable for non-adversarial file content.6
Notes
md5($string) and hash('md5', $string) produce identical 32-character lowercase hex output. Pass true as the second argument to md5() or the third argument to hash() for raw 16-byte binary output. Use hash_file('md5', $path) to hash a file path directly.
Never use MD5 for passwords. PHP's password_hash() function with PASSWORD_DEFAULT (currently bcrypt) or PASSWORD_ARGON2ID is the correct API for password storage. Using md5($password) for authentication is a security vulnerability because MD5 is fast and collision-broken, not because the PHP function itself is broken.
Examples
Hash a string
<?php
echo md5('Hello, World!'); // 32-char hex
echo hash('md5', 'Hello, World!'); // identical output Raw binary output
<?php
$raw = md5('data', true); // 16 bytes
$raw = hash('md5', 'data', true); // identical File checksum
<?php
$hash = hash_file('md5', '/path/to/file.tar.gz');
echo $hash; // 32-char hex, no memory load Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
<?php
echo md5('Hello, World!'); // 32-char hex
echo hash('md5', 'Hello, World!'); // identical output - 1.
The PHP Project, "Hash Message Digest Framework," php.net, accessed June 2026. https://www.php.net/manual/en/book.hash.php
- 2.
The PHP Project, "md5 - Calculate the md5 hash of a string," php.net, accessed June 2026. https://www.php.net/manual/en/function.md5.php
- 3.
OWASP Foundation, "Session Management Cheat Sheet," cheatsheetseries.owasp.org, accessed June 2026. https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- 4.
Turner and Chen, "Updated Security Considerations for the MD5 Message-Digest and the HMAC-MD5 Algorithms," RFC 6151, March 2011. https://www.rfc-editor.org/rfc/rfc6151
- 5.
OWASP Foundation, "Password Storage Cheat Sheet," cheatsheetseries.owasp.org, accessed June 2026. https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
- 6.
National Institute of Standards and Technology, "FIPS 180-4, Secure Hash Standard (SHS)," csrc.nist.gov, August 2015. https://csrc.nist.gov/pubs/fips/180-4/upd1/final
The output is identical. md5() is a standalone function that has existed since PHP 3. hash("md5") uses the hash extension introduced in PHP 5.1.2. Prefer hash() for new code because it uses a consistent interface that makes the algorithm explicit and is easier to refactor to a stronger algorithm.
No. MD5 is too fast for tokens and has known collision vulnerabilities. For tokens and API keys, use random_bytes(32) to generate cryptographically random bytes, then encode with bin2hex() or base64_encode(). Fast hashes like MD5 are inappropriate where brute force resistance matters.
Yes. hash_file() reads the file in internal chunks rather than loading it into memory, and the hashing happens in compiled C code. For large files, the memory advantage is significant.
Yes, when you can change both sides of the contract. SHA-256 gives you a stronger collision margin and is the default choice for new integrity checks. CapyToolkit's Hash Generator supports both so you can compute the legacy value for compatibility and the SHA-256 value for the replacement field.
No. Use password_hash() with PASSWORD_DEFAULT or PASSWORD_ARGON2ID. MD5 is fast, deterministic, and collision-broken, so it gives attackers a poor barrier once a password database is exposed.
MD5 in JavaScript
For JavaScript, MD5 remains a compatibility path rather than a native browser primitive. MDN lists the supported digest algorithms as SHA-1, SHA-256, SHA-384, and SHA-512, and notes that digest() requires the full input to be available before hashing.1 For legacy compatibility, js-md5 provides a JavaScript MD5 implementation for strings, ArrayBuffer, Uint8Array, and incremental create/update/hex usage in browsers and Node.js environments.2 In Node.js, the native crypto module wraps OpenSSL hash functions, so crypto.createHash('md5') is available where the bundled OpenSSL build includes MD5.3
Browser-based content fingerprinting with js-md5
Browser-based content fingerprinting calculates a hash of file or string content before sending it to a server. In JavaScript, js-md5 handles this with one function call: md5(arrayBuffer) for binary files or md5(string) for text.2 Because the hash runs entirely in the browser, it saves a round trip for simple de-duplication checks. A file upload form that hashes each file before submission can warn users about a duplicate file without actually sending it twice.
For legacy server APIs that expect an MD5 checksum in an ETag or Content-MD5 header, computing the hash client-side lets you include the checksum in the request header before the browser transmits the body. Amazon S3 general purpose buckets use the legacy Content-MD5 header as an end-to-end integrity check: S3 compares the object against the provided MD5 value and returns an error if the values do not match.4
Keeping the checksum separate from trust
A client-side MD5 checksum can help you detect accidental corruption or duplicate uploads, but it does not prove that a file came from a trusted party. Anyone who can modify the upload can also recompute MD5 unless the server verifies a stronger signature or HMAC. CapyToolkit keeps MD5 in the Hash Generator for compatibility with those legacy checksums, while recommending SHA-256 or HMAC-SHA256 for trust decisions.
You keep the distinction honest by using the MD5 value only to detect accidental change, never as proof that a file came from a trusted party. Anyone who can alter the upload can also recompute the digest unless the server verifies a stronger signature or HMAC. CapyToolkit keeps MD5 available for compatibility while pointing you toward SHA-256 whenever the checksum must influence a trust decision.
When the server API returns MD5 checksums, compare them locally
When the server-side API you call returns an MD5 checksum as part of its response, you receive a 32-character hex string to compare against your locally computed value.5 In JavaScript: const localMd5 = md5(await response.arrayBuffer()). Compare the result against the header or response field using a direct string comparison. Timing attacks are not a concern in this context because you are comparing a public file checksum, not a secret HMAC.
Avoiding MD5 in new security flows
If you are building a new browser-based application and need to hash data for any security-sensitive purpose, skip MD5 entirely and use crypto.subtle.digest('SHA-256', data) instead.1 The async pattern costs one await, and the security margin is much stronger than MD5 for collision-resistant applications. Keep js-md5 only for code paths that must interoperate with legacy systems that publish MD5 values and cannot yet be upgraded to SHA-256.
Streaming MD5 for large files avoids memory spikes
Streaming MD5 over large files avoids the memory spike that comes from calling file.arrayBuffer() on a multi-gigabyte input. The js-md5 library exposes an incremental API: call const h = md5.create(), then h.update(chunk) in a loop over file slices, and retrieve the final hex digest with h.hex(). Read the file in slices using file.slice(start, end) and convert each slice to an ArrayBuffer before calling update().
Reading in slices keeps memory use bounded for files that cannot fit in available browser memory at once. SubtleCrypto does not natively support streaming digest updates, so the js-md5 incremental API is more practical for large file use cases where the entire file must not be held in memory at once.
Checking slice order before hashing
Keep slice offsets monotonic and reuse the same order every time you compute a checksum. A different slice sequence produces the same final file bytes but a different MD5 result, which makes duplicate detection look inconsistent even when the file itself has not changed. Write a helper that accepts a file and a fixed chunk size, then iterates from zero to the file length in equal steps, so every invocation processes the same byte ranges in the same order regardless of when or where the hashing runs.
Notes
Install js-md5 via npm: npm install js-md5. Import with import md5 from 'js-md5' (ESM) or const md5 = require('js-md5') (CJS). The function accepts strings, ArrayBuffers, and Uint8Arrays, returning a 32-character lowercase hex string. For file hashing, read the file as an ArrayBuffer and pass it directly.
In Node.js, the built-in crypto module does support MD5: crypto.createHash('md5').update(data).digest('hex'). Use the native module for server-side Node.js code rather than js-md5, since the native implementation is faster and uses OpenSSL. The js-md5 package is mainly useful when the same code must run in both browser and Node.js environments.
Examples
Browser - hash a string (js-md5)
import md5 from 'js-md5';
const hash = md5('Hello, World!');
console.log(hash); // 32-char lowercase hex Browser - hash a File
import md5 from 'js-md5';
async function md5File(file) {
const buf = await file.arrayBuffer();
return md5(buf);
} Node.js - native crypto (no npm needed)
const crypto = require('crypto');
const hash = crypto.createHash('md5')
.update('Hello, World!')
.digest('hex');
console.log(hash); // 32-char hex Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Browser - hash a string (js-md5)
import md5 from 'js-md5';
const hash = md5('Hello, World!');
console.log(hash); // 32-char lowercase hex - 1.
MDN Web Docs, "SubtleCrypto: digest() method," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
- 2.
Chen, "js-md5 README," github.com, accessed June 2026. https://github.com/emn178/js-md5/blob/master/README.md
- 3.
Node.js Foundation, "Crypto," nodejs.org, accessed June 2026. https://nodejs.org/api/crypto.html
- 4.
Amazon Web Services, "PutObject - Amazon S3," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
- 5.
Turner and Chen, "Updated Security Considerations for the MD5 Message-Digest and the HMAC-MD5 Algorithms," RFC 6151, March 2011. https://www.rfc-editor.org/rfc/rfc6151
The Web Crypto specification was designed for security-sensitive operations. MD5's known collision vulnerabilities make it unsuitable for cryptographic use, so the spec authors excluded it. If you need MD5 for legacy compatibility, use a JavaScript library rather than the native crypto API.
js-md5 computes correct MD5 hashes. The safety question is about MD5 itself, not the library. Use js-md5 when MD5 output is required for interoperability with a legacy system. For any new security-sensitive use, choose SHA-256 via SubtleCrypto.
js-md5 accepts ArrayBuffer directly, so the limit is available browser memory. For large files (multi-GB), read the file in slices with file.slice() and hash incrementally using md5.create() and .update() from the js-md5 streaming API.
Use SHA-256 for any new project unless the other side of the integration explicitly requires MD5. SHA-256 is supported by SubtleCrypto in modern browsers and gives a stronger collision margin. CapyToolkit keeps MD5 available for legacy checksums, but its default security guidance points to SHA-256.
Yes. Node.js exposes MD5 through the built-in crypto module when the bundled OpenSSL build includes it. Use crypto.createHash('md5') for server-side code, and reserve js-md5 for browser code or shared JavaScript that must run in both environments.
SHA-512 in Python
With Python, SHA-512 gives you a longer SHA-2 digest without adding a dependency. hashlib exposes SHA-512 as a guaranteed constructor alongside SHA-256, and it returns a 512-bit digest that appears as 128 lowercase hex characters.1 SHA-384 and SHA-512 use 64-bit words, while SHA-224 and SHA-256 use 32-bit words, so the practical speed difference depends on the CPU and OpenSSL build.2 SHA-512/256 is a FIPS 180-4 addition that truncates SHA-512 to 256-bit output.3
Choosing between SHA-512 and SHA-256 for Python workloads
Choosing SHA-512 over SHA-256 in Python makes the most practical difference when you hash large volumes of data on a 64-bit processor, because the wider word size allows the algorithm to process more data per clock cycle. Python's hashlib is linked against the OpenSSL build used by your Python distribution, so algorithm availability and performance can vary by platform. SHA-512 uses 64-bit word arithmetic while SHA-256 uses 32-bit word arithmetic, which is why SHA-512 can be the more cost-effective 256-bit hashing choice on 64-bit architectures when the implementation is optimized for that hardware.24
Benchmark the choice with your specific workload using Python's timeit module: timeit.timeit(lambda: hashlib.sha512(data).hexdigest(), number=100000) versus the equivalent SHA-256 call with a representative data sample. The result varies by CPU architecture, Python version, and OpenSSL build, so benchmarking before committing avoids a wrong assumption baked into long-lived production code that is expensive to change later.
Treating the benchmark as a deployment decision
Run the benchmark on the same container image, CPU family, and Python build that production uses. A SHA-512 result that wins on your laptop can change on a 32-bit or heavily virtualized host, so the deployment target should decide the default rather than a generic benchmark copied from another environment. Record the benchmark result alongside the CPU model and OpenSSL version in your infrastructure documentation, so future team members can tell whether the stored measurement still applies after a hardware or OS upgrade.
Choosing output length before storage design
A SHA-512 hex string is twice as long as SHA-256, and that affects indexes, logs, and API payloads. If you only need 256-bit output, SHA-512/256 can preserve the SHA-512 family structure while fitting the same 64-character storage format as SHA-256. CapyToolkit's Hash Generator shows the full algorithm context so you do not copy a 128-character value into a field sized for 64 characters.
You prevent schema surprises by deciding the digest length before any table or index depends on it, because a wider value changes column width, log lines, and API payloads. A SHA-512 hex string is twice as long as a SHA-256 string, and that difference shows up everywhere the value is stored or compared. CapyToolkit shows the full algorithm context so you can match the output to the field that will hold it.
In HMAC authentication, SHA-512 doubles the MAC length
For HMAC authentication where an API explicitly requires HMAC-SHA512, pass hashlib.sha512 as the digestmod argument to hmac.new(). Python's HMAC API accepts a digest name, digest constructor, or module, and the HMAC digest length follows the selected digest size.5 The resulting HMAC-SHA512 value is 64 bytes (128 hex characters) instead of 32 bytes, which doubles the storage and bandwidth cost of each signature.
Configuring the digestmod at runtime
When your application supports configurable HMAC algorithms, store the algorithm name as a string and resolve it to the hashlib attribute at runtime: digestmod = getattr(hashlib, config.hmac_algorithm). This lets you switch from sha256 to sha512 through configuration without touching application code. Always validate the configured algorithm name against an explicit whitelist before calling getattr to prevent an attacker who controls the configuration from supplying an unexpected algorithm name.
SHA-512/256 delivers 64-bit speed with 256-bit output
SHA-512/256 gives you SHA-512's 64-bit word structure while producing a 256-bit digest identical in length to SHA-256. NIST added SHA-512/256 to FIPS 180-4 as a truncated SHA-512-based algorithm.3 The digest is 64 hex characters, fitting in the same database column as SHA-256, but the internal structure uses SHA-512's initialization constants and 64-bit arithmetic.
SHA-512/256 is worth benchmarking whenever your application hashes medium to large inputs on a 64-bit server and does not need the full 512-bit digest that sha512 produces. Run hashlib.algorithms_available to confirm that your Python runtime exposes sha512_256 before using it in production code. Python can make additional OpenSSL-provided algorithms available by name, but those algorithms are not guaranteed on every installation, so the availability check prevents a runtime error on deployment targets you did not test against locally.1
Notes
Call hashlib.sha512(data).hexdigest() to get a 128-character lowercase hex string. The raw digest is 64 bytes. Hashlib also provides hashlib.sha512_256() (SHA-512/256), a truncated variant that produces a 256-bit output using SHA-512's internal structure - useful when you want SHA-512's 64-bit performance on a 64-bit CPU but only need 256 bits of output.
For HMAC, pass hashlib.sha512 as the digestmod argument to hmac.new(). The resulting HMAC is 64 bytes (128 hex chars). Some APIs explicitly require HMAC-SHA512 for higher security margins; check your API documentation for the required algorithm.
Examples
Hash a string
import hashlib digest = hashlib.sha512(b"Hello, World!").hexdigest() print(len(digest)) # 128 print(digest[:16]) # first 16 of 128 hex chars
Hash a file
import hashlib
h = hashlib.sha512()
with open("large_file.bin", "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
print(h.hexdigest()) # 128-char hex SHA-512/256 (truncated variant)
import hashlib # Same speed as SHA-512, 256-bit output digest = hashlib.sha512_256(b"Hello, World!").hexdigest() print(len(digest)) # 64
sha512_256 availability depends on the linked OpenSSL build; check hashlib.algorithms_available before using it.
Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
import hashlib digest = hashlib.sha512(b"Hello, World!").hexdigest() print(len(digest)) # 128 print(digest[:16]) # first 16 of 128 hex chars
- 1.
Python Software Foundation, "hashlib - Secure hashes and message digests," docs.python.org, accessed June 2026. https://docs.python.org/3/library/hashlib.html
- 2.
Eastlake and Hansen, "US Secure Hash Algorithms (SHA and HMAC-SHA)," RFC 4634, July 2006. https://www.rfc-editor.org/rfc/rfc4634.txt
- 3.
Dang, "Changes in Federal Information Processing Standard (FIPS) 180-4, Secure Hash Standard," NIST, January 2013. https://www.nist.gov/publications/changes-federal-information-processing-standard-fips-180-4-secure-hash-standard
- 4.
Gueron, Johnson, and Walker, "SHA-512/256," IACR Cryptology ePrint Archive, Paper 2010/548. https://eprint.iacr.org/2010/548
- 5.
Python Software Foundation, "hmac - Keyed-Hashing for Message Authentication," docs.python.org, accessed June 2026. https://docs.python.org/3/library/hmac.html
Both are currently secure. SHA-512's 512-bit output provides a larger security margin, but SHA-256's 256-bit output already exceeds any known computational attack by an enormous factor. For most applications, SHA-256 is sufficient and produces smaller digests. Choose SHA-512 when your system explicitly requires it or when hashing performance matters on 64-bit hardware.
SHA-512 operates on 64-bit words internally, while SHA-256 uses 32-bit words. On 64-bit processors, SHA-512 can process more data per clock cycle, making it faster for large inputs despite producing a longer digest. On 32-bit processors or constrained hardware, SHA-256 is faster.
SHA-512/256 is a truncated variant of SHA-512 that produces 256-bit output. It uses SHA-512's 64-bit internal arithmetic (faster on 64-bit CPUs) but outputs only 256 bits. It has different initial hash values from SHA-256, so its output differs for the same input.
SHA-512 produces 512 bits, which is 64 raw bytes. In hex, the digest is 128 lowercase characters. In Base64 with padding, it is 88 characters.
No. When generating SHA-512 with CapyToolkit's Hash Generator, your input stays in the browser and is not uploaded. The page mirrors the local hashlib-style result and formats the hash without sending text or file bytes to a server.
SHA-512 in Node.js
In Node.js, SHA-512 uses the same crypto.createHash interface as SHA-256: change the algorithm string and the output length doubles.12 Because Node.js wraps OpenSSL, SHA-512 performance depends on the same OpenSSL build used by your Node.js distribution, and SHA-512 can be more cost-effective on 64-bit architectures when the implementation is optimized for that hardware.3
On 64-bit servers, SHA-512 throughput can exceed SHA-256
On 64-bit servers, SHA-512 can be more cost-effective than SHA-256 for large hashing workloads when the OpenSSL build behind Node.js is optimized for that hardware, because the wider internal word size allows the algorithm to process more data per clock cycle on modern processors.3 SHA-512 operates on 64-bit words while SHA-256 operates on 32-bit words, so the algorithm maps naturally to the wider registers used by modern 64-bit CPUs.2 This architectural alignment means that on a 64-bit x86 or ARM server, SHA-512 can actually finish faster than SHA-256 for the same input, despite producing a digest twice as long.
Benchmark both algorithms with your actual data sizes using performance.now() before assuming SHA-256 is always the right choice. The crossover point varies by input size, CPU model, and OpenSSL build, so measuring both algorithms under your actual workload avoids a wrong assumption baked into long-running production code that is expensive to change later. If your application hashes large files or bulk data in a Node.js stream, the performance difference can be significant at scale.
For Node.js services that compute SHA-512 in request handlers, keep the crypto module call short and isolated so tests can replace the helper without touching route logic. That separation also makes output-format assertions easier to review, because a test that asserts on a 128-character hex string immediately reveals whether the service is producing SHA-512 or accidentally falling back to SHA-256.
Batching measurements with production-like buffers
When benchmarking, feed buffers that resemble your real request sizes instead of tiny strings. A one-off measurement can hide stream overhead, garbage collection pauses, and OpenSSL startup costs. Run the same input shape you expect in production, then keep the faster default only if the result is stable across repeated measurements. Run each benchmark for at least one hundred iterations and discard the first ten as warm-up, so JIT compilation and buffer pool initialization do not skew the numbers you compare.
Treating output length as an API contract
SHA-512's 128-character hex output is useful, but it is not interchangeable with SHA-256. A database column, log parser, or JSON schema that expects 64 hex characters will reject a SHA-512 digest. Before switching algorithms, update the receiving contract and tests so the longer value is expected everywhere. CapyToolkit's Hash Generator labels the selected algorithm and output format to reduce copy-and-paste mistakes when you move from testing to production.
You treat the digest length as part of the contract rather than an incidental detail, because a longer value silently breaks a schema that expected the shorter one. A database column, log parser, or JSON field sized for 64 characters rejects a 128-character SHA-512 digest without an obvious error message. CapyToolkit labels the algorithm and format so the copied value carries the length the receiving system must expect.
Streaming SHA-512 file verification with the pipeline API
Streaming SHA-512 over a large file in Node.js uses the same pattern as SHA-256: pipe a fs.createReadStream() into a crypto.createHash('sha512') hash object, or use the async iterator pattern with for await. The output length is the only difference: digest('hex') returns 128 hex characters instead of 64.4 Verify the output length in any test that checks hash format to catch accidental algorithm mismatches early in development.
Using stream.pipeline for error propagation
Replace bare .pipe() calls with the stream.pipeline() utility from Node's core when robust error handling matters. pipeline(fs.createReadStream(path), hash, callback) propagates stream errors to the callback automatically, while .pipe() swallows them without invoking your error handler. For production file verification, pipeline ensures that read errors, permission errors, and premature stream ends surface as callback errors rather than producing a silently incorrect hash derived from a partially read file.5
HMAC-SHA512 suits long-lived API signing keys
HMAC-SHA512 is a practical choice for JWT HS512 tokens and API signing keys that require a larger MAC size than HMAC-SHA256.6 The Node.js pattern is crypto.createHmac('sha512', key).update(message).digest('hex'), producing a 128-character hex output.1 The choice most often comes down to what the consuming API or JWT library requires.
For JWT tokens specifically, the HS512 algorithm identifier signals HMAC-SHA512 to compatible libraries.6 If your server issues tokens and multiple downstream services verify them, all services must agree on the algorithm identifier. Changing from HS256 to HS512 requires coordinating the algorithm string across every service that calls jwt.verify() or its equivalent; a mismatch causes verification failures with no obvious error message pointing to the algorithm disagreement.
Notes
Use crypto.createHash('sha512').update(data).digest('hex') to get a 128-character lowercase hex string. Node.js also supports 'sha512-256' (the truncated variant) and 'sha384' through the same API - call crypto.getHashes() to see the full list available on your OpenSSL build.
For HMAC-SHA512, replace createHash with createHmac: crypto.createHmac('sha512', key).update(message).digest('hex'). The output is 64 bytes (128 hex characters). Some JWT implementations use RS512 (RSA + SHA-512) or HS512 (HMAC-SHA512) for higher security margins - verify your library's algorithm identifier against what your server expects.
Examples
Hash a string
const crypto = require('crypto');
const hash = crypto.createHash('sha512')
.update('Hello, World!')
.digest('hex');
console.log(hash.length); // 128 HMAC-SHA512
const crypto = require('crypto');
const mac = crypto.createHmac('sha512', 'secret_key')
.update('payload')
.digest('hex');
console.log(mac.length); // 128 File hash with async iterator
const crypto = require('crypto');
const fs = require('fs');
async function sha512File(path) {
const h = crypto.createHash('sha512');
for await (const chunk of fs.createReadStream(path)) h.update(chunk);
return h.digest('hex');
} Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
const crypto = require('crypto');
const hash = crypto.createHash('sha512')
.update('Hello, World!')
.digest('hex');
console.log(hash.length); // 128 - 1.
Node.js Foundation, “Crypto,” nodejs.org, accessed June 2026. https://nodejs.org/api/crypto.html
- 2.
Eastlake and Hansen, “US Secure Hash Algorithms (SHA and HMAC-SHA),” RFC 4634, July 2006. https://www.rfc-editor.org/rfc/rfc4634.txt
- 3.
Gueron, Johnson, and Walker, “SHA-512/256,” IACR Cryptology ePrint Archive, Paper 2010/548. https://eprint.iacr.org/2010/548
- 4.
NIST, “Secure Hash Standard,” FIPS 180-4, August 2015. https://www.nist.gov/publications/secure-hash-standard
- 5.
Node.js Foundation, “Stream,” nodejs.org, accessed June 2026. https://nodejs.org/api/stream.html
- 6.
Jones, “JSON Web Algorithms (JWA),” RFC 7518, May 2015. https://datatracker.ietf.org/doc/html/rfc7518.html
Choose SHA-512 when your integration partner or specification requires it, when you want the larger security margin for long-lived signatures, or when benchmarking shows it is faster for your data sizes on your hardware. For general-purpose hashing, SHA-256 is the de-facto standard.
Call crypto.getHashes() - it returns an array of supported algorithm strings. The list depends on the OpenSSL version bundled with your Node.js build. sha512, sha256, md5, and sha1 are always present.
SHA-512 produces 64 raw bytes, so the hex digest is 128 lowercase characters. Store that length in tests and database schemas before changing an API from SHA-256 to SHA-512.
Yes. Use crypto.createHmac('sha512', key).update(message).digest('hex'). The output is 128 hex characters, so every service that verifies the HMAC must expect that length.
Yes. CapyToolkit's Hash Generator computes SHA-512 locally in your browser, like a local Node.js crypto module example that never leaves your machine. The text or file you enter is not uploaded to calculate SHA-512, SHA-256, MD5, or HMAC values.
SHA-1 in Python
When Python code needs SHA-1 today, it is usually for compatibility rather than new trust decisions. Python's hashlib provides it through the same interface as SHA-256 and MD5, which makes compatibility straightforward even when the security posture is not.1 The SHAttered attack (2017) demonstrated that two different PDF files can be crafted to share the same SHA-1 hash, permanently marking SHA-1 as insecure for content authentication.2 Git historically used SHA-1 for object IDs and is actively migrating to SHA-256.3
SHA-1 persists in legacy protocol integrations
SHA-1 persists in several integration scenarios where migration to SHA-256 is not yet complete, most notably in authentication systems and legacy storage APIs that predate the SHA-2 family. TOTP and HOTP two-factor authentication seeds defined in RFC 6238 default to HMAC-SHA1, so authenticator apps and hardware tokens that implement the spec produce HMAC-SHA1 values unless explicitly configured otherwise.4 Switching to SHA-256 requires passing digest=hashlib.sha256 to the constructor, and the server and all client applications must switch simultaneously, which is the main reason many organizations still run SHA-1 in production despite knowing its weaknesses.
If you maintain Python code that interfaces with a legacy storage API expecting SHA-1, hashlib.sha1(data, usedforsecurity=False).hexdigest() produces the correct output.1 Document the usedforsecurity=False argument with a comment noting the specific legacy API integration that requires SHA-1, so future maintainers understand the constraint rather than replacing it with SHA-256 and breaking the integration.
Separating compatibility from security
The key distinction is whether SHA-1 is being used to interoperate or to prove trust. A legacy checksum that only detects accidental corruption can remain documented and isolated. A signature, certificate, token, or public file authenticity check should move to SHA-256 or HMAC-SHA256. CapyToolkit includes SHA-1 in the Hash Generator for compatibility checks, not as a recommendation for new security-sensitive systems. When auditing existing code, look for sha1 call sites that handle externally supplied content or drive authentication decisions, since those carry collision risk that a simple checksum over internal data does not.
You draw the line by asking whether the value must prove trust or merely detect accidental change, because those two jobs demand different algorithms. A legacy checksum over internal data can stay SHA-1, while any signature, certificate, or token should move to SHA-256. CapyToolkit includes SHA-1 for compatibility checks rather than recommending it for new security-sensitive systems that need collision resistance.
When you need to reproduce a Git blob object ID in Python, prepend the header
When your Python application needs to compute a Git-compatible blob object ID, prepend the Git header before hashing the content bytes. For any file content, the header format is blob <content_length>\0 where the length is the byte count of the content.3 Concatenate the header bytes and the content bytes, then compute SHA-1: hashlib.sha1(header + content, usedforsecurity=False).hexdigest(). This produces a 40-character hex string because SHA-1 outputs 160 bits, which maps to 40 hexadecimal characters.5
Verifying repository files in Python deployment scripts
Python scripts that verify cloned repository files against expected Git OIDs can use this pattern to confirm that a specific file's content matches a commit tree's recorded hash without needing to install the full Git command-line tool. This is useful in deployment scripts that verify critical configuration files were not modified after commit. Compare the Python-computed OID against the OID recorded in the repository using git ls-tree HEAD <file> to automate the check without importing a full Git client library.
Planning a SHA-1 to SHA-256 migration in Python requires three steps
Migrating a Python application from SHA-1 to SHA-256 requires identifying all call sites, auditing each for security criticality, and updating storage columns in the right order. A codebase-wide search for sha1 in Python files and string configuration values finds both direct calls and algorithm name strings passed to configurable hashing functions. Next, audit each call site for security criticality: signatures, authentication, and certificate handling require immediate migration, while checksums, cache keys, and internal IDs can follow in a later release.2
For storage systems that record SHA-1 hashes in databases or files, the migration adds a new SHA-256 column, backfills it by re-computing from source data, then updates readers to use the new column before dropping the old one. For systems where re-computing from source data is not possible, plan a gradual transition where new entries receive SHA-256 hashes and old SHA-1 entries are converted on first access with a double-write to both columns during the transition window.
Freezing legacy SHA-1 call sites during migration
Once the inventory is complete, prevent new SHA-1 call sites with code review and lint rules. The migration succeeds only if new code stops adding legacy hashes while existing callers move to SHA-256. That policy keeps the remaining SHA-1 list finite instead of growing during the transition. A codebase that continues adding SHA-1 call sites while migrating existing ones never finishes the work, because each new call site re-introduces the same collision risk the migration is trying to eliminate. Add a lint rule or CI check that flags any new hashlib.sha1 call without a usedforsecurity=False argument, so the migration backlog shrinks monotonically rather than accumulating new entries. CapyToolkit reports the algorithm alongside each generated hash, which helps reviewers spot legacy algorithm choices during code review.
Notes
Call hashlib.sha1(data).hexdigest() to get a 40-character lowercase hex string (160 bits). The API is identical to hashlib.sha256 - only the algorithm name and output length differ. In FIPS-mode environments, SHA-1 may be restricted in the same way as MD5; the usedforsecurity=False keyword argument (Python 3.9+) opts out of that restriction for non-security uses.
The primary legitimate use of SHA-1 today is reading checksums published by legacy systems or computing Git object IDs for compatibility. New code should always use SHA-256 or SHA-512. Certificate authorities stopped issuing SHA-1 TLS certificates in 2017, and all major browsers reject SHA-1 signatures in certificates.
Examples
Hash a string
import hashlib digest = hashlib.sha1(b"Hello, World!").hexdigest() print(len(digest)) # 40 print(digest) # 40-char lowercase hex
Git-style object ID (for reference)
import hashlib
# Git prefixes content with "blob <size>\0" before hashing
content = b"Hello, World!"
header = f"blob {len(content)}\0".encode()
git_oid = hashlib.sha1(header + content).hexdigest()
print(git_oid) # 40-char SHA-1, matches git hash-object output Git is migrating to SHA-256 object IDs (sha256 transition plan). SHA-1 OIDs remain the default for repositories not yet transitioned.
Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Hash a string
import hashlib digest = hashlib.sha1(b"Hello, World!").hexdigest() print(len(digest)) # 40 print(digest) # 40-char lowercase hex
- 1.
Python Software Foundation, “hashlib,” Python 3 documentation. https://docs.python.org/3/library/hashlib.html
- 2.
NIST, “Research Results on SHA-1 Collisions,” February 2017. https://csrc.nist.gov/News/2017/Research-Results-on-SHA-1-Collisions
- 3.
Git Project, “Git hash function transition,” accessed June 2026. https://github.com/git/git/blob/master/Documentation/technical/hash-function-transition.adoc
- 4.
M'Raihi et al., “TOTP: Time-Based One-Time Password Algorithm,” RFC 6238, May 2011. https://datatracker.ietf.org/doc/html/rfc6238.html
- 5.
Eastlake and Jones, “US Secure Hash Algorithm 1 (SHA1),” RFC 3174, September 2001. https://www.rfc-editor.org/rfc/rfc3174.txt
SHA-1 is safe for non-security checksums and Git object IDs (where collisions are impractical at repository scale without adversarial intent). It is not safe for digital signatures, TLS certificates, or any context where an attacker can choose the input. NIST deprecated SHA-1 for all security applications as of 2017.
SHAttered (2017) was the first practical SHA-1 collision: two different PDF files with identical SHA-1 hashes. Researchers from Google and CWI Amsterdam demonstrated it required approximately 9,200,000,000,000,000,000 SHA-1 computations, expensive but achievable with modern hardware. The attack proved SHA-1 unsuitable for security-critical uses.
Git has used SHA-1 for object IDs since its creation in 2005. The practical collision risk in a Git repository is low because the attacker would need to control the commit being merged. Git 2.29+ supports SHA-256 object IDs via the -object-format=sha256 flag for new repositories, but SHA-1 repositories vastly outnumber SHA-256 ones today.
Change hashlib.sha1() to hashlib.sha256() everywhere. The output length changes from 40 to 64 hex characters. Update any storage columns, comparison logic, or published checksums accordingly. If you store hashes in a database, plan a migration to update existing rows.
No. CapyToolkit includes SHA-1 in the Hash Generator for legacy compatibility and Git-style checks, but new security-sensitive code should use SHA-256, SHA-512, or HMAC-SHA256 depending on the trust requirement.
HMAC-SHA256 in Python
For Python services, HMAC-SHA256 is the right choice when a plain digest is not enough. Python's hmac module implements the RFC 2104 keyed-hash construction by combining a shared secret key with a fixed-digest hash function such as SHA-256.1 The result is a message authentication code: it proves the message content is intact and that the sender knew the key, while a plain SHA-256 hash proves neither one alone.2
When a third-party service sends a webhook, the verification steps are always the same
When a third-party service sends a webhook, it documents three things: the algorithm, the header name carrying the signature, and whether the header value includes an algorithm prefix like sha256=. GitHub uses X-Hub-Signature-256 with the sha256= prefix and says the signature is generated from the webhook secret and payload contents.3 Stripe uses HMAC-SHA256 with a separate Stripe-Signature header that includes a timestamp and one or more signatures, so the timestamp must be part of the signed payload before comparison.4 Read the provider's documentation for these specifics before writing the verification function, since the signed payload construction varies across providers.
The core Python code stays constant regardless of provider: hmac.new(key.encode('utf-8'), body_bytes, hashlib.sha256).hexdigest().1 Wrap this in a function that accepts the raw request bytes, the secret, and the received signature, then returns a boolean. Pass the raw request body as bytes, not a parsed string or decoded JSON object, because any re-serialization changes the byte sequence and produces a different HMAC that never matches the server-computed value; Stripe explicitly requires the raw request body for signature verification, and GitHub's validation guidance also computes the expected signature from the payload contents.
Matching the provider's exact signed payload
Build the verification function around the provider's documented payload order, timestamp rules, and prefix format. A correct HMAC algorithm still fails if the signed bytes differ from the sender's bytes, so the raw body and header parsing belong in the same helper that computes the expected signature. Write a provider-agnostic verifier that accepts a payload-constructor callback, so adding a new webhook source requires only a small function that builds the signed string for that provider rather than duplicating the entire HMAC comparison logic.
You avoid a common failure by building the verification helper around the provider's documented payload order, timestamp rules, and prefix format before you trust the result. A correct HMAC algorithm still fails when the signed bytes differ from what the sender computed, even by a single character. CapyToolkit keeps HMAC key handling explicit because correctness depends on the exact bytes used as both the key and the message.
Managing HMAC secrets securely in Python services
Managing HMAC secrets as environment variables protects them from appearing in source control. Read the secret with os.environ['WEBHOOK_SECRET'] and encode to bytes with .encode('utf-8') before passing to hmac.new(). Never hardcode secrets in source files, even temporarily; GitHub recommends storing webhook secret tokens securely on the server and never hardcoding or pushing them to a repository.3 For secrets that rotate, read the current value on each request rather than caching it at application startup, so a rotation takes effect immediately without requiring a service restart.
Rotating HMAC secrets without losing webhook delivery
During a rotation, configure both the old and new secret in your secret store simultaneously. The verification function tries both keys and accepts the request if either produces a valid HMAC. Stripe supports keeping the previous endpoint secret active for up to 24 hours while a new secret is rolled, which gives production services a bounded dual-secret window before the old secret is removed.4
In pytest, verify HMAC output against published test vectors
In pytest, test HMAC-SHA256 against the RFC 4231 test vectors, which are publicly documented and serve as a ground truth for any HMAC-SHA256 implementation.5 The first test vector uses a 20-byte key of 0x0b repeated and the message Hi There; the correct HMAC-SHA256 result is b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7.5 Include this assertion as a module-level constant and run it in every CI pipeline to catch encoding bugs, algorithm selection errors, and unexpected library changes.
For application-specific tests, construct known inputs and expected outputs using a reference implementation in a different language or a verified online HMAC-SHA256 calculator. Any deviation between your Python result and the reference confirms a bug in key encoding, message encoding, or algorithm selection rather than an environment difference. Testing against at least two distinct reference vectors rules out an accidentally correct but fundamentally wrong implementation.
Recording key version metadata
Include a key identifier in the signed token or webhook envelope when your system rotates HMAC keys. The identifier lets the verifier choose the right secret before comparing the MAC, while the secret itself stays out of logs. CapyToolkit's HMAC examples keep the key handling explicit because HMAC correctness depends on the exact bytes used as the key and message.
Notes
Create an HMAC with hmac.new(key, msg, digestmod) where key and msg are bytes objects and digestmod is hashlib.sha256. Retrieve the hex digest with .hexdigest(). For streaming data, omit msg from the constructor and call .update() incrementally.
For timing-safe comparison, use hmac.compare_digest(a, b) rather than ==. The function is designed to avoid content-based short-circuiting, which reduces timing-analysis risk when comparing cryptographic values.
Examples
Compute HMAC-SHA256
import hmac, hashlib
mac = hmac.new(
key=b"secret_key",
msg=b"payload_data",
digestmod=hashlib.sha256
)
print(mac.hexdigest()) # 64-char hex HMAC Verify a webhook signature (GitHub style)
import hmac, hashlib
def verify_signature(payload_body: bytes, secret: str, signature_header: str) -> bool:
"""signature_header looks like 'sha256=<hex>'"""
expected = "sha256=" + hmac.new(
key=secret.encode("utf-8"),
msg=payload_body,
digestmod=hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header) Streaming HMAC
import hmac, hashlib mac = hmac.new(b"key", digestmod=hashlib.sha256) mac.update(b"first chunk") mac.update(b"second chunk") print(mac.hexdigest()) # same as hmac.new(b"key", b"first chunksecond chunk", hashlib.sha256)
Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Compute HMAC-SHA256
import hmac, hashlib
mac = hmac.new(
key=b"secret_key",
msg=b"payload_data",
digestmod=hashlib.sha256
)
print(mac.hexdigest()) # 64-char hex HMAC - 1.
Python Software Foundation, “hmac,” Python 3 documentation. https://docs.python.org/3/library/hmac.html
- 2.
Krawczyk et al., “HMAC: Keyed-Hashing for Message Authentication,” RFC 2104, February 1997. https://www.rfc-editor.org/rfc/rfc2104.txt
- 3.
GitHub Docs, “Validating webhook deliveries,” accessed June 2026. https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
- 4.
Stripe Docs, “Receive Stripe events in your webhook endpoint,” accessed June 2026. https://docs.stripe.com/webhooks?verify=verify-manually
- 5.
Nystrom, “Identifiers and Test Vectors for HMAC-SHA-224, HMAC-SHA-256, HMAC-SHA-384, and HMAC-SHA-512,” RFC 4231, December 2005. https://www.rfc-editor.org/rfc/rfc4231.txt
A hash is deterministic: anyone who knows the input can reproduce the output. An HMAC requires knowing the secret key as well. CapyToolkit separates plain hash and HMAC modes because that distinction controls whether the value can authenticate a sender. If you hash a message and publish both the message and the hash, anyone can verify or forge the hash. With HMAC, only parties who hold the key can produce or verify the correct MAC.
hmac.new() requires bytes because the RFC 2104 algorithm operates on raw byte sequences. Call key.encode("utf-8") to convert a string key to bytes. For keys derived from random data (like API secrets), use bytes directly from os.urandom() or base64-decode the published secret.
Decode it with import base64; key = base64.b64decode(encoded_secret), then pass the resulting bytes to hmac.new(). Many services (Stripe, Twilio) publish raw hex or base64 secrets - check their documentation for the exact encoding.
Yes. hmac.compare_digest accepts both str and bytes, as long as both arguments are the same type. Comparing hex strings (str vs str) or raw bytes (bytes vs bytes) both work. Mixing types raises TypeError.
HMAC can accept short keys, but short secrets are easier to brute force. Use a high-entropy random secret from your secret manager, then encode it consistently before passing it to hmac.new().
HMAC-SHA256 in JavaScript (Web Crypto API)
In browser JavaScript, HMAC-SHA256 belongs in SubtleCrypto. The W3C Web Cryptography API describes a JavaScript API for cryptographic operations and key management, including signature generation and verification.1 First, crypto.subtle.importKey imports raw key bytes and returns a CryptoKey object.2 Then, crypto.subtle.sign computes the HMAC and returns a Promise that resolves to an ArrayBuffer signature.3 For HMAC, the digest algorithm is selected through HmacImportParams, so HMAC-SHA256 uses { name: 'HMAC', hash: 'SHA-256' }.4
Caching the CryptoKey avoids repeated importKey overhead
Caching a CryptoKey object at module level avoids calling crypto.subtle.importKey() on every HMAC computation. The import operation accepts raw key bytes and returns a Promise that fulfills with a CryptoKey, so keeping that object in module scope means later requests can skip repeated key parsing and allocation.2 The performance benefit depends on request volume and the browser implementation, so measure your own workload before adding a cache.
The correct pattern stores the CryptoKey lazily: let _key = null; async function getKey(secret) { if (!_key) { _key = await crypto.subtle.importKey('raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']); } return _key; }. Reset _key to null when the secret rotates, forcing the next call to re-import with the new bytes. Never share a single CryptoKey between different secrets; the key object is bound to the specific bytes it was created from.
Keeping HMAC signing client-side
A browser can compute an HMAC when it already holds the secret, but most public web apps should not expose long-lived signing keys to client-side JavaScript. HMAC signing in the browser is appropriate for extensions, service workers, local developer tools, or workflows where the key belongs to the user and is not shared with other visitors. CapyToolkit's Hash Generator can demonstrate the HMAC-SHA256 output locally, but production systems should keep service secrets on the server unless the client is the trusted key holder.
When verifying an incoming HMAC, use crypto.subtle.verify directly
When verifying an incoming HMAC signature, call crypto.subtle.verify() with a key whose keyUsages include 'verify', the received signature bytes, and the original signed message bytes.5 The method returns a Promise that resolves to a boolean, so it keeps the comparison inside the Web Crypto implementation instead of converting the signature to hex and comparing strings yourself.5 For HMAC, the algorithm parameter is 'HMAC' and the key must be the same secret key used for signing, which means the verification key must remain secret.3
Converting a hex signature string to Uint8Array
The crypto.subtle.verify() method expects the signature as a BufferSource, not a hex string. Convert an incoming hex signature with: const sigBytes = new Uint8Array(hexStr.match(/.{2}/g).map(b => parseInt(b, 16))). For base64-encoded signatures common in some webhook schemes, use Uint8Array.from(atob(b64Sig), c => c.charCodeAt(0)) instead. Always validate the format of the incoming signature string before conversion to avoid passing a malformed Uint8Array to verify(), which silently returns false rather than throwing.
Verifying the original message bytes
Keep the raw message bytes that the sender signed. If your code parses JSON first and then re-serializes it, whitespace and property order can change, so the HMAC no longer matches even though the visible data looks the same. Verification should compare the signature against the exact bytes received from the sender. Store the raw ArrayBuffer or Uint8Array from the network response and pass it directly to crypto.subtle.verify() without any intermediate string conversion, since even a UTF-8 encode-decode round trip can alter bytes that fall outside the ASCII range.
You keep verification correct by passing the raw ArrayBuffer or Uint8Array straight to crypto.subtle.verify without any intermediate string conversion. Even a UTF-8 encode and decode round trip can change bytes outside the ASCII range and break the match. CapyToolkit computes HMAC output locally so you can inspect the expected format before wiring the verifier into a request handler.
In browser extensions, keep Web Crypto operations in a secure extension context
In ordinary web pages and workers, Web Crypto is a secure-context API, so it is available only in HTTPS or another trustworthy origin. Extension content scripts run in the context of the web page, and MDN notes that secure-context-restricted Web APIs also apply to content scripts running in those contexts.6 If you need HMAC-SHA256 from an extension, move the crypto operation to an extension-owned context such as a background service worker and pass the message bytes through messaging rather than relying on an insecure page context.6
For extensions that sign requests to a server-side API, import the signing secret from chrome.storage.session (in-memory, cleared on session end) rather than chrome.storage.local (persisted on disk). Session storage limits the window of exposure if the extension or host machine is compromised between sessions. Rotate the signing secret through a server-side API call at extension startup and store only the current session's key in chrome.storage.session, so a stolen disk image does not contain a usable signing secret.
Notes
Import the key with algorithm { name: 'HMAC', hash: 'SHA-256' } and extractable: false. The keyUsages array must include 'sign' for HMAC computation and optionally 'verify' if you will also call crypto.subtle.verify. Passing extractable: false prevents the raw key bytes from being exported back out of the CryptoKey object, which is the correct setting for runtime keys.
For webhook verification in a browser extension or service worker, fetch the raw body as an ArrayBuffer before JSON parsing - JSON.parse re-encodes the body, which would change the byte sequence and invalidate the HMAC. Always sign and verify the raw bytes of the HTTP body, not a re-serialized JavaScript object.
Examples
Compute HMAC-SHA256
async function hmacSha256(secret, message) {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const sig = await crypto.subtle.sign('HMAC', key, enc.encode(message));
return Array.from(new Uint8Array(sig))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
const mac = await hmacSha256('secret_key', 'payload_data');
console.log(mac); // 64-char hex Verify HMAC (constant-time)
async function verifyHmac(secret, message, expectedHex) {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw', enc.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' }, false, ['verify']
);
const expectedBytes = new Uint8Array(
expectedHex.match(/.{2}/g).map(b => parseInt(b, 16))
);
return crypto.subtle.verify('HMAC', key, expectedBytes, enc.encode(message));
} crypto.subtle.verify performs a constant-time comparison internally.
Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Compute HMAC-SHA256
async function hmacSha256(secret, message) {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const sig = await crypto.subtle.sign('HMAC', key, enc.encode(message));
return Array.from(new Uint8Array(sig))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
const mac = await hmacSha256('secret_key', 'payload_data');
console.log(mac); // 64-char hex - 1.
World Wide Web Consortium, “Web Cryptography API,” W3C Proposed Recommendation, December 2016. https://www.w3.org/TR/2016/PR-WebCryptoAPI-20161215/
- 2.
MDN Web Docs, “SubtleCrypto: importKey() method,” accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
- 3.
World Wide Web Consortium, “Web Cryptography API,” latest published version, accessed June 2026. https://www.w3.org/TR/WebCryptoAPI/
- 4.
Node.js, “Web Crypto API,” accessed June 2026. https://nodejs.org/docs/latest/api/webcrypto.html
- 5.
NIST, “The Keyed-Hash Message Authentication Code (HMAC),” FIPS 198-1, July 2008. https://csrc.nist.gov/pubs/fips/198-1/final
- 6.
MDN Web Docs, “Content scripts,” accessed June 2026. https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts
In practice, you should cache the CryptoKey for reuse across multiple sign calls. Create the key once and store it in a module-level variable or closure. Calling importKey on every request is wasteful - the raw bytes parsing happens each time.
sign computes an HMAC from the key and message. verify checks whether a given MAC matches the key and message without exposing the key or computed MAC to the comparison, providing constant-time security. Use verify for checking incoming signatures rather than sign + manual comparison.
Yes. Service Workers have access to the full Web Crypto API. Computing HMAC-SHA256 for request signing or webhook verification in a Service Worker is a common pattern for edge authentication middleware.
Only when that browser is the trusted key holder. A public web page should not contain a service HMAC secret, because every visitor can inspect it. Keep service secrets on the server and use browser HMAC only for user-owned local tools.
No. CapyToolkit's Hash Generator runs in your browser. The secret, message, and HMAC result stay local while you test the output format.
HMAC-SHA256 in Node.js
Node.js has built-in HMAC support. The crypto.createHmac('sha256', key) call returns a Hmac object that works like the Hash object from createHash: update it with message data, then retrieve the hex digest.1 HMAC combines a hash function with a shared secret key for message authentication.2 Because Node.js wraps OpenSSL, it uses native cryptographic implementations rather than a JavaScript-only HMAC routine, which is a good fit for server-side webhook verification workloads.
Handling key encoding from environment variables in TypeScript
Environment variables store HMAC secrets as strings, but crypto.createHmac() works more reliably with a Buffer key that preserves the exact binary representation of the secret. When your webhook secret is base64-encoded in an environment variable, decode it before creating the HMAC: const key = Buffer.from(process.env.HMAC_SECRET!, 'base64'). Passing the raw string instead treats each base64 character as UTF-8 text, producing a different key than the original binary secret and causing verification failures that are hard to trace.3 This encoding mismatch is one of the most common bugs in webhook verification code, and it often goes undetected in development because test fixtures use simple ASCII secrets that happen to survive the string-to-buffer conversion unchanged.
In TypeScript, define a helper with explicit parameter types: function computeHmac(key: Buffer, message: string | Buffer): string. The Buffer parameter type makes key decoding an explicit step at every call site, preventing the silent string-key error from reaching code review undetected, and it also gives the TypeScript compiler a chance to flag callers that pass a raw string where a Buffer is expected.
Storing secrets outside the repository
Keep HMAC secrets in your deployment secret store, not in source files or example commits. Even a sample key can train a bad habit if reviewers copy it into a real environment. Rotate the value whenever a repository or CI log exposure could reveal it, then redeploy the updated secret before relying on new signatures. Use a dedicated secrets manager such as AWS Secrets Manager, HashiCorp Vault, or the encrypted secrets feature built into your CI platform, so that secret values never appear in plaintext in configuration files or deployment scripts.
You reduce risk by loading the secret from a manager that your deployment platform already protects rather than copying it into example code or commit messages. A sample key in a repository trains a habit that survives into production, where a leaked value can forge valid signatures. CapyToolkit keeps HMAC computation local so you can test output formats without pasting a real service secret into a shared machine.
In streaming contexts, the Hmac object accepts incremental updates
For large payloads received in chunks, call hmac.update(chunk) for each chunk before calling hmac.digest('hex') at the end to produce the final authentication tag. The Hmac object from crypto.createHmac() accumulates data across multiple update calls, so you can feed it partial data as it arrives from the network. Piping a readable stream directly also works because the Hmac object implements the Node.js Transform stream interface: readableStream.pipe(hmac).1
For Express webhook endpoints using express.raw(), the body arrives as a single Buffer because Express buffers the full request body before your route handler runs. Incremental updates apply to manual HTTP server implementations or streaming body parsers that process individual request data events as they arrive on the socket. The express.raw() approach is simpler for most webhook use cases and requires no streaming logic on your part.4
Keeping the raw bytes available to the verifier
The HMAC verifier must receive the same bytes the sender signed, because even a single byte difference produces a completely different authentication tag that causes verification to fail. If middleware parses the body before verification, it may normalize JSON or form data and invalidate the signature. Preserve the raw Buffer, compute the HMAC from it, then pass the parsed body to business logic only after the signature check succeeds. In Express, register express.raw() middleware on the webhook route before any express.json() middleware runs, and attach the raw Buffer to a custom property like req.rawBody so both the verifier and downstream handlers can access the bytes they need.
Testing HMAC-SHA256 against published RFC 4231 test vectors in Jest
Testing your HMAC implementation against published test vectors from RFC 4231 confirms that key encoding and the Node.js crypto API behave correctly in your environment. For HMAC-SHA256 with the key bytes 0x0b repeated 20 times and the message "Hi There" (ASCII), the correct digest is b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7.5 Define this as a named constant in your test file so future readers can cross-reference it against the RFC 4231 document.
Use crypto.timingSafeEqual for the final comparison because direct equality can short-circuit; OWASP recommends comparison functions that return in constant time to protect against timing attacks.6 Add separate tests for the edge cases most likely to fail: an empty message, a message longer than the SHA-256 block size (64 bytes), and a key provided as a raw string compared to the same key provided as a Buffer from identical bytes. Each test isolates one specific encoding path in the createHmac call and catches the most common implementation mistakes before they reach production.
Testing key rotation behavior
Add a test that accepts either the old or new secret during a rotation window and rejects unrelated keys. That test protects the deployment path, not just the math. It also documents when the old secret can be removed from the secret store. Parameterize the test with multiple rotation scenarios: old-only, new-only, both-active, and neither-matching, so the test suite covers every transition state the verifier can encounter during a real key rollover event.
Notes
Create the HMAC with crypto.createHmac('sha256', key) where key can be a string, Buffer, or TypedArray. Call .update(message) to feed in data, then .digest('hex') to get the 64-character hex output. The Hmac object supports chained calls: crypto.createHmac('sha256', key).update(msg).digest('hex').
When your webhook secret is base64-encoded in an environment variable, decode it with Buffer.from(process.env.HMAC_SECRET!, 'base64') before creating the HMAC. Passing the raw string treats each base64 character as UTF-8 text, producing a different key than the original binary secret and causing verification failures that are hard to trace.
For constant-time comparison, use crypto.timingSafeEqual(Buffer.from(a, 'hex'), Buffer.from(b, 'hex')). Both Buffers must be the same length - HMAC-SHA256 always produces 32 bytes, so this condition is always met when comparing two HMAC-SHA256 digests. Avoid === or string methods that can short-circuit on the first differing character.
Examples
Compute HMAC-SHA256
const crypto = require('crypto');
const mac = crypto.createHmac('sha256', 'secret_key')
.update('payload_data')
.digest('hex');
console.log(mac); // 64-char hex HMAC Verify GitHub webhook signature
const crypto = require('crypto');
function verifyGitHubSignature(body, secret, sigHeader) {
// sigHeader format: "sha256=<hex>"
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body) // body must be the raw Buffer, not parsed JSON
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(sigHeader);
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
} Express middleware for webhook verification
const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-hub-signature-256'];
const mac = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.body) // express.raw() keeps body as Buffer
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(mac))) {
return res.status(401).send('Invalid signature');
}
res.sendStatus(200);
}); Use express.raw() to receive the body as a Buffer. express.json() re-parses the body, altering the byte sequence and invalidating the HMAC.
Verify with the Hash Generator: MD5, SHA-1, SHA-256 & SHA-512 tool.
Compute HMAC-SHA256
const crypto = require('crypto');
const mac = crypto.createHmac('sha256', 'secret_key')
.update('payload_data')
.digest('hex');
console.log(mac); // 64-char hex HMAC - 1.
Node.js, “Crypto,” Node.js v26.3.1 Documentation, accessed June 2026. https://nodejs.org/api/crypto.html
- 2.
NIST, “The Keyed-Hash Message Authentication Code (HMAC),” FIPS 198-1, July 2008. https://csrc.nist.gov/pubs/fips/198-1/final
- 3.
Node.js, “Buffer,” Node.js v26.3.1 Documentation, accessed June 2026. https://nodejs.org/api/buffer.html
- 4.
expressjs, “body-parser,” GitHub repository, accessed June 2026. https://github.com/expressjs/body-parser
- 5.
IETF, “Identifiers and Test Vectors for HMAC-SHA-224, HMAC-SHA-256, HMAC-SHA-384, and HMAC-SHA-512,” RFC 4231, December 2005. https://www.rfc-editor.org/rfc/rfc4231.html
- 6.
OWASP, “Authentication Cheat Sheet,” accessed June 2026. https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
Both work. Strings are converted to UTF-8 bytes internally. If your key is base64-encoded (common for API secrets), decode it first: Buffer.from(encodedSecret, "base64"). Working with Buffers directly avoids any charset ambiguity.
HMAC is computed over the exact bytes of the HTTP body. express.json() parses the body as JSON and re-encodes it, which can change whitespace, key ordering, or floating-point representation. The re-encoded bytes differ from the original, causing the HMAC comparison to fail even for valid payloads.
No. After calling .digest(), the Hmac is finalised and cannot be updated. Create a new instance for each computation with crypto.createHmac("sha256", key).
No. CapyToolkit's Hash Generator computes HMAC-SHA256 locally in your browser, so the secret and message are not uploaded to generate the result. Treat any real production secret as sensitive and avoid entering shared service keys into public machines.
Accept signatures from both the old and new secrets for a short overlap window, then remove the old secret after delivery logs show the provider has switched. This avoids rejecting valid webhooks while the provider finishes propagation.