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).
Related
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
I'm trying to implement signed URLs for short lived access to static files.
The idea is:
generate an URL with an expiration timestamp (e.g. https://example.com/file.png?download=false&expires=1586852158)
sign it with HMACSHA256 and a shared secret and append the signature at the end of URL (e.g. https://example.com/file.png?download=false&expires=1586852158&signature=6635ea14baeeaaffe71333cf6c7fa1f0af9f6cd1a17abb4e75ca275dec5906d1
When i receive the request on the server, I take out the signature parameter and verify that the rest of the URL signed with HMACSHA256 and the same shared secret results in the same signature.
The implementation is as follows:
public static class URLSigner
{
private static string GetSignatureForUri(string uri, byte[] key)
{
var hmac = new HMACSHA256(key);
var signature = hmac.ComputeHash(Encoding.UTF8.GetBytes(uri));
var hexSignature = BitConverter.ToString(signature).Replace("-", string.Empty).ToLowerInvariant();
return hexSignature;
}
public static string SignUri(string uri, byte[] key)
{
var hexSignature = GetSignatureForUri(uri, key);
return QueryHelpers.AddQueryString(uri, new Dictionary<string, string> { { "signature", hexSignature }});
}
public static bool VerifyUri(string uri, byte[] key)
{
var signatureRegex = "[\\?&]signature=([a-z0-9]+)$";
var signatureMatch = Regex.Match(uri, signatureRegex);
if (!signatureMatch.Success || signatureMatch.Groups.Count != 2)
return false;
var parsedSignature = signatureMatch.Groups[1].Value;
var originalUri = Regex.Replace(uri, signatureRegex, "");
var hexSignature = GetSignatureForUri(originalUri, key);
return hexSignature == parsedSignature;
}
}
and it's used like so:
var signedUri = URLSigner.SignUri("https://example.com/file.png?download=false", secretKey);
var isVerified = URLSigner.VerifyUri(signedUri, secretKey);
Is this implementation of signed URLs reasonably secure?
Your implementation seems to be missing the verification of the expiration time, so any one key would currently work indefinitely.
Otherwise, I don't see anything wrong with this approach in general. You may want to add in a key beyond just the timestamp for identifying the user or request in some way though.
Here's a good article on how the general approach is used for one time passwords which is essentially what you are doing.
https://www.freecodecamp.org/news/how-time-based-one-time-passwords-work-and-why-you-should-use-them-in-your-app-fdd2b9ed43c3/
Yes, it is secure, as long as the key is treated properly. The hash should be able to ensure data integrity (data in URL are not modified by other people).
Perhaps, one little improvement is to dispose the HMACSHA256 object (maybe by using), but that may not be related to security.
I have one concern. You are saying you want to use HMACSHA256 and a private key, but in security terminology what you're passing to the HMAC is not a private key, it's a shared secret.
If you have to had a public, private key for your sign and verify authentication, I would suggest using the RSACryptoServiceProvider. With RSA you have two keys, public key and private key.
Your client creates a private key and keep it and give its public key to the server. So only client can sign and anyone with public key can verify it.
On another note, no matter what algorithm you ended up using, I would suggest to add the signature to a authorization header instead of query string. This is more common and you don't need to match a regex in each request.
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);
}
I want apply the RSA encryption to my project, but encountered some troubles:
First, I have download the JavaScripts library from
http://www.ohdave.com/rsa/ ,and add reference to my project;
Second, I have define the RSA object and code to initialize that:
internal RSACryptoServiceProvider Rsa
{
get
{
if (HttpContext.Cache["Rsa"] != null)
{
RSACryptoServiceProvider encryptKeys = (RSACryptoServiceProvider)HttpContext.Cache["Rsa"];
return encryptKeys;
}
else
{
return new RSACryptoServiceProvider(1024);
}
}
set
{
HttpContext.Cache.Remove("Rsa");
HttpContext.Cache.Insert("Rsa", value);
}
}
public ActionResult SignUp()
{
this.Rsa = Security.GetRsa();
RSAParameters param= this.Rsa.ExportParameters(true);
//this will bind to view
TempData["exponent"] = Util.BytesToHexString(param.Exponent);
TempData["key"] = Util.BytesToHexString(param.Modulus);
UserInfo user = new UserInfo();
user.Birthday = DateTime.Now.Date;
return View(user);
}
private RSACryptoServiceProvider GetRsa()
{
RSACryptoServiceProvider Rsa = new RSACryptoServiceProvider(1024);
return Rsa;
}
3.then, on JavaScript side , I have code, it encrypt the password user input and the bind it control:
var hash = document.getElementById("Pwd").value;
var exponent = document.getElementById("exponent").innerHTML;
var rsa_n = document.getElementById("key").innerHTML;
setMaxDigits(131);
var key = new RSAKeyPair(exponent, "", rsa_n);
hash = encryptedString(key, "111");
document.getElementById("Pwd").value = hash;
document.getElementById("Pwd2").value = hash;
document.getElementById("error").innerHTML = "";
document.getElementById("submit").click();
4.when user click submit, my C# code get the encrypted pwd string and try to decrypt it but failed with exception: bad data:
[HttpPost]
public ActionResult SignUp(UserInfo user)
{
user.UserId = user.UserId.ToLower(); //ignore case
user.UserGUID = Guid.NewGuid();
user.CreatedDate = DateTime.Now;
user.IsEnabled = false;
user.Pwd = Convert.ToBase64String(Rsa.Decrypt(Util.HexStringToBytes(user.Pwd), false));//Exception:Rsa.Decrypt throw bad data exception
who do you know how to fix it? thank you in advance.
I had a very similar problem in that most of the JavaScript based RSA encryption solutions wasn't "compatible" with .NET's implementation.
Almost all the implementations I found online had one or both of the following items causing the incompatibility with .NET's implementation.
The byte order encoding in JavaScript is different to the byte order that .NET used. This is a biggie as for example a string is represented with a different order of bytes in JS than it is in .NET so you'll need to convert before encrypting and after decrypting. I believe it's enough to just reverse the byte order to match .NET, but don't quote me on that.
Padding was different: .NET uses OAEP padding by default for RSA so the JS implementation of RSA should support the same padding too. I believe OAEP padding is also called PKCS#1 v2.0 padding, but don't quote me on that either.
Aside: I found an amazing JS library, called JavaScript.NET (from jocys.com) that mirrors tons of the .NET BCL functionality, including the RSA implementation, such that I could even use similar classes, properties and methods. Have a look at this. I can confirm it works with .NET RSA implementation. Give it a go - here are some links for it:
Jocys JS.NET Code Project demo
Jocys JS.NET Download
Hth
The class that produces "Bad Data" errors:
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
namespace MyNameSpace
{
public class RSAcrypt
{
private string _encryptedData;
private string _decryptedData;
public string EncryptedData
{
get { return _encryptedData; }
set { _encryptedData = value; }
}
public string DecryptedData
{
get { return _decryptedData; }
set { _decryptedData = value; }
}
public RSAcrypt()
{
}
/// <param name="CryptAction"> The action to perform on the string {Encrypt|Decrypt} </param >
/// <param name="StringToCrypt"> A string to perform the Action on </param>
public RSAcrypt(string CryptAction, string StringToCrypt)
{
UnicodeEncoding thisUnicodeEncoding = new UnicodeEncoding();
RSACryptoServiceProvider thisRSACryptoServiceProvider = new RSACryptoServiceProvider();
byte[] _stringToCrypt = thisUnicodeEncoding.GetBytes(StringToCrypt);
switch (CryptAction)
{
case "Encrypt":
byte[] encryptedData = Encrypt(_stringToCrypt, thisRSACryptoServiceProvider.ExportParameters(false));
_encryptedData = thisUnicodeEncoding.GetString(encryptedData);
break;
case "Decrypt":
byte[] decryptedData = Decrypt(_stringToCrypt, thisRSACryptoServiceProvider.ExportParameters(true));
_decryptedData = thisUnicodeEncoding.GetString(decryptedData);
break;
default:
break;
}
}
static private byte[] Encrypt(byte[] DataToEncrypt, RSAParameters keyInfo)
{
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSA.ImportParameters(keyInfo);
return RSA.Encrypt(DataToEncrypt, false);
}
static private byte[] Decrypt(byte[] DataToDecrypt, RSAParameters keyInfo)
{
#region Temporary Assignment - Remove before build
byte[] tmpVal = null;
#endregion
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
try
{
RSA.ImportParameters(keyInfo);
#region Temporary Assignment - Remove before build
tmpVal = RSA.Decrypt(DataToDecrypt, false);
#endregion
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message, "Exception Thrown");
}
#region Temporary Assignment - Remove before build
return tmpVal;
#endregion
}
}
}
Is there anything that I can change in this class that would allow me to check the encoding prior to passing the byte array to Encrypt / Decrypt?
It seems like I have a reference around here somewhere, but I am becoming frustrated, so I thought it would at least help if I stopped to do something other than reading and compiling...
BTW, I am calling this class to write to a password to an XML file using the Nini initialization framework.
http://nini.sourceforge.net/manual.php#ASimpleExample
Also, I used Notepad2 to change the file encoding (UTF-8) before I wrote to the XML file.
That was after the program halted after I compiled the first time. Using the debugger, I was able to see that the encoding was different between the XML data in memory (UTF-8) and the data on disk (ANSI).
That does not appear to be the case now, but the program still halts, referencing bad data returned from the Decrypt portion of RSAcrypt().
(also note that Encrypt and Decrypt were identical methods before my frustration set in, they do function the same, but I wanted to try to capture addition exception information related to the bad data claim. Of course, you will notice that I allowed my frustration to handicap my code ;-) )
Any suggestions, ideas or references would be great.
TIA,
E
Inside your constructor you generate a new RSA keypair each time when you do:
RSACryptoServiceProvider thisRSACryptoServiceProvider = new RSACryptoServiceProvider();
Since your constructor is where you encrypt and decrypt, you are encrypting with an RSA Key, and decrypting with a completely different one.
To make this work, you have several options based on how you plan to use your code.
One option is to export the RSA key, and use that for all encryption/decryption operations. This is the only option if you plan on decrypting/encrypting data between different runs of your executable.
Of course this completely glosses over how you will store your public/private key (I recommend DPAPI on windows), for use by your application.