Verifying Amazon SNS signatures in C# - c#

I have an ASP.NET endpoint where I receive Amazon SNS messages. I want to verify the signatures I receive along with those messages and have followed the guide at http://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.example.java.html trying to match the Java code in C#. Here's what I have so far (I'm only interested in validating notifications, not subscription confirmations):
private X509Certificate2 cert;
// cert is from constructor...
private bool IsValidMessageSignature(AmazonMessage msg)
{
// Verify the signature
var rsa = (RSACryptoServiceProvider)cert.PublicKey.Key;
var msgBytes = GetMessageBytes(msg);
var signedBytes = Convert.FromBase64String(msg.Signature);
return rsa.VerifyData(msgBytes, CryptoConfig.MapNameToOID("SHA1"), signedBytes);
}
private byte[] GetMessageBytes(AmazonMessage msg)
{
// Construct message string
var sb = new StringBuilder();
sb.AppendLine("Message");
sb.AppendLine(msg.Message);
sb.AppendLine("MessageId");
sb.AppendLine(msg.MessageId);
if (msg.Subject != null)
{
sb.AppendLine("Subject");
sb.AppendLine(msg.Subject);
}
sb.AppendLine("Timestamp");
sb.AppendLine(msg.Timestamp);
sb.AppendLine("TopicArn");
sb.AppendLine(msg.TopicArn);
sb.AppendLine("Type");
sb.AppendLine(msg.Type);
return Encoding.UTF8.GetBytes(sb.ToString());
}
The rsa.VerifyData() step returns false. I have built the example in Java as well, and here it works fine with the same message I'm trying to validate, and the same certificate that I'm using for C#.
Here's where the two programs differ as far as I can see. The C# GetMessageBytes byte-array returns 637 bytes, while the Java equivalent getMessageBytesToSign returns 627 bytes. Unfortunately I cannot post the message contents here for security reasons. My Java setup uses the windows-1252 charset by default, but even if I change the C# encoding to that the byte-array is still 637 in size. I'm not that experienced with encodings and the differences between C# and Java so I don't know if it's of any importance though.
Any ideas as to how my C# should be changed?

Amazon.SimpleNotificationService.Util.Message.ParseMessage(SNS_MESSAGE).IsMessageSignatureValid()

The Java code ends lines with \n. The C# version (StringBuilder.AppendLine()) uses \r\n.

It worked for me this way:
var sb = new StringBuilder();
sb.Append("Message\n");
sb.Append(notificationWrapper.Message).Append("\n");
sb.Append("MessageId\n");
sb.Append(notificationWrapper.MessageId).Append("\n");
if (notificationWrapper.Subject != null)
{
sb.Append("Subject\n");
sb.Append(notificationWrapper.Subject).Append("\n");
}
sb.Append("Timestamp\n");
sb.Append(notificationWrapper.Timestamp).Append("\n");
sb.Append("TopicArn\n");
sb.Append(notificationWrapper.TopicArn).Append("\n");
sb.Append("Type\n");
sb.Append(notificationWrapper.Type).Append("\n");
return Encoding.UTF8.GetBytes(sb.ToString());

Related

Generating an MD5 from a downloaded file via HttpClient does not match original MD5

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:

Authenticating Mandrill Inbound Webhook Requests in .NET

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);
...

Md5 Password in AS3

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.

HMACSHA1 from C# To PHP

I am trying to understand these line of code from C# (.Net) method to create a signature. However, I would like to do the same in PHP but not quite understand what it does in C#.
I wonder if any .net developer out there can help me to interpret the code below in 'English'?
Many thanks
HMAC hasher;
Byte[] utf8EncodedString = Encoding.UTF8.GetBytes(String.Format("{0}:{1}:{2}", MethodName, TimeStamp, AccessID));
hasher = HMACSHA1.Create();
hasher.Key = Encoding.UTF8.GetBytes(AccessKey);
Byte[] hashResult = hasher.ComputeHash(utf8EncodedString);
return Convert.ToBase64String(hashResult);
Here is my PHP code. Is this correct?
$signatureString = $methodName.':'.$timeStamp.':'.$accessID;
return hash_hmac('sha1', $signatureString, $accessKey, false);
Update: 30-05-2012
Just got it to work now
$signature = base64_encode(hash_hmac('sha1', $signatureString, $accessKey,true));
Answer on php.net for you http://www.php.net/manual/ru/function.hash-hmac.php#105099

Generate authenticated CMSEnvelopedData Messages with bouncycastle

I am trying to encrypt data with a password and store it inside a ASN.1 encoded CMS message (using C# and BouncyCastle 1.4)
The code I have seems to have two problems:
the data does not seem to be signed with a HMAC, so when I tamper with the encodedData (by enabling the commented out line), the decryption still succeeds.
when I decrypt the data I have tampered with, I get beck corrupted plain text. However only a two blocks of plaintext data are corrupted. This seems to suggest that the encryption does not actually use CBC mode.
(edit: disregard the second point, this is exactly how CBC is supposed to work)
This is what I am testing with:
public void TestMethod1()
{
byte[] data = new byte[1024]; // plaintext: a list of zeroes
CmsEnvelopedDataGenerator generator = new CmsEnvelopedDataGenerator();
CmsPbeKey encryptionKey = new Pkcs5Scheme2PbeKey("foo", new byte[] { 1, 2, 3 }, 2048);
generator.AddPasswordRecipient(encryptionKey, CmsEnvelopedDataGenerator.Aes256Cbc);
CmsProcessableByteArray cmsByteArray = new CmsProcessableByteArray(data);
CmsEnvelopedData envelopeData = generator.Generate(cmsByteArray, CmsEnvelopedDataGenerator.Aes256Cbc);
byte[] encodedData = envelopeData.GetEncoded();
// encodedData[500] = 10; // tamper with the data
RecipientID recipientID = new RecipientID();
CmsEnvelopedData decodedEnvelopeData = new CmsEnvelopedData(encodedData);
RecipientInformation recipient = decodedEnvelopeData.GetRecipientInfos().GetFirstRecipient(recipientID);
byte[] data2 = recipient.GetContent(encryptionKey);
CollectionAssert.AreEqual(data, data2);
}
What am I doing wrong? What would be the correct way to write this?
To add an HMAC to a CMS message, you would have to use a AuthenticatedData-structure.
I am not especially familiar with Bouncy Castle, but from a cursory look at the API, I would say that it does not support AuthenticatedData. In fact, it looks like it only supports SignedData for authentication.
So your options seems to be:
Use another library (or write your own code) to handle the AuthenticatedData-structure.
Calculate the HMAC and provide it in a non-standard way (in a proprietary Attribute or out-of-band).
Use SignedData with an RSA key pair instead.

Categories