How to use crypto and Buffer with vite in browser? - browser

I have a project need to encrypt data to send to backend.
and the crypto is like this:
const NULL_IV = Buffer.from([]) // new Buffer([]);
const crypto = require('crypto'),
algorithm = 'aes-256-ecb'
const { bodyCrypt:{password} } = require('../config/index')
function aesEncrypt(string = '') {
const cipher = crypto.createCipheriv(algorithm, password, NULL_IV)
let encrypted = Buffer.concat([cipher.update(Buffer.from(string, 'utf8')), cipher.final()])
return encrypted.toString('hex')
}
function aesDecrypt(string = '') {
const decipher = crypto.createDecipheriv(algorithm, password, NULL_IV)
let decrypted = Buffer.concat([decipher.update(Buffer.from(string, 'hex')), decipher.final()])
return decrypted.toString()
}
module.exports = {
aesEncrypt,
aesDecrypt,
}
How can I use this in browser?!!
I try to use cryptoJs to encrypt, but the ciphertext is changing and couldnt be decrypt by the code above.
const CryptoJS = require('crypto-js')
const data = '1'
const key = '123456x3bxiinky1xzc95wcgc0p9p2p7'
const cipher = CryptoJS.AES.encrypt(data, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
iv: '',
keySize: 256
})
// 将加密后的数据转换成 Base64
const hexText= cipher.ciphertext.toString(CryptoJS.enc.Hex)

If you want to learn how to work with native node crypto and vite, you can read this answer :
TypeError: crypto.createCipheriv is not a function

Thanks #Topaco.
If just want to encrypt request to a server or decrypt payload that a server send to us using "aes-256-ecb" algorithm.
Just use CryptoJS to make it works:
import CryptoJS from 'crypto-js'
import { crypt } from '#/config'
const { password } = crypt
const cryptKey = CryptoJS.enc.Utf8.parse(password)
const cryptoOptions = {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
iv: '',
keySize: 256
}
/**
* encrypt
* #param {*} string
* #returns {string}
*/
function aesEncrypt(string = '') {
const cipher = CryptoJS.AES.encrypt(string, cryptKey, cryptoOptions)
return CryptoJS.enc.Hex.stringify(cipher.ciphertext)
}
/**
* decrypt
* #param {*} string
* #returns {string}
*/
function aesDecrypt(string = '') {
const decipher = CryptoJS.AES.decrypt(
// make it params but not string that works.
{ ciphertext: CryptoJS.enc.Hex.parse(string) },
cryptKey,
cryptoOptions
)
return CryptoJS.enc.Utf8.stringify(decipher)
}
export { aesEncrypt, aesDecrypt }
It doesnt need Buffer or crypto module that in nodejs environment.

Related

What is WebCrypto.subtle.decrypt analog for node.js crypto.createDecipheriv (AES-CTR algorithm)?

