Skip to content
Advertisement

RSA-SHA1 signature differs in JavaScript and PHP

I need to create a RSA-SHA1 signature in nodeJS, I am using the following code

const crypto = require("crypto");
const sign = crypto.createSign('RSA-SHA1');
sign.update(data);
const result = sign.sign(privateKey, 'base64')
console.log(result);

I’m double checked that my code result is true, I have same result with online tools if I select sha1WithRSA algorithm and I also get same result in PHP when I use openssl_sign function

I need to sign data for a bank API, Unfortunately they don’t accept my signature and they have no NodeJS implementation

Also their documentation is a total mess, Here is the C# implementation of their document

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(“<RSAKeyValue><Modulus>oQRshGhLf2Fh...”);
string data = "DATA";
byte[] signMain = rsa.SignData(Encoding.UTF8.GetBytes(data), new
SHA1CryptoServiceProvider());
sign = Convert.ToBase64String(signMain); 

Can someone help me to understand why my signature is not equal to the C# implementation (I don’t check result of C# but their server written in C# and they don’t verify my signature)

The following links to other official code fragments in PHP that together work with their server, but the result differs from standard openssl_sign function in PHP: index L211-L216, RSAProcessor L45, rsa.class L17-L22, rsa.class L63-L82.

  1. Is their implementation a non-standard RSA-SHA1?
  2. What’s wrong with my NodeJS code that their don’t accept it?

Advertisement

Answer

The NodeJS code and the posted C# code provide the same signature for the same data to be signed and the same private key.

From the posted link it can be seen that the SHA1 hashed data is passed to RSA::rsa_sign, but nowhere is the SHA1 digest ID prepended. Consistent with this is that no digest is specified in the function parameters either.

The padding itself takes place in RSA::add_PKCS1_padding. Here, for signing, RSASSA-PKCS1-v1_5 is applied. The passed (already hashed) data is processed, as said, without considering the digest ID.

Thus, the signature created by the PHP code is not compliant with RSASSA-PKCS1-v1_5, while both the NodeJS code and the C# code follow the standard.

To my knowledge, when signing with the sign methods of the crypto module of NodeJS, a digest must always be specified and the digest ID is always set implicitly, so the signature of the PHP code cannot be reproduced with this.

An alternative that allows not to set the digest ID is crypto.privateEncrypt. The following code generates the signature of the PHP code or the signing process described in the posted link if the same data and private key are used:

var crypto = require('crypto')

var sha1 = crypto.createHash('sha1')
sha1.update(data)
var hash = sha1.digest()

var signature = crypto.privateEncrypt({key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING}, hash) 
console.log(signature.toString('base64'))

Another question is whether this signature can be verified by the bank API. After all, the documentation of the API apparently contains two inconsistent implementations, so the documentation does not seem to be very reliable. Moreover, other flaws are of course possible, see the comment by fgrieu.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement