Verifying PHP OpenSSL signature in C# with RSACryptoProvider - c#

I am attempting to verify an OpenSSL signature (created using openssl_sign with SHA1 in PHP) using C# RSACryptoProvider.VerifyData. It is returning false using the correct public key certificate.
Any idea about how to do this successfully?
EDIT:
I attempted to verify the OpenSSL SHA1 signature using BouncyCastle with the following code but verification is failing. Are the signatures calculated differently? How can I create a signature with OpenSSL that is verifiable by .NET?
byte[] signatureBytes = UTF8Encoding.Default.GetBytes(signature);
byte[] dataBytes = UTF8Encoding.Default.GetBytes(data);
StreamReader sr = new StreamReader(Path.Combine(#"C:\test", #"test\test.crt"));
PemReader pr = new PemReader(sr);
Org.BouncyCastle.X509.X509Certificate cert = (Org.BouncyCastle.X509.X509Certificate)pr.ReadObject();
ISigner sig = SignerUtilities.GetSigner("SHA1WithRSAEncryption");
sig.Init(false, cert.GetPublicKey());
sig.BlockUpdate(dataBytes, 0, dataBytes.Length);
if (sig.VerifySignature(signatureBytes)) {
Console.WriteLine("all good!");
}
PHP Code:
function signTokenWithPrivateKey($message, $keyLocation) {
try {
if (file_exists($keyLocation)) {
$privateKey= openssl_get_privatekey(file_get_contents($keyLocation));
$signature = '';
if (!openssl_sign($message, $signature, $privateKey)) {
die('Failed to encrypt');
}
openssl_free_key($privateKey);
}
}
catch (Exception $ex) {
}
return $signature;
}

The following code should do the trick for you.
It loads the certificate from the file path given and then uses the public key to verify the data against the given signature. Returns true if valid.
byte[] signature = Convert.FromBase64String(Signature);
byte[] data = Encoding.UTF8.GetBytes(Data);
var x509 = new X509Certificate2(Path.Combine(#"C:\test", #"test\test.crt"));
var rsa = x509.PublicKey.Key as RSACryptoServiceProvider;
if (rsa == null)
{
LogMessage("Authorize", "Invalid", Level.Alert);
return false;
}
string sha1Oid = CryptoConfig.MapNameToOID("SHA1");
//use the certificate to verify data against the signature
bool sha1Valid = rsa.VerifyData(data, sha1Oid, signature);
return sha1Valid;

Related

Sign XML using RSA from C# to php

I'm trying to digitally sign XML using RSA using PHP and I already have the code in C# but when trying to sign the XML in PHP and verify it in c# it fails.
Sign Code in C#:
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider();
rsaCryptoServiceProvider.FromXmlString(GetCertificates(certificateSerialNumber).PrivateKey.ToXmlString(true));
RSACryptoServiceProvider.UseMachineKeyStore = true;
rsaCryptoServiceProvider.ExportParameters(false);
rsaCryptoServiceProvider.KeySize = 2048;
return Convert.ToBase64String(rsaCryptoServiceProvider.SignData(Encoding.Unicode.GetBytes(elementsValue), CryptoConfig.MapNameToOID("SHA256")));
Verify Code in C#:
RSACryptoServiceProvider rsaCryptoServiceProvider = (RSACryptoServiceProvider)GetCertificates(certificateSerialNumber).PublicKey.Key;
RSACryptoServiceProvider.UseMachineKeyStore = true;
rsaCryptoServiceProvider.ExportParameters(false);
rsaCryptoServiceProvider.KeySize = 2048;
byte[] Signature;
try
{
Signature = Convert.FromBase64String(signature);
}
catch
{
return false;
}
return rsaCryptoServiceProvider.VerifyData(Encoding.Unicode.GetBytes(elementsValue), CryptoConfig.MapNameToOID("SHA256"), Signature);
Sign Code in PHP:
$rsa = new Crypt_RSA();
$rsa->loadKey($privateKey);
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$rsa->setHash('sha256');
$hashed = $rsa->hash->hash($encryptedData);
base64_encode($rsa->sign($hashed));
When I use the above PHP code to sign the XML string and try to verify the value in C# is return false, can anyone help with this.
Thank you.

How to verify digital signature in C#

I am new here.
I am learning the digital signature in C#. The certificates are generated followed by this document. Other documents I read: RSACng,
X509Certificate2.
I am working on Windows 10 Pro 1809, .Net Core 2.1, VSCode.
class Program
{
static void Main(string[] args)
{
var passwd = "password";
// Get client certificate.
var clientCertPath = #"./Certificates/test.pfx";
var clientCert = new X509Certificate2(clientCertPath, passwd);
// Get server certificate.
var serverCertPath = #"./Certificates/test.cer";
var serverCert = new X509Certificate2(serverCertPath);
// Generate data.
var translateResultData = BuildData();
var content = String.Join('&', translateResultData.Select(p => String.Join('=', p.Key, p.Value)));
// Sign
var sign = SignatureUtil.Sign(data: content, clientCert: clientCert);
// translateResultData.TryAdd(key: "sign", value : sign);
// Copy content ONLY for test.
var checkSign = sign;
var checkContent = content;
// Verify
var valid = SignatureUtil.Verify(data: checkContent, signature: checkSign, serverCert: serverCert);
System.Console.WriteLine(valid);
}
}
public class SignatureUtil
{
public static string Sign(string data, X509Certificate2 clientCert)
{
using(var privateKey = clientCert.GetRSAPrivateKey())
{
var dataByteArray = Encoding.UTF8.GetBytes(data);
var signatureByteArray = privateKey.SignData(
data: dataByteArray,
hashAlgorithm: HashAlgorithmName.SHA256,
padding: RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signatureByteArray);
}
}
public static bool Verify(string data, string signature, X509Certificate2 serverCert)
{
try
{
using(var publicKey = serverCert.GetRSAPublicKey())
{
var dataByteArray = Encoding.UTF8.GetBytes(data);
var signatureByteArray = Convert.FromBase64String(signature);
return publicKey.VerifyData(
data: dataByteArray,
signature: signatureByteArray,
hashAlgorithm: HashAlgorithmName.SHA256,
padding: RSASignaturePadding.Pkcs1);
}
}
catch (System.Exception)
{
return false;
}
}
}
Expected result: valid should be true because I am checking the original data.
Fact: The Verify method always returns false even the original data are passed.
Can you tell me what I did wrong?
I cannot tell you what is going wrong in your code since I cannot reproduce it. Here is a very detailed answer how you can sign using RSA and SHA256. Your approach and the one described in this answer are conceptually the same, but maybe there is a difference in your code compared to this answer.
And here is a example how in my company certificates associated with smart cards are used for signing and verifying signatures. One big difference is that we do not store the signature as a string but rather keep it as an array of bytes.
public byte[] SignData(byte[] data)
{
using (var sha256 = SHA256.Create())
{
using (var rsa = Certificate.GetRSAPrivateKey())
{
return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
}
}
}
public bool VerifySignature(byte[] data, byte[] signature)
{
using (var sha256 = SHA256.Create())
{
using (var rsa = Certificate.GetRSAPublicKey())
{
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
}
}
}

Signing Data Using C#

I have file which I am signing using RSA algorithm using two methods:
bouncycastle
openssl
both results in two different output and openssl results in valid file. I don't know why my c# code is giving wrong output.
Here is my code for bouncy castle in c#
private void GenerateSignatureFile(string sourceFile)
{
try
{
var stringToSign = ReadText(sourceFile).ToString();
var sig = Sign(stringToSign);
var fileContent = Encoding.UTF8.GetString(sig);
using (var sw = File.CreateText(Path.Combine(_projectLocation, _sigFileName)))
{
sw.WriteLine(fileContent);
}
}
catch (Exception ex)
{
LoggingService.Log(ex.Message);
}
}
public byte[] Sign(String data)
{
var key = readPrivateKey();
/* Make the key */
var keyParameter = new RsaKeyParameters(key.IsPrivate, ((RsaPrivateCrtKeyParameters)key).Modulus, ((RsaPrivateCrtKeyParameters)key).Exponent);
/* Init alg */
ISigner sig = SignerUtilities.GetSigner("SHA256withRSA");
/* Populate key */
sig.Init(true, key);
/* Get the bytes to be signed from the string */
var bytes = Encoding.UTF8.GetBytes(data);
/* Calc the signature */
sig.BlockUpdate(bytes, 0, bytes.Length);
return sig.GenerateSignature();
}
public static IEnumerable<string> ReadText(string scriptPath)
{
var buffer = new StringBuilder();
foreach (var line in File.ReadLines(scriptPath))
{
if (line == "GO")
{
yield return buffer.ToString();
buffer.Clear();
}
else
{
buffer.AppendLine(line);
}
}
}
private AsymmetricKeyParameter readPrivateKey()
{
AsymmetricCipherKeyPair keyPair;
using (var reader = new StringReader(_privateKey))
keyPair = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
return keyPair.Private;
}
and in openssl I use following command to sign data
openssl dgst -sha256 -sign content_private_key.pem -out content.zip.sig content.zip
I don't know why my c# code is resulting in different output.
I don't think that ReadText will always return the text identical to the binary text that openssl processes. To be sure, just write the bytes variable to file after the following line was executed:
var bytes = Encoding.UTF8.GetBytes(data);
For instance you could compare the two files using sha256sum.
If this is indeed the culprit then simply read in the file as binary and sign that.

What is the difference between signing by OpenSSL and Microsoft Cryptography libraries?

I wrote two methods for signing using RSA and SHA256, the first one with OpenSSL library and the second one with Microsoft Cryptography library.
OpenSSL implementation:
private string PasswordHandler(bool verify, object userdata)
{
return userdata.ToString();
}
private string Sign(string signParams)
{
var privateCertPath = HttpContext.Current.Server.MapPath(#"~\certificate.pem");
string privateKey;
using (StreamReader sr = new StreamReader(privateCertPath))
{
privateKey = sr.ReadToEnd();
}
OpenSSL.Crypto.RSA rsa = OpenSSL.Crypto.RSA.FromPrivateKey(new BIO(privateKey), PasswordHandler, _password);
//hash method
MessageDigest md = MessageDigest.SHA1;
BIO b = new BIO(signParams);
CryptoKey ck = new CryptoKey(rsa);
byte[] res1 = MessageDigestContext.Sign(md, b, ck);
return Uri.EscapeDataString(System.Convert.ToBase64String(res1));
}
Cryptography implementation:
private string Sign(string data)
{
var privateCertPath = HttpContext.Current.Server.MapPath(#"~\certificate.pfx");
X509Certificate2 privateCert = new X509Certificate2(privateCertPath, _password, X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey;
RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
privateKey1.ImportParameters(privateKey.ExportParameters(true));
// Get the bytes to be signed from the string
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
//const string sha256Oid = "2.16.840.1.101.3.4.2.1";
//HashAlgorithm algorithm = new SHA256CryptoServiceProvider();
//byte[] hashBytes = algorithm.ComputeHash(bytes);
//byte[] signature = privateKey1.SignHash(hashBytes, sha256Oid);
byte[] signature = privateKey1.SignData(bytes, "SHA256");
// Base 64 encode the sig so its 8-bit clean
return Convert.ToBase64String(signature);
}
Signing with OpenSSL works, generates valid digital signature but signing with Cryptography lib generates invalid signature so my question is what I implemented wrong?
I tried to use different encoding but it did not help. Certificates are generated correctly.
It might by also useful to tell basic info about the .pem certificate:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC

Java Digital Signature different to C#

I have the following c# code to generate a digital signature from a private key:
static string Sign(string text, string certificate)
{
X509Certificate2 cert = new X509Certificate2(certificate, "TestPassword", X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
// Hash the data
SHA1Managed sha1 = new SHA1Managed();
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1.ComputeHash(data);
// Sign the hash
return System.Convert.ToBase64String(rsa.SignHash(hash, CryptoConfig.MapNameToOID("SHA1")));
}
I then created what I thought was the equivalent java code:
public static String signData(String dataToSign, String keyFile) {
FileInputStream keyfis = null;
try {
keyfis = new FileInputStream(fileName);
KeyStore store = KeyStore.getInstance("PKCS12");
store.load(keyfis, "TestPassword".toCharArray());
KeyStore.PrivateKeyEntry pvk = (KeyStore.PrivateKeyEntry)store.
getEntry("testkey",
new KeyStore.PasswordProtection("TestPassword".toCharArray()));
PrivateKey privateKey = (PrivateKey)pvk.getPrivateKey();
byte[] data = dataToSign.getBytes("US-ASCII");
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] hashed = md.digest(data);
Signature rsa = Signature.getInstance("SHA1withRSA");
rsa.initSign(privateKey);
rsa.update(data);
return Base64.encode(rsa.sign());
} catch (Exception ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} finally {
if ( keyfis != null ) {
try { keyfis.close() } catch (Exception ex) { keyfis = null; }
}
}
return null;
}
Unfortunately the digital signatures do not match.
Any assistance will be greatly appreciated.
Thanks in advance.
EDIT: If I remove the MessageDigest from the java code then the output is the same. Why? I thought hashing is needed.
Regards,
Eugene
The Java sign method does hashing and signing based on the algorithms provided in getInstance method of the Signature class, so basically you were hashing twice in Java.
Ok so I have confirmed it. If I remove the MessageDigest/Hashing code from the java sample code then the two digital signatures are the same. Not sure why, but I'll try and find out.
If anyone would like to educate me further feel free.

Categories