I am trying to rewrite old NodeJs encryption algorithm from
crypto.createDecipheriv(algorithm, key, iv[, options])
into webcrypto
subtle.decrypt(algorithm, key, data)
This code work good enough with AES-128-CTR algorithm
const algorithm = 'aes-128-ctr';
const iv = '0123456789ABCDEF0123456789ABCDEF';
const privateKey = '16Random_Letters';
const hexBufferFromIv = Buffer.from(iv, 'hex');
const utfBufferFromPrivateKey = Buffer.from(privateKey, 'utf8');
function oldEncryptData(data: string): string {
const cipher = createCipheriv(
algorithm,
utfBufferFromPrivateKey,
hexBufferFromIv,
);
let crypted = cipher.update(data, 'utf8', 'base64');
crypted += cipher.final('base64');
return crypted;
}
function oldDecryptData(data: string): string {
const decipher = createDecipheriv(
algorithm,
utfBufferFromPrivateKey,
hexBufferFromIv,
);
let dec = decipher.update(data, 'base64', 'utf8');
dec += decipher.final('utf8');
return dec;
}
async function testDecrypt() {
const sourceText = `any text to encrypt!`;
const encryptedText = oldEncryptData(sourceText);
const decryptedText = oldDecryptData(encryptedText);
return sourceText === decryptedText;
}
testDecrypt().then(console.log);
Right now I test this code and WebCrypto examples in nodejs, but as a final result I wont to move webCrypto.subtle.decrypt functionality into NGINX njs and as I know, njs doesn't support other options for decryption except for WebCrypto.
Interface for WebCrypto decrypt for AES-CTR in general looks like
const data = await crypto.subtle.decrypt(
{
name: "AES-CTR",
counter, // BufferSource
length: 128, // 1-128
},
key, // AES key
encData, // BufferSource
);
And I don't undersatnd.
counter is the same thing as the Initialization vector in createDecipheriv method?
How I should generate key for subtle.decrypt method from the same passphrase?
Do I need to do any additional transformation from or to base64 or utf8 encoding to reproduce input and output encoding in cipher.update(data, 'utf8', 'base64'); and in decipher.update(data, 'base64', 'utf8'); methods?
Thanks Topaco for hints. I'll write a more complete answer. Maybe it will be useful for someone.
Yes, Initialization vector and counter can be treated as the same thing.
For generating a key from the same passphrase you should use importKey method. And you should sent the same ArrayBuffer from the passphrase as in createCipheriv method.
Yes, if your old method used some specific encoding and decoding, you should repeat the same encoding/decoding logic after Webcrypto.SubtleCrypto.encrypt() and decrypt() methods.
Full workable example may looks something like
import { webcrypto } from 'crypto';
const iv = '0123456789ABCDEF0123456789ABCDEF';
const privateKey = '16Random_Letters';
const hexBufferFromIv = Buffer.from(iv, 'hex');
const utfBufferFromPrivateKey = Buffer.from(privateKey, 'utf8');
async function generateKeyFromPassPhrase(): Promise<CryptoKey> {
return webcrypto.subtle.importKey(
'raw',
utfBufferFromPrivateKey,
{
name: 'AES-CTR',
},
true,
['decrypt', 'encrypt'],
);
}
async function newEncryptData(data: string): Promise<string> {
const key = await generateKeyFromPassPhrase();
const encryptResult = await webcrypto.subtle.encrypt(
{
name: 'AES-CTR',
length: 128,
counter: hexBufferFromIv,
},
key,
Buffer.from(data),
);
return Buffer.from(encryptResult).toString('base64');
}
async function newDecryptData(data: string): Promise<string> {
const key = await generateKeyFromPassPhrase();
const decryptResult = await webcrypto.subtle.decrypt(
{
name: 'AES-CTR',
length: 128,
counter: hexBufferFromIv,
},
key,
Buffer.from(data, 'base64'),
);
return Buffer.from(decryptResult).toString();
}
async function testDecrypt() {
const sourceText = `any text to encrypt!`;
const encrypted2 = await newEncryptData(sourceText);
const decrypted2 = await newDecryptData(encrypted2);
return sourceText === decrypted2;
}
testDecrypt().then(console.log);

Nodejs AES-256-GCM encryption and decryption in client by browser webcrypto api

