Verification of ruby-generated RSA SHA256 signature fails in C# - c#

I have the following code in ruby and C# to generate and verify RSA signature.
Ruby
Keys generation (in PEM format)
keys = OpenSSL::PKey::RSA.new 512
private_key = key.to_pem
public_key = key.public_key.to_pem
I store these keys in environment variables ENV["PRIVATE_KEY"] and ENV["PUBLIC_KEY"]
Generate signature
def generate_signature(token)
sign_hash = OpenSSL::Digest::SHA256.new
priv = OpenSSL::PKey::RSA.new(ENV["PRIVATE_KEY"])
signature = Base64.urlsafe_encode64(priv.sign(sign_hash, token))
signature.gsub!(/\n/, '')
end
Verify signature
def verify_signature(signature, token)
verify_hash = OpenSSL::Digest::SHA256.new
pub = OpenSSL::PKey::RSA.new(ENV["PUBLIC_KEY"])
pub.verify(verify_hash, Base64.urlsafe_decode64(signature), token)
end
given these methods, the following code returns true, so verification is succesful.
signature = generate_signature("string")
puts verify_signature(signature, "string")
C#
In C# I don't need to generate keys, I just need to verify signature, generated previously in ruby. Since System.Security.Cryptography doesn't work with PEM keys out of the box, I converted PEM key to XML format (using online converter)
Here is verification code, which returns false, so verification is unsuccesfull.
public const string publicKey = #"Public key in XML format";
public static bool Verify(string nonce, string signature)
{
RSACryptoServiceProvider RSAVerifier = new RSACryptoServiceProvider();
RSAVerifier.FromXmlString(publicKey);
signature = Base64Decode(signature);
byte[] SignatureBytes = Encoding.UTF8.GetBytes(signature);
byte[] VerifyFileData = Encoding.UTF8.GetBytes(nonce);
bool isValidSignature = RSAVerifier.VerifyData(VerifyFileData, CryptoConfig.MapNameToOID("SHA256"), SignatureBytes);
return isValidSignature;
}
Where Base64Decode is my custom method to replicate Base64.urlsafe_decode64 in ruby
public static string Base64Decode(string base64EncodedData)
{
byte[] base64EncodedBytes = Convert.FromBase64String(base64EncodedData.Replace('-', '+').Replace('_', '/'));
return Encoding.UTF8.GetString(base64EncodedBytes);
}

Related

How to encrypt plain text using RSA asymmetric encryption

