crypto.createVerify()
Creates a Verify object that checks a signature against data using a public key, returning true or false.
crypto.createVerify(algorithm[, options])- Added in Node.js v0.1.94
Parameters
| Parameter | Type | Description |
|---|---|---|
$algorithm | string | The digest algorithm the signature was produced over, such as 'sha256'. Use crypto.getHashes() to list the available names. |
$optionsoptional | Object | stream.Writable options, controlling the stream behaviour of the returned Verify object. |
Return value
VerifyA `Verify` object: a writable stream that accumulates data and reports signature validity when `verify.verify()` is called.
Errors
verify.verify() throws ERR_CRYPTO_INVALID_STATE if called twice on the same object and ERR_CRYPTO_UNSUPPORTED_OPERATION for Ed25519 and Ed448 keys; a malformed or wrong-length signature returns false rather than throwing.
Changelog
- PHP v6.0.0The default inputEncoding for verify.update() changed from 'binary' to 'utf8'.
- PHP v11.6.0verify.verify() accepts a KeyObject, not only PEM strings and Buffers.
- PHP v11.7.0A private key may be passed where a public key is expected; the public half is derived from it.
createVerify() is the mirror of createSign(): same digest name, same data, but the public key and the signature go in and a boolean comes out. Everything about the signature scheme is inferred from the key — an RSA public key means RSASSA-PKCS1-v1_5, an EC key means ECDSA.
It returns false, it does not throw
This is the behaviour people misread. A tampered payload, a signature from the wrong key, and a signature that is complete garbage all produce false. Only a structurally invalid key, a reused Verify object, or an unsupported key type raises. Any code shaped like try { verify() } catch { reject() } without checking the return value accepts every forged request.
Matching the signer exactly
- The digest must match. A SHA-512 signature checked as
'sha256'is simply false. - Bytes must match. Verify the raw request body, not
JSON.stringify(JSON.parse(body))— key order and whitespace change the digest. - Padding must match. RSA-PSS signatures need the same
paddingandsaltLengthpassed here. - Pass
signatureEncodingwhen the signature arrives as a hex or base64 string; omit it for aBuffer.
Pin the expected public key in configuration. A signature that verifies against a key the attacker supplied proves nothing.
Example
When to use it
- Validate an inbound webhook against the sender's published public key before acting on it.
- Check a detached .sig file against a downloaded release artefact.
- Verify a licence blob offline in a desktop app using a key compiled into the binary.
- Confirm an ES256 token signature during authentication.
Frequently asked questions
Why does crypto.createVerify always return false?
Almost always because the verifier is not hashing the exact bytes that were signed — a re-serialised JSON body, a missing trailing newline, or a different digest name. Check that you verify the raw payload and that the algorithm string matches the signer's.
Does verify() throw when a signature is invalid?
No. An invalid, tampered or malformed signature returns false; only a bad key, a reused Verify object, or an unsupported key type throws. Code must branch on the returned boolean, not merely on the absence of an exception.
How do I verify a signature that arrives as a base64 string?
Pass 'base64' as the third argument: verify.verify(publicKey, signature, 'base64'). Use 'hex' for hex-encoded signatures, and omit the argument entirely when the signature is already a Buffer.
More examples
Verify a webhook against the raw request body
The important line is express.raw(): once a body parser has turned the payload into an object, re-serialising it changes key order, spacing and number formatting, and the digest no longer matches. Verifying the original Buffer is the only reliable approach, and the explicit if (!...) branch is what actually rejects a forgery, since verify() returns false rather than throwing.
const express = require('express');
const { createVerify } = require('node:crypto');
const PARTNER_PUBLIC_KEY = process.env.PARTNER_PUBLIC_KEY;
const app = express();
// Capture the exact bytes. express.json() alone would discard them.
app.use('/hooks', express.raw({ type: '*/*' }));
app.post('/hooks/partner', (req, res) => {
const signature = req.get('x-signature');
if (!signature) return res.status(400).send('missing signature');
const verifier = createVerify('sha256');
verifier.update(req.body); // the Buffer, not a re-serialised object
if (!verifier.verify(PARTNER_PUBLIC_KEY, signature, 'base64')) {
return res.status(401).send('bad signature');
}
const event = JSON.parse(req.body.toString('utf8'));
console.log('accepted event:', event.type);
res.sendStatus(204);
});Verify a detached signature over a file
Verify is a writable stream, so pipeline() streams a multi-gigabyte archive through it without buffering the file. The signature bytes are read separately because a detached signature is not part of the artefact. Note the explicit non-zero exit code: an installer that logs the failure but continues has verified nothing.
import { createVerify } from 'node:crypto';
import { pipeline } from 'node:stream/promises';
import fs from 'node:fs';
const publicKey = fs.readFileSync('release-signing.pub', 'utf8');
const verifier = createVerify('sha256');
await pipeline(fs.createReadStream('app-1.4.0.tar.gz'), verifier);
const signature = fs.readFileSync('app-1.4.0.tar.gz.sig');
const ok = verifier.verify(publicKey, signature);
console.log('artefact signature valid:', ok);
if (!ok) {
console.log('refusing to install');
process.exitCode = 1;
}Reject a signature made with the wrong digest or key
The output is true, false, false. The second line is the subtle one: the signature is perfectly valid, made by the right key over the right bytes, but the verifier hashed with SHA-256 while the signer used SHA-512, so the digests differ and the check fails. When a signature you believe is correct will not verify, compare digest names before suspecting the key.
const { generateKeyPairSync, createSign, createVerify } = require('node:crypto');
const alice = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
const mallory = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
const data = 'ship it';
const sig512 = createSign('sha512').update(data).sign(alice.privateKey);
const sigMallory = createSign('sha256').update(data).sign(mallory.privateKey);
console.log('right key, right digest:',
createVerify('sha512').update(data).verify(alice.publicKey, sig512));
console.log('right key, wrong digest:',
createVerify('sha256').update(data).verify(alice.publicKey, sig512));
console.log('wrong key, right digest:',
createVerify('sha256').update(data).verify(alice.publicKey, sigMallory));
Discussion