I generate a pair public/private key on Client and send the publicKey to the Server and the backend will generate a sharedKey on its side and respond me a publicKey which help me to generate a sharedKey on the client too for encryption/decryption. So I encrypt a message by AES-256-GCM on Nodejs and decrypted the message on the Client.
Backend-Side:
export function encrypt(sharedKey: string, message: string) {
const firstIv = getRandomIV();
const cipher = crypto.createCipheriv(
'aes-256-gcm',
Buffer.from(sharedKey, 'base64'),
firstIv
);
const encrypted = cipher.update(message, 'utf8');
return Buffer.from(encrypted + cipher.final()).toString('base64');
}
function getRandomIV() {
return crypto.randomBytes(12);
}
Client-Side:
async function decrypt(encryptedData: Uint8Array) {
const aesKey = await generateAesKey();
const nonce = encryptedData.subarray(0, SERVER_ENCRYPTION_IV_LENGTH);
const data = encryptedData.subarray(SERVER_ENCRYPTION_IV_LENGTH);
const decrypted = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: nonce,
},
aesKey,
data
);
return {
decrypted: new Uint8Array(decrypted),
decryptedString: new TextDecoder().decode(decrypted),
};
}
async function generateAesKey() {
const publicKey = await getServerPublicKey();
const privateKey = await getPrivateKey();
const sharedSecret = await crypto.subtle.deriveBits(
{
name: 'ECDH',
public: publicKey!,
},
privateKey,
256
);
const aesSecret = await crypto.subtle.digest('SHA-256', sharedSecret);
return crypto.subtle.importKey('raw', aesSecret, 'AES-GCM', true, [
'encrypt',
'decrypt',
]);
}
Now, I can't decrypt the server encrypted response in the client and I encounter to DOMException error and I don't know why?
GCM uses an authentication tag that is handled separately by NodeJS/Crypto, while WebCrypto automatically concatenates it with the ciphertext.
Therefore, in the NodeJS code, the tag must be explicitly determined and appended to the ciphertext. This is missing in the current NodeJS code and can be taken into account as follows. Note the determination of the tag with cipher.getAuthTag() and its concatenation:
var crypto = require('crypto');
function encrypt(key, plaintext) {
var nonce = getRandomIV();
var cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
var nonceCiphertextTag = Buffer.concat([
nonce,
cipher.update(plaintext),
cipher.final(),
cipher.getAuthTag() // Fix: Get tag with cipher.getAuthTag() and concatenate: nonce|ciphertext|tag
]);
return nonceCiphertextTag.toString('base64');
}
function getRandomIV() {
return crypto.randomBytes(12);
}
var message = Buffer.from('The quick brown fox jumps over the lazy dog', 'utf8');
var sharedKey = Buffer.from('MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE=', 'base64');
var ciphertext = encrypt(sharedKey, message);
console.log(ciphertext); // wRE5KM6FG81QSMNvG0xR+iaIeF77cyyeBceGS5NkcYaD17K9nL0/helnqRBOkD9pLVoWM/nRAcaKg/YdvfNJcO1Zn/7ZM0k=
A possible output is
wRE5KM6FG81QSMNvG0xR+iaIeF77cyyeBceGS5NkcYaD17K9nL0/helnqRBOkD9pLVoWM/nRAcaKg/YdvfNJcO1Zn/7ZM0k=
The following code for decryption on the WebCrypto side is essentially based on your code (without the derivation of the key from a shared secret, which is irrelevant to the current problem):
(async () => {
var nonceCiphertextTag = base64ToArrayBuffer('wRE5KM6FG81QSMNvG0xR+iaIeF77cyyeBceGS5NkcYaD17K9nL0/helnqRBOkD9pLVoWM/nRAcaKg/YdvfNJcO1Zn/7ZM0k=');
var nonceCiphertextTag = new Uint8Array(nonceCiphertextTag);
var decrypted = await decrypt(nonceCiphertextTag);
console.log(decrypted); // The quick brown fox jumps over the lazy dog
})();
async function decrypt(nonceCiphertextTag) {
const SERVER_ENCRYPTION_IV_LENGTH = 12; // For GCM a nonce length of 12 bytes is recommended!
var nonce = nonceCiphertextTag.subarray(0, SERVER_ENCRYPTION_IV_LENGTH);
var ciphertextTag = nonceCiphertextTag.subarray(SERVER_ENCRYPTION_IV_LENGTH);
var aesKey = base64ToArrayBuffer('MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE=');
aesKey = await window.crypto.subtle.importKey('raw', aesKey, 'AES-GCM', true, ['encrypt', 'decrypt']);
var decrypted = await crypto.subtle.decrypt({name: 'AES-GCM', iv: nonce}, aesKey, ciphertextTag);
return new TextDecoder().decode(decrypted);
}
// Helper
// https://stackoverflow.com/a/21797381/9014097
function base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
which successfully decrypts the ciphertext of the NodeJS side:
The quick brown fox jumps over the lazy dog

encrypt long string with node js module crypto

