Hash Generator: Code Examples

Generate SHA-256, SHA-512, SHA-1, MD5 and HMAC hashes instantly in your browser, with copy-ready code for every major language below.

ZERO UPLOAD · ALL LOCAL
  1. Type or paste any string into the input box — all four hashes update instantly as you type.
  2. Use the HEX / BASE64 toggle above the results to switch output format at any time.
  3. Click Copy next to any hash to copy it to the clipboard in the current format.
  4. Pick a language below for a copy-ready code example producing the same hash.
MD5 LEGACY
SHA-1 LEGACY
SHA-256 RECOMMENDED
SHA-512 SECURE
SHA-256

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
Sources
  1. 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. 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. 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. 4.

    NIST, "Secure Hash Standard (SHS) — Example Algorithms," csrc.nist.gov, August 2015. https://csrc.nist.gov/groups/ST/toolkit/examples

FAQ

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
Sources
  1. 1.

    MDN Contributors, "SubtleCrypto: digest() method," developer.mozilla.org, December 2025. https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest

  2. 2.

    WHATWG, "Encoding Standard," encoding.spec.whatwg.org, May 2026. https://encoding.spec.whatwg.org/#interface-textencoder

  3. 3.

    MDN Contributors, "Secure contexts," developer.mozilla.org, November 2025. https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts

  4. 4.

    Peter Marshall, "Reduce spread/apply call max arguments," chromium.googlesource.com, November 2018. https://chromium.googlesource.com/v8/v8/+/4e3a17d0408627517d4a81b3bf5daf85e416e9ac

FAQ

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
Sources
  1. 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. 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. 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. 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

FAQ

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
Sources
  1. 1.

    PHP, “Hash,” php.net, accessed June 2026 https://www.php.net/manual/en/book.hash.php

  2. 2.

    GitHub Docs, “Validating webhook deliveries,” docs.github.com, accessed June 2026 https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries

  3. 3.

    PHP, “php://input,” github.com, accessed June 2026 https://raw.githubusercontent.com/php/doc-en/master/language/wrappers/php.xml

  4. 4.

    PHP, “hash_equals,” php.net, accessed June 2026 https://www.php.net/manual/en/function.hash-equals.php

  5. 5.

    PHP, “hash_file,” github.com, accessed June 2026 https://raw.githubusercontent.com/php/doc-en/master/reference/hash/functions/hash-file.xml

FAQ

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
Sources
  1. 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. 2.

    OWASP Foundation, “Authentication Cheat Sheet,” cheatsheetseries.owasp.org, accessed June 2026 https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet

  3. 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. 4.

    The Go Authors, “hash.Hash interface,” github.com, accessed June 2026 https://raw.githubusercontent.com/golang/go/master/src/hash/hash.go

  5. 5.

    H. Krawczyk, M. Bellare, and R. Canetti, “RFC 2104: HMAC,” rfc-editor.org, February 1997 https://www.rfc-editor.org/info/rfc2104/

FAQ

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
Sources
  1. 1.

    Node.js Foundation, “Crypto,” nodejs.org, accessed June 2026 https://nodejs.org/api/crypto.html

  2. 2.

    expressjs, “body-parser,” github.com, accessed June 2026 https://github.com/expressjs/body-parser

  3. 3.

    Node.js Foundation, “Stream,” nodejs.org, accessed June 2026 https://nodejs.org/api/stream.html

  4. 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. 5.

    M. Nystrom, “RFC 4231: HMAC-SHA Test Vectors,” rfc-editor.org, December 2005 https://www.rfc-editor.org/rfc/rfc4231.txt

FAQ

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
Sources
  1. 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. 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. 3.

    Microsoft, ".NET cryptography model," learn.microsoft.com, February 2024. https://learn.microsoft.com/en-us/dotnet/standard/security/cryptography-model

  4. 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

FAQ

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
Sources
  1. 1.

    Rivest, "The MD5 Message-Digest Algorithm," RFC 1321, April 1992. https://www.rfc-editor.org/rfc/rfc1321

  2. 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. 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. 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. 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

FAQ

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
Sources
  1. 1.

    The PHP Project, "Hash Message Digest Framework," php.net, accessed June 2026. https://www.php.net/manual/en/book.hash.php

  2. 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. 3.

    OWASP Foundation, "Session Management Cheat Sheet," cheatsheetseries.owasp.org, accessed June 2026. https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html

  4. 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. 5.

    OWASP Foundation, "Password Storage Cheat Sheet," cheatsheetseries.owasp.org, accessed June 2026. https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html

  6. 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

FAQ

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
Sources
  1. 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. 2.

    Chen, "js-md5 README," github.com, accessed June 2026. https://github.com/emn178/js-md5/blob/master/README.md

  3. 3.

    Node.js Foundation, "Crypto," nodejs.org, accessed June 2026. https://nodejs.org/api/crypto.html

  4. 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. 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

FAQ

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
Sources
  1. 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. 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. 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. 4.

    Gueron, Johnson, and Walker, "SHA-512/256," IACR Cryptology ePrint Archive, Paper 2010/548. https://eprint.iacr.org/2010/548

  5. 5.

    Python Software Foundation, "hmac - Keyed-Hashing for Message Authentication," docs.python.org, accessed June 2026. https://docs.python.org/3/library/hmac.html

FAQ

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
Sources
  1. 1.

    Node.js Foundation, “Crypto,” nodejs.org, accessed June 2026. https://nodejs.org/api/crypto.html

  2. 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. 3.

    Gueron, Johnson, and Walker, “SHA-512/256,” IACR Cryptology ePrint Archive, Paper 2010/548. https://eprint.iacr.org/2010/548

  4. 4.

    NIST, “Secure Hash Standard,” FIPS 180-4, August 2015. https://www.nist.gov/publications/secure-hash-standard

  5. 5.

    Node.js Foundation, “Stream,” nodejs.org, accessed June 2026. https://nodejs.org/api/stream.html

  6. 6.

    Jones, “JSON Web Algorithms (JWA),” RFC 7518, May 2015. https://datatracker.ietf.org/doc/html/rfc7518.html

FAQ

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
Sources
  1. 1.

    Python Software Foundation, “hashlib,” Python 3 documentation. https://docs.python.org/3/library/hashlib.html

  2. 2.

    NIST, “Research Results on SHA-1 Collisions,” February 2017. https://csrc.nist.gov/News/2017/Research-Results-on-SHA-1-Collisions

  3. 3.

    Git Project, “Git hash function transition,” accessed June 2026. https://github.com/git/git/blob/master/Documentation/technical/hash-function-transition.adoc

  4. 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. 5.

    Eastlake and Jones, “US Secure Hash Algorithm 1 (SHA1),” RFC 3174, September 2001. https://www.rfc-editor.org/rfc/rfc3174.txt

FAQ

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
Sources
  1. 1.

    Python Software Foundation, “hmac,” Python 3 documentation. https://docs.python.org/3/library/hmac.html

  2. 2.

    Krawczyk et al., “HMAC: Keyed-Hashing for Message Authentication,” RFC 2104, February 1997. https://www.rfc-editor.org/rfc/rfc2104.txt

  3. 3.

    GitHub Docs, “Validating webhook deliveries,” accessed June 2026. https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries

  4. 4.

    Stripe Docs, “Receive Stripe events in your webhook endpoint,” accessed June 2026. https://docs.stripe.com/webhooks?verify=verify-manually

  5. 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

FAQ

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
Sources
  1. 1.

    World Wide Web Consortium, “Web Cryptography API,” W3C Proposed Recommendation, December 2016. https://www.w3.org/TR/2016/PR-WebCryptoAPI-20161215/

  2. 2.

    MDN Web Docs, “SubtleCrypto: importKey() method,” accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey

  3. 3.

    World Wide Web Consortium, “Web Cryptography API,” latest published version, accessed June 2026. https://www.w3.org/TR/WebCryptoAPI/

  4. 4.

    Node.js, “Web Crypto API,” accessed June 2026. https://nodejs.org/docs/latest/api/webcrypto.html

  5. 5.

    NIST, “The Keyed-Hash Message Authentication Code (HMAC),” FIPS 198-1, July 2008. https://csrc.nist.gov/pubs/fips/198-1/final

  6. 6.

    MDN Web Docs, “Content scripts,” accessed June 2026. https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts

FAQ

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
Sources
  1. 1.

    Node.js, “Crypto,” Node.js v26.3.1 Documentation, accessed June 2026. https://nodejs.org/api/crypto.html

  2. 2.

    NIST, “The Keyed-Hash Message Authentication Code (HMAC),” FIPS 198-1, July 2008. https://csrc.nist.gov/pubs/fips/198-1/final

  3. 3.

    Node.js, “Buffer,” Node.js v26.3.1 Documentation, accessed June 2026. https://nodejs.org/api/buffer.html

  4. 4.

    expressjs, “body-parser,” GitHub repository, accessed June 2026. https://github.com/expressjs/body-parser

  5. 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. 6.

    OWASP, “Authentication Cheat Sheet,” accessed June 2026. https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html

FAQ