Signing and verifying signatures with RSA C#
Your problem is at the beginning of the VerifyData
method:
public static bool VerifyData(string originalMessage, string signedMessage, RSAParameters publicKey)
{
bool success = false;
using (var rsa = new RSACryptoServiceProvider())
{
//Don't do this, do the same as you did in SignData:
//byte[] bytesToVerify = Convert.FromBase64String(originalMessage);
var encoder = new UTF8Encoding();
byte[] bytesToVerify = encoder.GetBytes(originalMessage);
byte[] signedBytes = Convert.FromBase64String(signedMessage);
try
...
For some reason you switched to FromBase64String
instead of UTF8Encoding.GetBytes
.
C# Signing and verifying signatures with RSA. Encoding issue
You cannot use ASCIIEncoding
on the encoded message because it contains bytes which are invalid ASCII characters. The typical way you would store the encoded message is in a base64 string.
In SignData
, use the following to encode the byte array into a string:
return Convert.ToBase64String(signedBytes);
and in VerifyData
, use the following to decode the string back to the same byte array:
byte[] signedBytes = Convert.FromBase64String(signedMessage);
validating rsa signature from C# in java
In the XML representation on the C# side, the data are stored as unsigned big endian (and Base64 encoded).
However, the Java BigInteger(byte[] val)
-constructor expects the data as signed (two's complement) big endian.
Therefore the BigInteger(int signum, byte[] magnitude)
-constructor must be used, which expects the sign in the first parameter and the data in the second parameter as unsigned big endian, i.e. the following change is necessary:
RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(1, modulus), new BigInteger(1, exponent));
RSA signing with Python verifying with C#
RSAParameters#Modulus
and RSAParameters#Exponent
expect modulus and exponent unsigned, so Org.BouncyCastle.Math.BigInteger#ToByteArrayUnsigned()
must be used instead of Org.BouncyCastle.Math.BigInteger#ToByteArray()
. With this change, verification is successful.
Also, the big endian byte order is required, but this applies to both methods.
Note that System.Numerics.BigInteger#ToByteArray()
returns the data signed with little endian order.
Your current approach uses BC for key import and built-in .NET methods for verification. Alternatively, BC classes can also be used for verification, making for a slightly more efficient implementation overall.
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
...
ISigner signer = SignerUtilities.GetSigner("SHA256withRSA");
signer.Init(false, o);
signer.BlockUpdate(data, 0, data.Length);
bool verified = signer.VerifySignature(signature);
Verifying signed data from node in c# using rsa
PSS has a number of parameters, including the salt length. RFC8017, A.2.3. RSASSA-PSS defines a default salt length that corresponds to the output length of the digest, i.e. 32 bytes for SHA256.
Your recent C# code applies the C# built-in classes that use this default salt length. A different salt length cannot be specified!
The NodeJS code, on the other hand, defaults to the maximum possible salt length (crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN
), which is given by:<keysize> - <digest output length> - 2 = 256 - 32 - 2 = 222
.
Thus, the two codes are incompatible!
Unlike the C# built-in classes, BouncyCastle allows the salt length to be configured:
string x509Pem = @"-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----";
string validar = "...";
string hash64 = "...";
byte[] message = Encoding.UTF8.GetBytes(validar);
byte[] signature = Convert.FromBase64String(hash64);
PemReader pr = new PemReader(new StringReader(x509Pem));
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject();
PssSigner pssSigner = new PssSigner(new RsaEngine(), new Sha256Digest(), 256 - 32 - 2);
pssSigner.Init(false, publicKey);
pssSigner.BlockUpdate(message, 0, message.Length);
bool valid = pssSigner.VerifySignature(signature); // succeeds when the maximum possible salt length is used
With this, verification is successful.
Note that in the NodeJS code you can explicitly change the salt length to the output length of the digest (crypto.constants.RSA_PSS_SALTLEN_DIGEST
). Then verification will also work with the built-in C# classes.
Verifying a RSA signature made by Crypto Node.JS in C#
As pointed out in the comments, the problem was that data
in the C# client was converted to from Base64 when the data in the NodeJS application read from UTF-8.
The solution was to convert the string using Encoding.UTF8.GetBytes()
Thanks for the quick response!
Related Topics
What Are the Limitations of SQLdependency
Resizing an Image in ASP.NET Without Losing the Image Quality
How to Convert Datatable to JSON String Using JSON.Net
How to Render a Razor View to a String in ASP.NET MVC 3
Removing Nodes from an Xmldocument
Differencebetween Int, Int16, Int32 and Int64
Compare Equality Between Two Objects in Nunit
Does List<T> Guarantee Insertion Order
Using the Wpf Dispatcher in Unit Tests
How to Check If Tcpclient Connection Is Closed
"Could Not Load Type [Namespace].Global" Causing Me Grief
Icecast 2: Protocol Description, Streaming to It Using C#
Is There a Generic Constraint I Could Use for the + Operator
C# Naming Convention for Constants
Why Do Local Variables Require Initialization, But Fields Do Not