i am trying to encrypt long tokens but i get error
Error: Trying to add data in unsupported state
import * as crypto from 'crypto';
import { APP_CONFIG } from "#app-config";
const algorithm = 'aes-256-cbc';
const cipher = crypto.createCipheriv(algorithm, Buffer.from(APP_CONFIG.ENCRYPTION.ENCRYPTION_KEY), APP_CONFIG.ENCRYPTION.IV);
const decipher = crypto.createDecipheriv(algorithm, Buffer.from(APP_CONFIG.ENCRYPTION.ENCRYPTION_KEY), APP_CONFIG.ENCRYPTION.IV);
class cryptoHelper {
constructor() {
}
encrypt(text) {
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('hex');
}
decrypt(encryptedData) {
let encryptedText = Buffer.from(encryptedData, 'hex');
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
}
export const CryptoHelper = new cryptoHelper();
CryptoHelper.encrypt("long string"); // 1200 length for example
// encrypt will fail
i am trying to encrypt google auth tokens(access_token and refresh_token)
is there any way to encrypt long strings?

encrypt data using aes-128-ecb in nodejs

I have To encrypt data using aes-128-ecb in nodejs my code is
I am using Crypto to encrypt data
const crypto = require('crypto');
const secret = '1000060000000000';
const cipher = crypto.createCipher('aes-128-ecb', secret);
const ciphertext = cipher.update('9', 'utf8', 'base64')+cipher.final('base64');
console.log("Cipher text is: " + ciphertext);
the output should be EtgITaHs6lEvEHBipj08Kg==
but the output is coming as nNzqejauQBnfiDqznGhZ0Q==
The problem here is the use of crypto.createCipher, it doesn't use the key directly, but rather a digest.
To quote the documentation:
The implementation of crypto.createCipher() derives keys using the OpenSSL function EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt.
If we use cipher.createCipheriv on the other hand, we can specify the key directly and it will give us the expected output.
Here's an example:
const crypto = require("crypto");
function encrypt(plainText, key, outputEncoding = "base64") {
const cipher = crypto.createCipheriv("aes-128-ecb", key, null);
return Buffer.concat([cipher.update(plainText), cipher.final()]).toString(outputEncoding);
}
function decrypt(cipherText, key, outputEncoding = "utf8") {
const cipher = crypto.createDecipheriv("aes-128-ecb", key, null);
return Buffer.concat([cipher.update(cipherText), cipher.final()]).toString(outputEncoding);
}
const key = "1000060000000000";
const plainText = "9";
const encrypted = encrypt(plainText, key, "base64");
console.log("Encrypted string (base64):", encrypted);
const decrypted = decrypt(Buffer.from(encrypted, "base64"), key, "utf8")
console.log("Decrypted string:", decrypted);
The output will be
EtgITaHs6lEvEHBipj08Kg==
It is late but will help others
You can pass any Algorithm i.e. aes-128-cbc , aes-128-ecb
Create a new file and name it as aes-service.js in service folder or anywhere in Node.js application
aes-service.js
const crypto = require('crypto');
const cryptkey = 'C51GH00SE8499727';
const iv = 'BDA30EGDH1578F81';
async function encrypt(text){
try {
var cipher = crypto.createCipheriv('aes-128-cbc',cryptkey,iv);
var crypted = cipher.update(text,'utf8','base64'); //base64 , hex
crypted += cipher.final('base64');
return crypted;
} catch (err) {
console.error('encrypt error',err);
return null;
}
}
async function decrypt(encryptdata){
//Check all Algorithms
console.log(crypto.getCiphers()); // ['aes-128-cbc', 'aes-128-ccm', ...]
try {
let decipher = crypto.createDecipheriv('aes-128-cbc',cryptkey,iv)
decipher.setAutoPadding(false)
let decoded = decipher.update(encryptdata,'base64','utf8') //base64 , hex
decoded += decipher.final('utf8')
return decoded
} catch (err) {
console.error('decrypt error',err)
return null
}
}
const AesService = {
encrypt:encrypt,
decrypt:decrypt,
}
module.exports = AesService
Node.js contorller i.e abc.controller.js
//Get aes encrypted data from node.js request
const AesService = require("./services/aes-service")
exports.getAesEncryptedDatafromReq= async (req, res) => {
try{
let decryptData = ''
try{
const buffers = [];
for await (const chunk of req) {
buffers.push(chunk);
}
const dataBuffer = Buffer.concat(buffers).toString();
const jsonParsedData = JSON.parse(dataBuffer)
decryptData = jsonParsedData.data
}catch(err){}
let decryptedData = await AesService.decrypt(decryptData)
console.log('decrypted data',decryptedData)
let sendbackdata = {
"status": 0,
"anotehr_key":[
{ "dec":"0", "asc":"1"}
]
}
sendbackdata = JSON.stringify(sendbackdata)
let encryptedData = await AesService.encrypt(sendbackdata)
//Check if encrypted performed well
// let decryptedDataAgain = await AesService.decrypt(encryptedData)
//console.log('decryptedDataAgain ',decryptedDataAgain)
return res.status(201).send({"data":encryptedData})
}catch(err){
return res.status(500)
}
}
Add route
router.post("/get/aes/encrypted/data/from/req", controller.getAesEncryptedDatafromReq)

NodeJS Crypto Fails to Verify Signature Created by Web Crypto API

I'm having troubles verifying signatures created by the Web Crypto API.
Here is the code I'm using to generate RSA keys in the browser:
let keys;
const generateKeys = async () => {
const options = {
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: { name: 'SHA-256' },
};
keys = await window.crypto.subtle.generateKey(
options,
false, // non-exportable (public key still exportable)
['sign', 'verify'],
);
};
And to export the public key:
const exportPublicKey = async () => {
const publicKey = await window.crypto.subtle.exportKey('spki', keys.publicKey);
let body = window.btoa(String.fromCharCode(...new Uint8Array(publicKey)));
body = body.match(/.{1,64}/g).join('\n');
return `-----BEGIN PUBLIC KEY-----\n${body}\n-----END PUBLIC KEY-----`;
// Output:
//
// -----BEGIN PUBLIC KEY-----
// MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx7J3SUG4sq/HSGIaGZWY
// 8b26cfEpVFYHoDUDUORIJzA/fLE9aj+uOKpGUTSfW69rMm7DAOLDz05KaEJJSI5+
// YbDPr2S82A2ByHHQt+Vu168sGz4noXTTSX2HIdVutaR/IJ0a5pNOa1vRR4MUW/ZO
// YaRir3yC5YXgcFLwwQaifNZ3lZ7WndbYEjTGOcieQQ81IUP2221PZCJI52S95nYm
// VfslsLiPhOFH7XhGSqelGYDi0cKyl0p6dKvYxFswfKKLTuWnu2BEFLjVq4S5Y9Ob
// SGm0KL/8g7pAqjac2sMzzhHtxZ+7k8tynzAf4slJJhHMm5U4DcSelTe5zOkprCJg
// muyv0H1Acb3tfXsBwfURjiE0cvSMhfum5I5epF+f139tsr1zNF24F2WgvEZZbXcG
// g1LveGCJ/0BY0pzE71DU2SYiUhl+HGDv2u32vJO80jCDf2lu7izEt544a+XE+2X0
// zVpwjNQGa2Nd4ApGosa1fbcS5MsEdbyrjMf80SAmOeb9g3y5Zt2MY7M0Njxbvmmd
// mF20PkklpH0L01lhg2AGma4o4ojolYHzDoM5a531xTw1fZIdgbSTowz0SlAHAKD3
// c2KCCsKlBbFcqy4q7yNX63SqmI3sNA3kTH9CQJdBloRvV103Le9C0iY8CAWQmow5
// N/sDJUabgOMqe9yopSjb7LUCAwEAAQ==
// -----END PUBLIC KEY-----
};
To sign a message:
const generateHash = async (message) => {
const encoder = new TextEncoder();
const buffer = encoder.encode(message);
const digest = await window.crypto.subtle.digest('SHA-256', buffer);
return digest;
};
const signMessage = async (message) => {
const { privateKey } = keys;
const digest = await generateHash(message);
const signature = await window.crypto.subtle.sign('RSASSA-PKCS1-v1_5', privateKey, digest);
return signature;
};
To verify the message in browser:
const verifyMessage = async (signature, message) => {
const { publicKey } = keys;
const digest = await generateHash(message);
const result = await window.crypto.subtle.verify('RSASSA-PKCS1-v1_5', publicKey, signature, digest);
return result;
};
When the keys are created, the public key is exported and sent to the server. Later:
const message = 'test';
const signature = await signMessage(message);
await verifyMessage(signature, message); // true
sendToServer(message, bufferToHex(signature));
Since the signature is an ArrayBuffer, I convert it to hex with the following code:
const bufferToHex = input => [...new Uint8Array(input)]
.map(v => v.toString(16).padStart(2, '0')).join('');
On the server (NodeJS 8.11.0):
const publicKey = getPublicKey(userId);
const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(message, 'utf-8');
const sigBuf = Buffer.from(signature, 'hex');
verifier.verify(publicKey, sigBuf); // false
I've been chasing down this issue for days and just cannot seem to figure it out. I've tried both RSA-SHA256 and sha256WithRSAEncryption for verification to no avail. Furthermore, no errors are being thrown. Any help would be enormously appreciated!
So I don't fully understand why this is the case, but to solve the issue I needed to convert the SHA hash from an ArrayBuffer into a hex string, then read back into an array buffer using TextEncoder.
const generateHash = async (message) => {
const encoder = new TextEncoder();
const buffer = encoder.encode(message);
const digest = await window.crypto.subtle.digest('SHA-256', buffer);
// Convert to hex string
return [...new Uint8Array(digest)]
.map(v => v.toString(16).padStart(2, '0')).join('');;
};
Then when signing:
const signMessage = async (message) => {
const encoder = new TextEncoder();
const { privateKey } = keys;
const digest = await generateHash(message);
const signature = await window.crypto.subtle.sign('RSASSA-PKCS1-v1_5', privateKey, encoder.encode(digest));
return signature;
};
The signature no longer verifies on the client but it verifies in node. 🤷‍♂️
The issue is that you are signing the hash of hash of your input when you should actually be signing hash of your input. SubtleCrypto internally hashes the input. There is no need for you to provide hashed input. Since you provided hashed input as an argument, SubtleCrypto hashed it again and then signed it which led to mismatch of signatures.
It may be useful to note that both crypto and crypto.subtle hash the message before signing, so hashing separately is not necessary.
Assuming you were able to create and load your keys properly, your code should look like this.
Browser
const message = 'hello'
const encoder = new TextEncoder()
const algorithmParameters = { name: 'RSASSA-PKCS1-v1_5' }
const signatureBytes = await window.crypto.subtle.sign(
algorithmParameters,
privateKey,
encoder.encode(message)
)
const base64Signature = window.btoa(
String.fromCharCode.apply(null, new Uint8Array(signatureBytes))
)
console.log(base64Signature)
// TiJZTTihhUYAIlOm2PpnvJa/+15WOX2U0iKJ2LXsLecvohhRIWnwFfdHy4ci10mcv/UQgf2+bFf9lfFZUlPPdzckBNfXIqAjafM8XquJiw/t1v+pEGtJpaGASlzuWuL37gp3k8ux3l6zBKKbBVPPASkHVhz37uY1AXeMblfRbFE=
Node
This implementation is using crypto, but you could use the crypto.subtle to be more similar to the browser javascript syntax
const crypto = require('crypto')
const message = 'hello'
const base64Signature = 'TiJZTTihhUYAIlOm2PpnvJa/+15WOX2U0iKJ2LXsLecvohhRIWnwFfdHy4ci10mcv/UQgf2+bFf9lfFZUlPPdzckBNfXIqAjafM8XquJiw/t1v+pEGtJpaGASlzuWuL37gp3k8ux3l6zBKKbBVPPASkHVhz37uY1AXeMblfRbFE='
const hashingAlgorithm = 'rsa-sha256'
const doesVerify = crypto.verify(
hashingAlgorithm,
Buffer.from(message),
{ key: publicKey },
Buffer.from(base64Signature, 'base64')
);
console.log(doesVerify)
// true
Not a direct answer but it might just be easier to use this: https://www.npmjs.com/package/#peculiar/webcrypto so your code on client and server is consistent while addressing this problem at the same time.

Resources