I'm trying to convert this Node JS code to ASP.NET C#:
const crypto = require('crypto');
/**
* Validates a billing service webhook
*
* #param {string} req Node request object, where 'req.body' is a Node
* Buffer object containing the request body
* #param {string} secret the secret string saved in the service App
*/
const isValidSignature = (req, secret) => {
const fsSignature = req.headers['X-FS-Signature'];
const computedSignature = crypto.createHmac('sha256', secret)
.update(req.body)
.digest()
.toString('base64');
return fsSignature === computedSignature;
}
Here is my attempt in C#
private bool CheckNotificationValidContextual(string varRequestHashValue, string varMessageBody) // #2047
{
// This involves a test of the webhook functionality using ngroks and Postman
// to send values that were previously generated from a test webhook from billing service
// to a local development copy of the seller application (running in Visual Studio)
// where the inputs are:
// varRequestHashValue = Request.Headers["X-Fs-Signature"];
// varMessageBody = new System.IO.StreamReader(Request.InputStream).ReadToEnd();
// get the local copy of the webhook secret key from the local web config file
var AMPTK_FSP_HMAC_SHA256_Key = ConfigVal.AMPTK_FSP_HMAC_SHA256();
// convert the local copy of the secret key to a byte array
byte[] AMPTK_keyBytes = Encoding.UTF8.GetBytes(AMPTK_FSP_HMAC_SHA256_Key);
// create a hash object with the local copy of the secret key
var _hashObjectOfLocalKey = new HMACSHA256(AMPTK_keyBytes);
// convert the input webhook message body to a byte array
byte[] _messageBodyByteArray = Encoding.UTF8.GetBytes(varMessageBody);
// create a hash byte array of the message body byte array
// using the hash object based on the local copy of the webhook secret
byte[] _computedMessageHashBytes = _hashObjectOfLocalKey.ComputeHash(_messageBodyByteArray);
// convert the hash byte array of the message body to a string
string _stringOfComputedMessageHashBytes = Encoding.UTF8.GetString(_computedMessageHashBytes, 0, _computedMessageHashBytes.Length);
// remove dashes and convert to lowercase
_stringOfComputedMessageHashBytes= BitConverter.ToString(_computedMessageHashBytes).Replace("-", "").ToLower();
// compare the string of the computed message body hash
// to the received webhook secret hash value from Request.Headers["X-Fs-Signature"]
if (_stringOfComputedMessageHashBytes == varRequestHashValue)
{
return true;
}
else
{
return false;
}
}
The C# code compiles and runs ok.
The result I'm looking for is:
prNdADI26M0ov5x6ZlMr2J2zzB8z2TJRBDy+8gjPttk=
What I'm getting from the C# code "_stringOfComputedMessageHashBytes"
is this:
f37cdae653e167e36c8ed17e44ffa456832dbb7dcec1d00dc1b44a1234965e73
I've checked the inputs carefully (could still be wrong).
Question: have I translated the Node code properly to C#, and if not, how can I improve it? Or what else might be off?
Thanks!
I checked your C# code for computing hash, in general looks ok, you used correct library and correct class HMACSHA256 to calculate HMAC_SHA256 hash. The problem seems on the part that you were trying to convert the computed hash value to a base 64 string. You can use Convert.ToBase64String method to convert your byte array to base 64 string.
Here below you can find your updated C# code(I did minor improvements and updated the code to find _stringOfComputedMessageHashBytes).
I tested the hash results generated from node.js and C#, now they are both generating the same hash result:
private bool CheckNotificationValidContextual(string varRequestHashValue, string varMessageBody) // #2047
{
// This involves a test of the webhook functionality using ngroks and Postman
// to send values that were previously generated from a test webhook from billing service
// to a local development copy of the seller application (running in Visual Studio)
// where the inputs are:
// varRequestHashValue = Request.Headers["X-Fs-Signature"];
// varMessageBody = new System.IO.StreamReader(Request.InputStream).ReadToEnd();
// get the local copy of the webhook secret key from the local web config file
var AMPTK_FSP_HMAC_SHA256_Key = ConfigVal.AMPTK_FSP_HMAC_SHA256();
// convert the local copy of the secret key to a byte array
var AMPTK_keyBytes = Encoding.UTF8.GetBytes(AMPTK_FSP_HMAC_SHA256_Key);
// create a hash object with the local copy of the secret key
using (var _hashObjectOfLocalKey = new HMACSHA256(AMPTK_keyBytes))
{
// convert the input webhook message body to a byte array
var _messageBodyByteArray = Encoding.UTF8.GetBytes(varMessageBody);
// create a hash byte array of the message body byte array
// using the hash object based on the local copy of the webhook secret
var _computedMessageHashBytes = _hashObjectOfLocalKey.ComputeHash(_messageBodyByteArray);
var _stringOfComputedMessageHashBytes = Convert.ToBase64String(_computedMessageHashBytes);
// compare the string of the computed message body hash
// to the received webhook secret hash value from Request.Headers["X-Fs-Signature"]
return _stringOfComputedMessageHashBytes == varRequestHashValue;
}
}
Related
I have an application with a "secret" (eg password)
I don't want to store this secret locally in a user-accessible context file, so I'm planning to retrieve it on-demand from the server over HTTPS
I also don't want it visible in memory (eg from a crash log), for obvious reasons, so I'm storing the secret in a SecureString
However, when I serialise the SecureString, the result just shows the length of the plaintext string, eg {"Length":4}
If I transmit the password in plaintext, though, then it will be visible in the retrieved JSON in memory, even if subsequently store it in a SecureString
Is there any way to serialize a SecureString, or to receive the JSON and convert a plaintext string to a SecureString without needing an intermediate regular String that would be stored in memory?
In this scenario, I have to store/send the actual password, rather than, for example, a one-time-use key as I'd prefer: that's beyond my control here. I need the actual plaintext password to access another service, so the usual "Hash it then compare the hash" doesn't apply either
Here is a worked example of using RSA encryption to send a string to someone whose public key you have. In your question you want the server to send a message (password) to the client and for the client to securely use that password without having to worry about logging etc in the middle. For this to work you need to have the client create the private key file and send the public key file to the server which can then communicate back securely.
There are probably libraries that make this way easier.
[Test]
public void TestEncryption()
{
/////////////// Create Key Files ////////////////
RSACryptoServiceProvider provider = new RSACryptoServiceProvider(4096);
//Create the key files on disk and distribute them to sender / reciever
var publicKey = provider.ToXmlString(false);
var privateKey = provider.ToXmlString(true);
/////////////// Actual Test ////////////////
//send with the public key
byte[] sent = Send("hey",publicKey);
//cannot receive with public key
var ex = Assert.Throws<CryptographicException>(()=>Receive(sent, publicKey));
StringAssert.Contains("Key does not exist",ex.Message);
//but can with private key
Assert.AreEqual("hey", Receive(sent,privateKey));
}
private Byte[] Send(string send, string publicKey)
{
using (RSACryptoServiceProvider rsaSender = new RSACryptoServiceProvider())
{
rsaSender.FromXmlString(publicKey);
return rsaSender.Encrypt(Encoding.ASCII.GetBytes(send), false);
}
}
private object Receive(byte[] sent, string privateKey)
{
using (RSACryptoServiceProvider rsaReceiver = new RSACryptoServiceProvider())
{
rsaReceiver.FromXmlString(privateKey);
return Encoding.ASCII.GetString(rsaReceiver.Decrypt(sent, false));
}
}
One of our internal/external services (B) is generating a URL with encrypted data in the query string; this is generated for another service (A). Service B handles the request of this url.
I'm finding that I cannot generate the same MD5 and do not understand why.
Here is the relevant code and where it lays in the entire flow.
Service A
Stores binary data in SQL
Generates an MD5 and passes this, along with other information, to service B in order to receive a link
Link is then emailed out
Service B (link generation)
Receives a request to generate a link
stores the supplied MD5 and other information
Generates an encrypted url and returns it to Service A
Service B (respond to link request)
URL is decrypted
Fetches stored information, including the MD5 passed in earlier
Downloads a file from service A
Uses the same MD5 algorithm as earlier to generate an MD5 for the download
Compares this MD5 to the one passed in earlier
they do not match
MD5 Code
Code to generate the MD5 looks like this (Service B):
public static string Generate(this MemoryStream stream)
{
if (!stream.CanSeek || !stream.CanRead)
throw new NotSupportedException("Stream must support seek and read to use MD5.Generate()");
using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
{
stream.Position = 0;
return BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", string.Empty).ToLower();
}
}
Code downloading the file and comparing MD5
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(dcBaseUrl);
HttpResponseMessage result = client.GetAsync("USDC/Package/" + request.PackageID).Result;
if (result.StatusCode != HttpStatusCode.OK && result.StatusCode != HttpStatusCode.NotFound)
throw new InvalidOperationException("Distribution Center threw an exception when trying to download package");
downloadResponse.Stream = (MemoryStream)result.Content.ReadAsStreamAsync().Result;
}
string fileMD5 = downloadResponse.Stream.Generate();
// This is ALWAYS false, getting different MD5 and do not know wht
if (!md5.Equals(fileMD5))
throw new InvalidOperationException("md5 mismatch");
I suspect it is how I am downloading the file, though I have tried numerous ways to get the contents, including:
byte[] package = result.Content.ReadAsByteArrayAsync().Result;
downloadResponse.Stream.Write(package, 0, package.Length);
string fileMD5 = downloadResponse.Stream.Generate();
Update
Saves the file to disk from both places. File is the same:
I finished my web api with data encriptions, but now, i have to encripty the Json result. When i put the querystring on the browser, now i have the answer (example) :
[{"SUSPID":"111","IVNOME":"teste","IVMAE":"teste","IVPAI":"teste","IVDATANASC":"02/07/1970","IVRG":"0000 (IFP)","ICPF":"Não Cadastrado"}]
I cannot show this...i have to show like (ENCRYPTED): [{"SUSPID":"AUAUAUA","IVNOME":"UAUAU","IVMAE":"UAUAU", ......]
I am seeing some examples, but i am not finding one that is what i need
Part of the code on my service (Cliente-side):
var response = await client.GetAsync(urllink);
var JsonResult = response.Content.ReadAsStringAsync().Result;
if (typeof(T) == typeof(string))
return null;
var rootobject = JsonConvert.DeserializeObject<T>(JsonResult);
return rootobject;
And at my controller (web api BackEnd), i return this dataset:
return lretorno.Tables[0].AsEnumerable().Select(row => new Envolvido
{
SUSPID = Convert.ToString(row["SUSPID"]),
IVNOME = Convert.ToString(row["SUSPNOME"]),
IVMAE = Convert.ToString(row["SUSPMAE"]),
IVPAI = Convert.ToString(row["SUSPPAI"]),
IVDATANASC = Convert.ToString(row["SUSPDATANASC"]).Replace(" 00:00:00", ""),
IVRG = Convert.ToString(row["RG"]),
ICPF = Convert.ToString(row["CPF"]),
MANDADO = Convert.ToInt16(row["TEMMANDADO"]),
OCORRENCIA = Convert.ToInt16(row["TEMOCORRENCIA"]),
});
I cannot understand where i have to encripty and where i have to decrypt on the code.
If you really must do some additional encryption on top of https, for instance if you want to help stop automated man-in-the-middle you can do something like the following... this would require the people in between (Government, ISP, Telco, Endpoint network admin's) to do a second man in the middle attack by figuring out specifically how you are passing your extra public key. In addition to this you could also include the "pk" parameter inside your JSON before its encrypted... and then when you decrypt the json you can compare it against the public-key that you sent, if they don't match then for sure there was a man-in-the-middle. I used the built in RSACryptoServiceProvider.
CLIENT-SIDE
// Generate private and public keys (use any asymmetric crypto/key size you want)
RSACryptoServiceProvider rsaKeys = new RSACryptoServiceProvider();
var privateXmlKeys = rsaKeys.ToXmlString(true);
var publicXmlKeys = rsaKeys.ToXmlString(false);
// Make the request for the json data from the server, and also pass along the public xml keys encoded as base64
var response = await http.GetAsync(new Uri(String.Format("https://example.com/data?id=777&pk=\"{0}\"", Convert.ToBase64String(Encoding.ASCII.GetBytes(publicXmlKeys)))));
var encryptedJsonBytes = await response.Content.ReadAsByteArrayAsync();
// Decrypt the bytes using the private key generated earlier
RSACryptoServiceProvider rsaDecrypt = new RSACryptoServiceProvider();
rsaDecrypt.FromXmlString(privateXmlKeys);
byte[] decryptedBytes = rsaDecrypt.Decrypt(encryptedJsonBytes, false);
// Now change from bytes to string
string jsonString = Encoding.ASCII.GetString(decryptedBytes);
// TODO: For extra validation, parse json, get the public key out that the server
// had used to encrypt, and compare with the "pk" you sent "publicXmlKeys",
// if these values do not match there was an attack.
SERVER-SIDE
// Assuming you have your JSON string already
string json = "{\"key\":\"secret_value\"}";
// Get the "pk" request parameter from the http request however you need to
string base64PublicKey = request.getParameter("pk");
string publicXmlKey = Encoding.ASCII.GetString(Convert.FromBase64String(base64PublicKey));
// TODO: If you want the extra validation, insert "publicXmlKey" into the json value before
// converting it to bytes
// var jo = parse(json); jo.pk = publicXmlKey; json = jo.ToString();
// Convert the string to bytes
byte[] jsonBytes = Encoding.ASCII.GetBytes(json);
// Encrypt the json using the public key provided by the client
RSACryptoServiceProvider rsaEncrypt = new RSACryptoServiceProvider();
rsaEncrypt.FromXmlString(publicXmlKey);
byte[] encryptedJsonBytes = rsaEncrypt.Encrypt(jsonBytes, false);
// Send the encrypted json back to the client
return encryptedJsonBytes;
If you need to protect against man in the middle attacks then I suggest you turn off the computer or pre-share the key via a different method not over the internet, phone, or mail and then do not embed it into your application :P Take a look into Off-the-record, End-to-end-encryption and Diffie–Hellman key exchange
Basically I have some generic ideas about this sort of cases for you. If you knows about the Man in the middle attack, only in E2E connection you can rely on your encryption algorithm, That's because only these End-points have the private and public key and the attacker can not spoof, But in these cases(like your case) the attacker can simply have your public key and even your encrypted block which you're trying to send to your webservice, That's because all you have in client-side is in the javascript resources that everyones can read.
So the only solution I can give you is that take your webservices on the HTTPS protocols which normally handle these kind of issues and you don't need to any encryption.
Regards.
I'm using Mandrill Inbound Webhooks to call a method in my WCF API. The request is coming through, I can successfully parse it, etc.
My problem lies in getting the value of the X-Mandrill-Signature header to match the signature that I'm generating (based on the process detailed here: https://mandrill.zendesk.com/hc/en-us/articles/205583257-Authenticating-webhook-requests).
This is what I'm currently doing:
List<string> keys = HttpContext.Current.Request.Params.AllKeys.ToList();
keys.Sort();
string url = "MyMandrillWebhookURL";
string MandrillKey = "MyMandrillWebhookKey"
foreach (var key in keys)
{
url += key;
url += HttpContext.Current.Request.Params[key];
}
byte[] byteKey = System.Text.Encoding.ASCII.GetBytes(MandrillKey);
byte[] byteValue = System.Text.Encoding.ASCII.GetBytes(url);
HMACSHA1 myhmacsha1 = new HMACSHA1(byteKey);
byte[] hashValue = myhmacsha1.ComputeHash(byteValue);
string generatedSignature = Convert.ToBase64String(hashValue);
And generatedSignature does not match the value for X-Mandrill-Signature
I know that the Mandrill docs indicate that the encoding needs to be done in binary and not hexadecimal (and I think my code does that, but correct me if I'm wrong), but, beyond that I can't make heads or tails of what my issue is. Any help is greatly appreciated.
The problem is with how you're retrieving the keys in your validation. You only need to use the request's POST variables alphabetically by key, not all the Request parameters. There is only one POST variable, mandrill_events that needs to be used in the signature generation.
string url = "MyMandrillWebhookURL";
string MandrillKey = "MyMandrillWebhookKey"
url += "mandrill_events";
url += mandrillEvents;
byte[] byteKey = System.Text.Encoding.ASCII.GetBytes(MandrillKey);
byte[] byteValue = System.Text.Encoding.ASCII.GetBytes(url);
...
I have a problem recreating a password that's hashed in C#. In an online project when a user do the registration process his password is save after passing for this function:
private static string ToMD5Hash(string inputString)
{
using (MD5 md5 = MD5.Create())
{
byte[] data = Encoding.Unicode.GetBytes(inputString);
byte[] hash = md5.ComputeHash(data);
return Convert.ToBase64String(hash);
}
}
I'm working on an offline version that at some point will do a sync with the online version and I can't reproduce the same results on AS3 (Adobe Air). For example the password "1234" after passing to the C# code will be "DwN1hMmef9T0+MWVUPj1Bw==".
Can someone help me out?
My AS3 code is like this:
private function encode():void
{
var ba:ByteArray = new ByteArray();
ba.writeMultiByte("1234","unicode");
var str:String = MD5.hash(ba.toString());
var ba2:ByteArray = new ByteArray();
ba2.writeMultiByte(str.toString(),"unicode");
var encoder:Base64Encoder = new Base64Encoder();
encoder.encodeUTFBytes(ba2.toString());
trace(encoder.toString());
}
When I do the ba.writeMultiByte("1234","unicode"); I get exactly the same ByteArray as in the C# but when I do the MD5.hash(ba.toString()); the new ByteArray is different.
So it looks like it may be a bug in as3corelib's implementation of writing the bits to the digest ByteArray.
It seemingly writes them in big endian format, rather than little endian. Or more specifically, it writes the bits as a collection of 4 integers rather than a collection of bytes, and in the process it mangles the byte order of the bits (which is why you are seeing different Base64 results -- the bytes are in a different order).
You can address this bug by adding on line 184 of MD5.as, in as3corelib, insert the following one line of code:
digest.endian = Endian.LITTLE_ENDIAN;
Also make sure you add an import at the top of the file for import flash.utils.Endian. I've created a public gist of the changes available here
Then it should generate the same byte order as c#, and then it should Base64 encode the same way. I verified it using the following as3 function:
private function encode():void
{
var ba:ByteArray = new ByteArray();
ba.endian = Endian.BIG_ENDIAN
ba.writeMultiByte("1234","unicode");
var str:String = MD5.hashBytes(ba);
var encoder:Base64Encoder = new Base64Encoder();
encoder.encodeBytes(MD5.digest);
trace(encoder.toString()); // DwN1hMmef9T0+MWVUPj1Bw==
}
Look at this.
Be aware that the original download mentioned in that site has a few bugs, so that you have to use the corrected vesion that you can find in the same post.
If you're using AS3CoreLib, do it this way:
Different MD5 with as3corelib.