Verifying large files with node crypto

Node.js’s built-in crypto library lets you verify the signature of a file with very few lines of (Typescript) code:

verify(filePath: string, signatureBase64: string): boolean {
    const fileData = fs.readFileSync(filePath);
    const verifier = crypto.createVerify('sha256');
    verifier.update(fileData);
    return verifier.verify(this.publicKey, signatureBase64,'base64');
}

What if the file is large and/or you’re running on a low-memory device? This might be the case if you’re writing a firmware updater to run on an embedded device e.g. a Raspberry Pi Zero.

In this case, the above code could fail at runtime due to an out of memory error. This is because fs.readFileSync tries to read the entire contents of the file into memory, which might be more than you have available.

To avoid this, you can use streams. It turns out the Verify class extends <stream.Writable>, so you can pipe data from the input file to it like this:

verify(filePath: string, signatureBase64: string, callback: (err: any, result: boolean) => void) {
    const readStream = fs.createReadStream(filePath);
    const verifier = crypto.createVerify('sha256');
    const publicKey = this.publicKey;

    readStream.on('open', function () {
        readStream.pipe(verifier);

        readStream.on('end', () => {
            const result = verifier.verify(publicKey, signatureBase64,'base64');
            callback(undefined, result);
        });
    });

    readStream.on('error', function(err) {
        callback(err, false);
    });
}

Note that calling verify only after the readStream has ended will prevent you from getting verify.update Error: Not initialised.

Congrats, you can now verify multi-GB images on your 512MB RAM device! (Probably.)

If you have any corrections or improvements, drop me a comment below.