I am working on an application where I need to encrypt plain text using the RSA algorithm. I encrypt the plain text but it is not working as it gives Error Decoding Text. Basically, I am calling third-party API which gives me the error. When I encrypt my text using this link reference link it works perfectly fine so I think I am doing something wrong. Here is my code
public static string Encryption(string strText)
{
var publicKey = #"<RSAKeyValue><Modulus>MIIDSjCCAjKgAwIBAgIEWrJUKTANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJE
RTEPMA0GA1UECAwGQmF5ZXJuMQ8wDQYDVQQHDAZNdW5pY2gxDzANBgNVBAoMBkxl
eGNvbTEkMCIGA1UEAwwbQWdyb3BhcnRzX0RNU19CYXNrZXRfVXBsb2FkMCAXDTE4
MDMyMTEyNDYzM1oY################################################
A1UECAwG########################################################
################################################################
WaOa0parvIrMk9/#################################################
NCIeGu+epwg8oUCr6Wd0BNATNjt8Tk64pgQvhdX9/KRDSC8V4QCJBiE3LQPHUVdN
nWRixrcOpucMo6m9PPegjnicn/rBKdFZLfJqLHHm+TrHrNCsEQIDAQABMA0GCSqG
SIb3DQEBCwUAA4IBAQBGwlNnDh2UaZphkEf70MPhySFVnTnLSxUFuwuWaDu8l7YP
zBMeJxcNk3HNiXPeba03GQBj+JqGAwDALJLityGeGEzlESfv/BsgQOONt+lAJUjs
b7+vr2e5REE/dpJZ1kQRQC##########################################
np+GstsdWjIWbL6L6VoqU18qLO5b0k8OoEMsP3akUTcj0w8JwD5V5iLqDhnv1aXK
kntkd/QmVCY6zlzH/dnTh8RNO2CfRtB1GEzNnkJB</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
var testData = Encoding.UTF8.GetBytes(strText);
using (var rsa = new RSACryptoServiceProvider(1024))
{
try
{
rsa.FromXmlString(publicKey);
byte[] data = Encoding.UTF8.GetBytes(strText);
byte[] cipherText = rsa.Encrypt(data,true);
var base64Encrypted = Convert.ToBase64String(cipherText);
return base64Encrypted;
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
}
}
}
Here is my public key. I am using an RSA certificate. I am passing the certificate key to the module tag here is my key. I think I might be using it wrong.
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIEWrJUKTANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJE
RTEPMA0GA1UECAwGQmF5ZXJuMQ8wDQYDVQQHDAZNdW5pY2gxDzANBgNVBAoMBkxl
eGNvbTEkMCIGA1UEAwwbQWdyb3BhcnRzX0RNU19CYXNrZXRfVXBsb2FkMCAXDTE4
MDMyMTEyNDYzM1oY################################################
A1UECAwG########################################################
################################################################
WaOa0parvIrMk9/#################################################
NCIeGu+epwg8oUCr6Wd0BNATNjt8Tk64pgQvhdX9/KRDSC8V4QCJBiE3LQPHUVdN
nWRixrcOpucMo6m9PPegjnicn/rBKdFZLfJqLHHm+TrHrNCsEQIDAQABMA0GCSqG
SIb3DQEBCwUAA4IBAQBGwlNnDh2UaZphkEf70MPhySFVnTnLSxUFuwuWaDu8l7YP
zBMeJxcNk3HNiXPeba03GQBj+JqGAwDALJLityGeGEzlESfv/BsgQOONt+lAJUjs
b7+vr2e5REE/dpJZ1kQRQC##########################################
np+GstsdWjIWbL6L6VoqU18qLO5b0k8OoEMsP3akUTcj0w8JwD5V5iLqDhnv1aXK
kntkd/QmVCY6zlzH/dnTh8RNO2CfRtB1GEzNnkJB
-----END CERTIFICATE-----
Any help would be highly appreciated. The encryption through this code is not working. But when I used the mentioned link above and pass this key it worked fine.
The answer to my question is here. I solved my problem and I am posting it because maybe someone in the future will have the same issue I am facing and what mistake I did to achieve my requirements.
Findings
I found during my research there is a difference between Public Key and Certificate. I miss understood the terminology I was passing a certificate instead of passing Public Key for encryption. So one of the community members #Topaco basically redirected me to the correct path which helps me to solve my problem. There are steps involved if you have a public key then you can achieve encryption but if you have a certificate then first you need to get the public key by using the method GetRSAPublicKey. When you got your public key in XML form then you pass it to encrypt method to get your result.
Here is the coding
Program.cs
var x509 = new X509Certificate2(File.ReadAllBytes(#"D:\xyz.cer"));
string xml = x509.GetRSAPublicKey().ToXmlString(false);
var result = EncryptUtil.Encryption("start01!", xml);
Utility Class
public static string Encryption(string strText, string publicKey)
{
using (var rsa = new RSACryptoServiceProvider(1024))
{
try
{
rsa.FromXmlString(publicKey);
byte[] data = Encoding.UTF8.GetBytes(strText);
byte[] cipherText = rsa.Encrypt(data, RSAEncryptionPadding.Pkcs1);
var base64Encrypted = Convert.ToBase64String(cipherText);
return base64Encrypted;
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
}
So you can achieve encryption using the above code you need to pass RSAEncryptionPadding.Pkcs1 for encryption.
#happycoding #keephelping

Walmart Affiliate Signature C# .Net

I am trying to create a sha256 signature using a RSA Private Key but I am getting a 401 "Could not authenticate in-request, auth signature : Signature verification failed: affil-product, version: 2.0.0, env: prod
I think the issue is to do whit how it get my .pem file. I have read the Microsoft documentation and the provided Walmart example. I am following this guide. I created a non password protected key pair and uploaded the public key to Walmart. I then added my consumer ID and key version to appsettings.json {"Settings": {"consumerID": "e2ca6a2f-56f2-4465-88b3-273573b1e0c9","keyVer": "4"}}.
I am then getting this data in program.cs via the following code.
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
// Get values from the config given their key and their target type.
Settings settings = config.GetRequiredSection("Settings").Get<Settings>();
I then instantiate Walmart affiliate object allowing us to use the methods needed to access and read Walmart api
WalMartAfilAPI wallMartAfilAPI = new WalMartAfilAPI();
From there I Create a RSACryptoServiceProvider object and import the .pem and export the parameter.
RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
var rsaPem = File.ReadAllText("D:\\Users\\Adam\\source\\repos\\DealsBot\\DealsBot\\DealsBot\\wallmartAfill\\WM_IO_private_key.pem");
//now we instantiate the RSA object
var rsa = RSA.Create();
//replace the private key with our .pem
rsa.ImportFromPem(rsaPem);
//Export the key information to an RSAParameters object.
// You must pass true to export the private key for signing.
// However, you do not need to export the private key
// for verification.
RSAParameters Key = rsa.ExportParameters(true);
From here I get the time stamp and call methods from the Walmart Affiliate object.
//Get current im in unix epoch milliseconds
TimeSpan t = DateTime.UtcNow - new DateTime(1970, 1, 1);
var time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
Console.WriteLine(time);
byte[] conicData = wallMartAfilAPI.Canonicalize(settings.KeyVer, settings.ConsumerID, time);
byte[] signedData = wallMartAfilAPI.HashAndSignBytes(conicData, Key);
if (wallMartAfilAPI.VerifySignedHash(conicData, signedData, Key))
{
Console.WriteLine("The data was verified");
;
Console.WriteLine(Convert.ToBase64String(signedData));
}
else
{
Here is the WalMartAfilAPI class
namespace DealsBot.wallmartAfill
{
public class WalMartAfilAPI
{
public byte[] Canonicalize(string version, string consumerId, string timestamp)
{
ASCIIEncoding ByteConverter = new ASCIIEncoding();
// Follow after the java code, which just orders the keys/values.
StringBuilder keyBuilder = new StringBuilder();
StringBuilder valueBuilder = new StringBuilder();
SortedDictionary<string, string> dictionary = new SortedDictionary<string, string>() { { "WM_CONSUMER.ID", consumerId }, { "WM_CONSUMER.INTIMESTAMP", timestamp }, { "WM_SEC.KEY_VERSION", version } };
foreach (string key in dictionary.Keys)
{
keyBuilder.Append($"{key.Trim()};");
valueBuilder.AppendLine($"{dictionary[key].Trim()}");
}
string[] conHeader = { keyBuilder.ToString(), valueBuilder.ToString() };
byte[] originalData = ByteConverter.GetBytes(conHeader[1]);
return originalData;
}
public byte[] HashAndSignBytes(byte[] DataToSign, RSAParameters Key)
{
try
{
// Create a new instance of RSACryptoServiceProvider using the
// key from RSAParameters.
RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
RSAalg.ImportParameters(Key);
// Hash and sign the data. Pass a new instance of SHA256
// to specify the hashing algorithm.
return RSAalg.SignData(DataToSign, SHA256.Create());
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}
public bool VerifySignedHash(byte[] DataToVerify, byte[] SignedData, RSAParameters Key)
{
try
{
// Create a new instance of RSACryptoServiceProvider using the
// key from RSAParameters.
RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
RSAalg.ImportParameters(Key);
// Verify the data using the signature. Pass a new instance of SHA256
// to specify the hashing algorithm.
return RSAalg.VerifyData(DataToVerify, SHA256.Create(), SignedData);
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return false;
}
}
}
}
As of today, auth signature code is available in Java (https://www.walmart.io/docs/affiliate/onboarding-guide)
The idea we provided sample code to help the customers to implement the logic at customer end by referring it
You can implement the logic in(C# .NET, Python, PHP or JS) in such a way that whenever your system invoking Walmart APIs, generate the signature on the fly and pass as input parameter
This is how all of customers implemented and consuming our APIs
Pls refer the below documentation for complete.
https://walmart.io/docs/affiliate/quick-start-guide
https://www.walmart.io/docs/affiliate/onboarding-guide
Regards,
Firdos
IO Support

Using an X509 private key to sign data in dotnet core v2 (SHA256)

I'm having trouble reproducing some cryptographic functionality in dotnet core v2.0. This is code ported from a .NET 4.5 project
.NET 4.5 code
public byte[] SignData(byte[] dataToSign, X509Certificate2 certificate)
{
var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
var xml = certificate.PrivateKey.ToXmlString(true);
rsaCryptoServiceProvider.FromXmlString(xml);
var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, CryptoConfig.MapNameToOID("SHA256"));
return signedBytes;
}
In dotnet core the ToXmlString() and FromXmlString() methods are not implemented, so I used a helper class workaround. Aside from that the dotnet core implementation works but, given the same input data and certificate it produces a different outcome.
dotnet core v2.0 code
public byte[] SignData(byte[] dataToSign, X509Certificate2 certificate)
{
var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
var rsa = (RSA)certificate.PrivateKey;
var xml = RSAHelper.ToXmlString(rsa);
var parameters = RSAHelper.GetParametersFromXmlString(rsa, xml);
rsaCryptoServiceProvider.ImportParameters(parameters);
SHA256 alg = SHA256.Create();
var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, alg);
return signedBytes;
}
EDIT
The dotnet core signed data fails a signature verification check in the .NET 4.5 codebase. Theoretically it should make no difference what the signing method was, so this should work but doesn't.
public void VerifySignature(byte[] signedData, byte[] unsignedData, X509Certificate2 certificate)
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PublicKey.Key)
{
if (rsa.VerifyData(unsignedData, CryptoConfig.MapNameToOID("SHA256"), signedData))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
}
Does anyone know if there are compatibility issues between the two methods of signing data?
EDIT 2
For clarification this is what both code snippets are attempting:
Taking a X509 certificate's private key which is RSA-FULL and cannot sign using SHA256 encoding
Creating a new private key which is RSA-AES which can sign using SHA256 encoding
Import your X509 private key into this new private key
Signing the required data using this private key.
The complication comes when attempting the same thing in .NEt4.5 and dotnet core v2.0.
Seems there are differences between frameworks, libraries and OS's. This answer states that the RSACryptoServiceProvider object relies on the CryptoAPI of the machine the software is on in .NET 4.5, and this informative post shows you the difference on how this is implemented in different environments/frameworks.
I'm still working on a solution based on this information but am left the central issue, namely that signed data using this dotnet core above cannot be verified by the .NET 4.5 implementation.
If you MUST stick with 4.5, your .NET Framework code is as good as it gets. (Well, you could eliminate the usage of the XML format and just use ExportParameters directly)
In .NET 4.6 the problem was solved with the soft-deprecation (which just means I tell everyone on StackOverflow to not use it) of the PrivateKey property:
using (RSA rsa = certificate.GetRSAPrivateKey())
{
return rsa.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
This is the same code you should write for .NET Core (all versions). Part of the reason for the refactoring was to get people off of the RSACryptoServiceProvider type, which doesn't work well on non-Windows systems.
The verification code would be
using (RSA rsa = certificate.GetRSAPublicKey())
{
return rsa.VerifyData(
dataToSign,
signature,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
Much less code, stronger type-safety, doesn't have the PROV_RSA_FULL problem, no key exporting/importing...
Solution
To be able to verify in .NET 4.5 the data signed using a X509 RSA private key in dotnet core v2.0
Verification code (.NET 4.5)
public void VerifySignedData(byte[] originalData, byte[] signedData, X509Certificate2 certificate)
{
using (var rsa = (RSACryptoServiceProvider)certificate.PublicKey.Key)
{
if (rsa.VerifyData(originalData, CryptoConfig.MapNameToOID("SHA256"), signedData))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
}
Signing Code (dotnet core v2.0)
private byte[] SignData(X509Certificate2 certificate, byte[] dataToSign)
{
// get xml params from current private key
var rsa = (RSA)certificate.PrivateKey;
var xml = RSAHelper.ToXmlString(rsa, true);
var parameters = RSAHelper.GetParametersFromXmlString(rsa, xml);
// generate new private key in correct format
var cspParams = new CspParameters()
{
ProviderType = 24,
ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
};
var rsaCryptoServiceProvider = new RSACryptoServiceProvider(cspParams);
rsaCryptoServiceProvider.ImportParameters(parameters);
// sign data
var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return signedBytes;
}
Helper Class
public static class RSAHelper
{
public static RSAParameters GetParametersFromXmlString(RSA rsa, string xmlString)
{
RSAParameters parameters = new RSAParameters();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlString);
if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
{
foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
{
switch (node.Name)
{
case "Modulus": parameters.Modulus = Convert.FromBase64String(node.InnerText); break;
case "Exponent": parameters.Exponent = Convert.FromBase64String(node.InnerText); break;
case "P": parameters.P = Convert.FromBase64String(node.InnerText); break;
case "Q": parameters.Q = Convert.FromBase64String(node.InnerText); break;
case "DP": parameters.DP = Convert.FromBase64String(node.InnerText); break;
case "DQ": parameters.DQ = Convert.FromBase64String(node.InnerText); break;
case "InverseQ": parameters.InverseQ = Convert.FromBase64String(node.InnerText); break;
case "D": parameters.D = Convert.FromBase64String(node.InnerText); break;
}
}
}
else
{
throw new Exception("Invalid XML RSA key.");
}
return parameters;
}
public static string ToXmlString(RSA rsa, bool includePrivateParameters)
{
RSAParameters parameters = rsa.ExportParameters(includePrivateParameters);
return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
Convert.ToBase64String(parameters.Modulus),
Convert.ToBase64String(parameters.Exponent),
Convert.ToBase64String(parameters.P),
Convert.ToBase64String(parameters.Q),
Convert.ToBase64String(parameters.DP),
Convert.ToBase64String(parameters.DQ),
Convert.ToBase64String(parameters.InverseQ),
Convert.ToBase64String(parameters.D));
}
}

How to properly verify data with rsa?

I want to sign a message with a private key and verify it with a public key, but I can't get it to work..
Here is how I sign the data (edited, but still not working):
public static string SignData(string message, string privateKey) {
byte[] plainText = ASCIIEncoding.Unicode.GetBytes(message);
var rsaWrite = new RSACryptoServiceProvider();
rsaWrite.FromXmlString(privateKey);
byte[] signature = rsaWrite.SignData(plainText, new SHA1CryptoServiceProvider());
return Convert.ToBase64String(signature);
}
Here is how I test the data (edited, still not working):
public static bool VerifyData(string sign, string publicKey, string orig) {
byte[] signature = Convert.FromBase64String(sign);
byte[] original = ASCIIEncoding.Unicode.GetBytes(orig);
var rsaRead = new RSACryptoServiceProvider();
rsaRead.FromXmlString(publicKey);
if (rsaRead.VerifyData(original, new SHA1CryptoServiceProvider(), signature)) {
return true;
} else {
return false;
}
}
I store the keypair as an xml string inside my account class. This function is executed in the constructor of account.cs:
public void addKeys() {
RSACryptoServiceProvider provider = new RSACryptoServiceProvider(1024);
privateKey = provider.ToXmlString(true);
publicKey = provider.ToXmlString(false);
}
I test the overall thing with this:
string signedHash = Utility.SignData("test" ,account.privateKey);
if (Utility.VerifyData(signedHash, account.publicKey, "test")) {
Console.WriteLine("WORKING!");
} else {
Console.WriteLine("SIGNING NOT WORKING");
}
Why isn't the overall thing working? My guess is that it doesn't work because of some encoding stuff.
return ASCIIEncoding.Unicode.GetString(signature);
The signature is arbitrary binary data, it isn't necessarily legal Unicode/UCS-2. You need to use an arbitrary encoding (https://en.wikipedia.org/wiki/Binary-to-text_encoding#Encoding_standards) to encode all of the arbitrary data. The most popular transport for signatures is Base64, so you'd want
return Convert.ToBase64String(signature);
And, of course, use Convert.FromBase64String in the verify method.
If you're compiling with a target of .NET 4.6 or higher you can also make use of the newer sign/verify API:
rsaRead.VerifyData(original, new SHA1CryptoServiceProvider(), signature)
would be
rsaRead.VerifyData(original, signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1)
While it might not look simpler, it prevents the allocation and finalization of the SHA1CryptoServiceProvider that the other method did, and it sets up for The Future when you may want to switch from Pkcs1 signature padding to PSS signature padding. (But the real advantage is that method is on the RSA base class instead of the RSACryptoServiceProvider specific type).

Convert SHA Hash Computation in Python to C#

Can someone please help me convert the following two lines of python to C#.
hash = hmac.new(secret, data, digestmod = hashlib.sha1)
key = hash.hexdigest()[:8]
The rest looks like this if you're intersted:
#!/usr/bin/env python
import hmac
import hashlib
secret = 'mySecret'
data = 'myData'
hash = hmac.new(secret, data, digestmod = hashlib.sha1)
key = hash.hexdigest()[:8]
print key
Thanks
You could use the HMACSHA1 class to compute the hash:
class Program
{
static void Main()
{
var secret = "secret";
var data = "data";
var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
Console.WriteLine(BitConverter.ToString(hash));
}
}

Categories

Resources