For a payment provider, I need to calculate a hash-based message authentication code, using HMAC-SHA256. That is causing me quite a bit of trouble.
The payment provider gives two examples of orrectly calculated authentication code in pseudo-code. All keys are in hex.
Method 1
key = 57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66
message = "amount=100¤cy=EUR"
MAC = HMAC-SHA256( hexDecode(key), message )
result = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Method 2
message = "amount=100¤cy=EUR"
Ki = 61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c17616075060950
Ko = 0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b0a1f6c633a
MAC = SHA256( hexDecode(Ko) + SHA256( hexDecode(Ki) + message ) )
result = b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
I tried to write the code to do this, after doing some research, but I keep coming up with different results.
private static void Main(string[] args)
{
var key = "57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66";
var ki = "61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c17616075060950";
var ko = "0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b0a1f6c633a";
var mm = "amount=100¤cy=EUR";
var result1 = CalcHMACSHA256Hash(HexDecode(key), mm);
var result2 = CalcSha256Hash(string.Format("{0}{1}", HexDecode(ko), CalcSha256Hash(HexDecode(ki) + mm)));
Console.WriteLine("Expected: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905");
Console.WriteLine("Actual 1: " + result1);
Console.WriteLine("Actual 2: " + result2);
Console.WriteLine("------------------------------");
Console.ReadKey();
}
private static string HexDecode(string hex)
{
var sb = new StringBuilder();
for (int i = 0; i <= hex.Length - 2; i += 2)
{
sb.Append(Convert.ToString(Convert.ToChar(Int32.Parse(hex.Substring(i, 2), System.Globalization.NumberStyles.HexNumber))));
}
return sb.ToString();
}
private static string CalcHMACSHA256Hash(string plaintext, string salt)
{
string result = "";
var enc = Encoding.Default;
byte[]
baText2BeHashed = enc.GetBytes(plaintext),
baSalt = enc.GetBytes(salt);
System.Security.Cryptography.HMACSHA256 hasher = new HMACSHA256(baSalt);
byte[] baHashedText = hasher.ComputeHash(baText2BeHashed);
result = string.Join("", baHashedText.ToList().Select(b => b.ToString("x2")).ToArray());
return result;
}
public static string CalcSha256Hash(string input)
{
SHA256 sha256 = new SHA256Managed();
byte[] sha256Bytes = Encoding.Default.GetBytes(input);
byte[] cryString = sha256.ComputeHash(sha256Bytes);
string sha256Str = string.Empty;
for (int i = 0; i < cryString.Length; i++)
{
sha256Str += cryString[i].ToString("x2");
}
return sha256Str;
}
And this is the result I get:
Expected: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Actual 1: 421ce16f2036bb9f2a3770c16f01e9220f0232d45580584ca41768fd16c15fe6
Actual 2: 290f14398bf8c0959dfc963e2fd9c377534c6fec1983025d2ab192382f132b92
So with none of the two methods, I can get the result the provider example wants.
What am I missing here? Is it encoding? Is my hexDecode screwed up?
Test tool from payment provider: http://tech.dibs.dk/dibs_api/other_features/hmac_tool/
PHP sample code: http://tech.dibspayment.com/dibs_api/other_features/mac_calculation/
Edit: You likely are looking for a quick and simple way to do HMAC-SHA256 and not get into the finer details. The original question asks of those finer details which are explained further below.
I want to perform a HMAC-SHA256 on a byte[] message input
using System.Security.Cryptography;
...
private static byte[] HashHMAC(byte[] key, byte[] message)
{
var hash = new HMACSHA256(key);
return hash.ComputeHash(message);
}
I want to perform HMAC-SHA256 but I have a hex string input
In .NET 5 and above, use System.Convert.FromHexString like so, (thanks #proximab). If you're on pre-.NET 5, scroll to "Helper functions" which has alternative solutions.
using System;
using System.Security.Cryptography;
...
private static byte[] HashHMACHex(string keyHex, string messageHex)
{
var key = Convert.FromHexString(hexKey);
var message = Convert.FromHexString(messageHex);
var hash = new HMACSHA256(key);
return hash.ComputeHash(message);
}
I'm using a strange API service that sort of does HMAC, but it's something custom
Continue reading. You likely want to use "Method 2" below as a reference point and adjust it to however your service wants you to implement HMAC for message anti-tampering.
How HMAC-SHA256 Works (should you need to know how...)
Here we will compute an HMAC-SHA256 manually (this answers "Method 2" from the original question).
Assume outerKey, innerKey, and message are already byte arrays, we perform the following:
Notation: Assume A + B concatenates byte array A and B. You may
alternatively see A || B notation used in more academic settings.
HMAC = SHA256( outerKey + SHA256( innerKey + message ) )
. . `------------------´ . .
\ \ `innerData` / /
\ `------------------------´ /
\ `innerHash` /
`----------------------------------´
`data`
So the code can be broken down into these steps (using the above as a guide):
Create an empty buffer byte[] innerData the length of innerKey.Length + message.Length (again assuming byte arrays)
Copy the innerKey and the message into the byte[] innerData
Compute SHA256 of innerData and store it in byte[] innerHash
Create an empty buffer byte[] data the length of outerKey.Length + innerHash.Length
Copy the outerKey and innerHash (from step #3)
Compute the final hash of data and store it in byte[] result and return it.
To do the byte copying I'm using the Buffer.BlockCopy() function since it apparently faster than some other ways (source).
n.b. There is likely (read: most certainly) a better way to do this using the the new ReadOnlySpan<T> API.
We can translate those steps into the following:
using System;
using System.Security.Cryptography;
...
private static byte[] HashSHA(byte[] innerKey, byte[] outerKey, byte[] message)
{
var hash = new SHA256Managed();
// Compute the hash for the inner data first
byte[] innerData = new byte[innerKey.Length + message.Length];
Buffer.BlockCopy(innerKey, 0, innerData, 0, innerKey.Length);
Buffer.BlockCopy(message, 0, innerData, innerKey.Length, message.Length);
byte[] innerHash = hash.ComputeHash(innerData);
// Compute the entire hash
byte[] data = new byte[outerKey.Length + innerHash.Length];
Buffer.BlockCopy(outerKey, 0, data, 0, outerKey.Length);
Buffer.BlockCopy(innerHash, 0, data, outerKey.Length, innerHash.Length);
byte[] result = hash.ComputeHash(data);
return result;
}
Helper functions
string -> byte[]
You have plain ASCII or UTF8 text, but need it to be a byte[].
Use ASCIIEncoding or UTF8Encoding or whichever exotic encoding you're using.
private static byte[] StringEncode(string text)
{
var encoding = new System.Text.ASCIIEncoding();
return encoding.GetBytes(text);
}
byte[] -> hex string
You have a byte[], but you need it to be a hex string.
private static string HashEncode(byte[] hash)
{
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
hex string -> byte[]
You have a hex string, but you need it to be a byte[]`.
.NET 5 and above
private static byte[] HexDecode(string hex) =>
System.Convert.FromHexString(hex);
Before .NET 5 (thanks #bobince)
private static byte[] HexDecode(string hex)
{
var bytes = new byte[hex.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = byte.Parse(hex.Substring(i * 2, 2), NumberStyles.HexNumber);
}
return bytes;
}
n.b. If you need a performance tuned version on .NET Framework 4.x, you can alternatively backport the .NET 5+ version (by replacing ReadOnlySpan<byte> with byte[]). It uses proper lookup tables and conscious about hot-code paths. You can reference the .NET 5 (MIT licensed) System.Convert code on Github.
For completeness, here are the final methods answering the question using both "Method 1" and "Method 2"
"Method 1" (using .NET libraries)
private static string HashHMACHex(string keyHex, string message)
{
byte[] hash = HashHMAC(HexDecode(keyHex), StringEncode(message));
return HashEncode(hash);
}
"Method 2" (manually computed)
private static string HashSHAHex(string innerKeyHex, string outerKeyHex, string message)
{
byte[] hash = HashSHA(HexDecode(innerKeyHex), HexDecode(outerKeyHex), StringEncode(message));
return HashEncode(hash);
}
We can perform a quick sanity check with a console app:
static void Main(string[] args)
{
string message = "amount=100¤cy=EUR";
string expectedHex = "b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905";
Console.WriteLine("Expected: " + expectedHex);
// Test out the HMAC hash method
string key = "57617b5d2349434b34734345635073433835777e2d244c31715535255a366773755a4d70532a5879793238235f707c4f7865753f3f446e633a21575643303f66";
string hashHMACHex = HashHMACHex(key, message);
Console.WriteLine("Method 1: " + hashHMACHex);
// Test out the SHA hash method
string innerKey = "61574d6b157f757d02457573556645750e0341481b127a07476303136c005145436c7b46651c6e4f4f040e1569464a794e534309097258550c17616075060950";
string outerKey = "0b3d27017f151f17682f1f193f0c2f1f64692b227178106d2d096979066a3b2f2906112c0f760425256e647f032c2013243929636318323f667d0b0a1f6c633a";
string hashSHAHex = HashSHAHex(innerKey, outerKey, message);
Console.WriteLine("Method 2: " + hashSHAHex);
}
You should have all the hashes line up correctly:
Expected: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Method 1: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
Method 2: b436e3e86cb3800b3864aeecc8d06c126f005e7645803461717a8e4b2de3a905
The original code for this answer can be accessed at:
http://pastebin.com/xAAuZrJX
Here's a string extension method for getting a fairly standard HMAC SHA 256 token for a given string:
usage:
myMessageString.HmacSha256Digest(mySecret)
string extension method:
public static string HmacSha256Digest(this string message, string secret)
{
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] keyBytes = encoding.GetBytes(secret);
byte[] messageBytes = encoding.GetBytes(message);
System.Security.Cryptography.HMACSHA256 cryptographer = new System.Security.Cryptography.HMACSHA256(keyBytes);
byte[] bytes = cryptographer.ComputeHash(messageBytes);
return BitConverter.ToString(bytes).Replace("-", "").ToLower();
}
You can use this method for HMACSHA256.
string key = "your key";
string message = "your message";
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(key);
HMACSHA256 hmacsha256 = new HMACSHA256(keyByte);
byte[] messageBytes = encoding.GetBytes(message);
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
return ByteToString(hashmessage);
Here is the ByteToString method:
public static string ByteToString(byte[] buff)
{
string sbinary = "";
for (int i = 0; i < buff.Length; i++)
{
sbinary += buff[i].ToString("X2"); // hex format
}
return (sbinary);
}
A SHA hash is calculated on a sequence of bytes. Bytes are a profoundly different datatype to characters. You should not use character Strings to store binary data such as hashes.
sb.Append(Convert.ToString(Convert.ToChar(Int32.Parse(hex.Substring(i, 2)...
This creates a character string by reading each encoded byte and turning into a character of the same Unicode code point number. This is equivalent to decoding the bytes 0-255 using the ISO-8859-1 (Latin1) encoding, due to that encoding's property of matching the first 256 code points in Unicode.
var enc = Encoding.Default; [...]
baSalt = enc.GetBytes(salt);
byte[] sha256Bytes = Encoding.Default.GetBytes(input);
These both convert the characters back to bytes using the system default encoding. This encoding varies between installs, but it will never be ISO-8859-1 - even the similar Western European code page 1252 has different characters in the range 0x80-0x9F.
Consequently the byte array you are using doesn't contain the bytes implied by the example hex sequences. A cheap fix would be to use Encoding.GetEncoding("ISO-8859-1") instead of the default encoding, but really you should be using a bytes array to store data in the first place instead of a String, eg:
byte[] key= new byte[] { 0x57, 0x61, 0x7b, 0x5d, 0x23, 0x49, ... };
and pass that directly into ComputeHash.
If you must initialise data from a hex string, parse it directly into a byte array, eg:
private static byte[] HexDecode(string hex) {
var bytes= new byte[hex.Length/2];
for (int i= 0; i<bytes.Length; i++) {
bytes[i]= byte.Parse(hex.Substring(i*2, 2), NumberStyles.HexNumber);
}
return bytes;
}
I realize the question is answered, but I am posting this in case others need it. Here is a snippet of code created by the payment provider (DIBS):
/**
* calculateMac
* Calculates the MAC key from a Dictionary<string, string> and a secret key
* #param params_dict The Dictionary<string, string> object containing all keys and their values for MAC calculation
* #param K_hexEnc String containing the hex encoded secret key from DIBS Admin
* #return String containig the hex encoded MAC key calculated
**/
public static string calculateMac(Dictionary<string, string> paramsDict, string kHexEnc)
{
//Create the message for MAC calculation sorted by the key
var keys = paramsDict.Keys.ToList();
keys.Sort();
var msg = "";
foreach (var key in keys)
{
if (key != keys[0]) msg += "&";
msg += key + "=" + paramsDict[key];
}
//Decoding the secret Hex encoded key and getting the bytes for MAC calculation
var kBytes = new byte[kHexEnc.Length / 2];
for (var i = 0; i < kBytes.Length; i++)
{
kBytes[i] = byte.Parse(kHexEnc.Substring(i * 2, 2), NumberStyles.HexNumber);
}
//Getting bytes from message
var msgBytes = Encoding.Default.GetBytes(msg);
//Calculate MAC key
var hash = new HMACSHA256(kBytes);
var macBytes = hash.ComputeHash(msgBytes);
var mac = BitConverter.ToString(macBytes).Replace("-", "").ToLower();
return mac;
}
http://tech.dibspayment.com/DX/Hosted/HMAC
Thanks you saved my time.
request.Method = "GET";
string signature = "";
string strtime = DateTime.UtcNow.ToString("yyyy-MM-ddTHH\\:mm\\:ssZ");
string secret = "xxxx";
string message = "sellerid:email:" + strtime;
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(secret);
byte[] messageBytes = encoding.GetBytes(message);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
var hash = new HMACSHA256(keyByte);
byte[] signature1 = hash.ComputeHash(messageBytes);
signature = BitConverter.ToString(signature1).Replace("-", "").ToLower();
}
request.Headers.Add("authorization", "HMAC-SHA256" + " " +
"emailaddress=xxx#xx.com,timestamp=" + strtime + ",signature=" + signature);
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
private static string GenerateSignature(string data, string signatureKey)
{
var keyByte = Encoding.UTF8.GetBytes(signatureKey);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(data));
return hmacsha256.Hash.Aggregate("", (current, t) => current + t.ToString("X2")).ToLower();
}
}
Related
I want to encrypt password using both in CryptoJS and C#. Unfortunately, my C# code fails to generate the proper value. This is my code
internal static byte[] ComputeSha256(this byte[] value)
{
using (SHA256 sha256Hash = SHA256.Create())
return sha256Hash.ComputeHash(value);
}
internal static byte[] ComputeSha256(this string value) => ComputeSha256(Encoding.UTF8.GetBytes(value));
internal static byte[] ComputeMD5(this byte[] value)
{
using (MD5 md5 = MD5.Create())
return md5.ComputeHash(value);
}
internal static byte[] ComputeMD5(this string value) => ComputeMD5(Encoding.UTF8.GetBytes(value));
internal static byte[] CombineByteArray(byte[] first, byte[] second)
{
byte[] bytes = new byte[first.Length + second.Length];
Buffer.BlockCopy(first, 0, bytes, 0, first.Length);
Buffer.BlockCopy(second, 0, bytes, first.Length, second.Length);
return bytes;
}
internal static string EncryptPassword()
{
using (AesManaged aes = new AesManaged())
{
//CLIENT SIDE PASSWORD HASH
/*
var password = '12345';
var passwordMd5 = CryptoJS.MD5(password);
var passwordKey = CryptoJS.SHA256(CryptoJS.SHA256(passwordMd5 + '12345678') + '01234567890123456');
var encryptedPassword = CryptoJS.AES.encrypt(passwordMd5, passwordKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.NoPadding });
encryptedPassword = CryptoJS.enc.Base64.parse(encryptedPassword.toString()).toString(CryptoJS.enc.Hex);
//encryptedPassword result is c3de82e9e8a28a4caded8c2ef0d49c80
*/
var y1 = Encoding.UTF8.GetBytes("12345678");
var y2 = Encoding.UTF8.GetBytes("01234567890123456");
var password = "12345";
var passwordMd5 = ComputeMD5(password);
var xkey = CombineByteArray(ComputeSha256(CombineByteArray(passwordMd5, y1)), y2);
var passwordKey = ComputeSha256(xkey);
aes.Key = passwordKey;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
ICryptoTransform crypt = aes.CreateEncryptor();
byte[] cipher = crypt.TransformFinalBlock(passwordMd5, 0, passwordMd5.Length);
var encryptedPassword = BitConverter.ToString(cipher).Replace("-", "").ToLower();
return encryptedPassword; //e969b60e87339625c32f805f17e6f993
}
}
The result of the C# code above is e969b60e87339625c32f805f17e6f993. It should be the same with CryptoJS c3de82e9e8a28a4caded8c2ef0d49c80. What is wrong here?
In the CryptoJS code hashes (in the form of WordArrays) and strings are added in several places. Thereby the WordArray is implicitly encoded with toString() into a hex string with lowercase letters. This is missing in the C# code.
In the C# code the addition is done with CombineByteArray(), where the hash is passed in the parameter first as byte[]. Therefore this parameter must first be converted to a hex encoded string with lowercase letters and then UTF8 encoded, e.g.:
internal static byte[] CombineByteArray(byte[] first, byte[] second)
{
// Hex encode (with lowercase letters) and Utf8 encode
string hex = ByteArrayToString(first).ToLower();
first = Encoding.UTF8.GetBytes(hex);
byte[] bytes = new byte[first.Length + second.Length];
Buffer.BlockCopy(first, 0, bytes, 0, first.Length);
Buffer.BlockCopy(second, 0, bytes, first.Length, second.Length);
return bytes;
}
where ByteArrayToString() is from here.
With this change, the C# code gives the same result as the CryptoJS code.
I am not quite clear about the purpose of the CryptoJS code. Usually plaintext and key are independent, i.e. are not derived from the same password.
Perhaps this is supposed to implement a custom password-based key derivation function. If so, and unless a custom implementation is mandatory for compatibility reasons, it is more secure to use a proven algorithm such as Argon2 or PBKDF2. In particular, the lack of a salt/work factor is insecure.
I've a C# method which I have to convert to PHP. I tried several things, but I still get different results. Unfortunately I cannot change anything on the C# application. It has to be as it is. Maybe one of you could help?
C#:
static public void Main ()
{
string StringToSign = "test";
string Key = "123456";
//Calculate Signature
string Signature = CalculateSignature(StringToSign, Key);
Console.WriteLine ("StringToSign: " + StringToSign);
Console.WriteLine ("Key: " + Key);
Console.WriteLine ("Signature Caculated: " + Signature + "\r\n");
}
static private string CalculateSignature(String StringToSign, String Key)
{
Encoding enc = Encoding.GetEncoding(65001);
byte[] KeyHex = StringHexValuesToByteArray(Key);
byte[] StringToSign_byte = enc.GetBytes(StringToSign);
//Check Signature
HMACSHA256 hmac = new HMACSHA256(KeyHex);
byte[] hashValue = hmac.ComputeHash(StringToSign_byte);
return BitConverter.ToString(hashValue).Replace("-", "");
}
static public byte[] StringHexValuesToByteArray(string str)
{
if (str.Length % 2 != 0)
return null;
string s = string.Empty;
byte[] ret = new byte[str.Length / 2];
for (int run = 0; run < str.Length / 2; run++)
{
s = str.Substring(run * 2, 2);
ret[run] = Convert.ToByte(s, 16);
}
return ret;
}
PHP:
public function send() {
$stringToSign = 'test';
$key = '123456';
//Calculate Signature
$signature = $this->calculateSignature($stringToSign, $key);
print_r("StringToSign: " . $stringToSign . PHP_EOL);
print_r("Key: " . $key . PHP_EOL);
print_r("Signature Caculated: " . $signature . PHP_EOL);
}
private function calculateSignature($stringToSign, $key) {
// check signature
$hash = strtoupper(hash_hmac('sha256', $stringToSign, $key, false));
return $hash;
}
For better understanding, here is the output of the code blocks above:
C#
StringToSign: test
Key: 123456
Signature Caculated:
DA3617974490FB780F04F06287BF93B0F24A7F15970471146428B943FFDC7850
PHP
StringToSign: test
GroupKey: 123456
Signature Caculated: 9D2BB116E1DF997FFE8A5139FC1D187F976C19579A138414A112BC2E39020EBA
If you want to modify the PHP to make it equivalent to C#
Change
$hash = strtoupper(hash_hmac('sha256', $stringToSign, $key, false));
to
$hash = strtoupper(hash_hmac('sha256', $stringToSign, hex2bin($key), false));
Be sure to check the code will work for non-ASCII characters like àèéìòù.
ideone here.
If you want to modify the C# code to make it equivalent to PHP
You are complicating everything:
static private string CalculateSignature(String stringToSign, String key)
{
byte[] key_byte = Encoding.UTF8.GetBytes(key);
byte[] stringToSign_byte = Encoding.UTF8.GetBytes(stringToSign);
//Check Signature
HMACSHA256 hmac = new HMACSHA256(key_byte);
byte[] hashValue = hmac.ComputeHash(stringToSign_byte);
return BitConverter.ToString(hashValue).Replace("-", "");
}
There is nothing hex. And instead of using Encoding.GetEncoding(65001) it is normally better to use Encoding.UTF8, because it is clearer for everyone.
Note that there could be problems with the encoding. Try calculating the hmac of something like àèéìòù to see which encoding the PHP is using.
Let's say I need to do this in Powershell:
$SecurePass = Get-Content $CredPath | ConvertTo-SecureString -Key (1..16)
[String]$CleartextPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($CredPass));
The content of $CredPath is a file that contains the output of ConvertFrom-SecureString -Key (1..16).
How do I accomplish the ConvertTo-SecureString -key (1..16) portion in C#/.NET?
I know how to create a SecureString, but I'm not sure how the encryption should be handled.
Do I encrypt each character using AES, or decrypt the string and then create a the secure string per character?
I know next to nothing about cryptography, but from what I've gathered I might just want to invoke the Powershell command using C#.
For reference, I found a similar post about AES encryption/decryption here:
Using AES encryption in C#
UPDATE
I have reviewed the link Keith posted, but I face additional unknowns. The DecryptStringFromBytes_Aes takes three arguments:
static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
The first argument is a byte array represents the encrypted text. The question here is, how should the string be represented in the byte array? Should it be represented with or without encoding?
byte[] ciphertext = Encoding.ASCII.GetBytes(encrypted_text);
byte[] ciphertext = Encoding.UTF8.GetBytes(encrypted_text);
byte[] ciphertext = Encoding.Unicode.GetBytes(encrypted_text);
byte[] ciphertext = new byte[encrypted_password.Length * sizeof(char)];
System.Buffer.BlockCopy(encrypted_password.ToCharArray(), 0, text, 0, text.Length);
The second byte array is the key should simply be an array of integers:
byte[] key = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
The third byte array is an "Initialization Vector" - it looks like the Aes.Create() call will generate a byte[] for IV randomly. Reading around, I've found that I might need to use the same IV. As ConvertFrom-SecureString and ConvertTo-SecureString are able to encrypt/decrypt using simply the key, I am left with the assumption that the IV[] can be random -or- has a static definition.
I have not yet found a winning combination, but I will keep trying.
I know this is an old post. I am posting this for completeness and posterity, because I couldn't find a complete answer on MSDN or stackoverflow. It will be here in case I ever need to do this again.
It is a C# implementation of of powershell's ConvertTo-SecureString with AES encryption (turned on by using the -key option). I will leave it for exercise to code a C# implementation of ConvertFrom-SecureString.
# forward direction
[securestring] $someSecureString = read-host -assecurestring
[string] $psProtectedString = ConvertFrom-SecureString -key (1..16) -SecureString $someSecureString
# reverse direction
$back = ConvertTo-SecureString -string $psProtectedString -key (1..16)
My work is combining answers and re-arranging user2748365's answer to be more readable and adding educational comments! I also fixed the issue with taking a substring -- at the time of this post, his code only has two elements in strArray.
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Globalization;
// psProtectedString - this is the output from
// powershell> $psProtectedString = ConvertFrom-SecureString -SecureString $aSecureString -key (1..16)
// key - make sure you add size checking
// notes: this will throw an cryptographic invalid padding exception if it cannot decrypt correctly (wrong key)
public static SecureString ConvertToSecureString(string psProtectedString, byte[] key)
{
// '|' is indeed the separater
byte[] asBytes = Convert.FromBase64String( psProtectedString );
string[] strArray = Encoding.Unicode.GetString(asBytes).Split(new[] { '|' });
if (strArray.Length != 3) throw new InvalidDataException("input had incorrect format");
// strArray[0] is a static/magic header or signature (different passwords produce
// the same header) It unused in our case, looks like 16 bytes as hex-string
// you know strArray[1] is a base64 string by the '=' at the end
// the IV is shorter than the body, and you can verify that it is the IV,
// because it is exactly 16bytes=128bits and it decrypts the password correctly
// you know strArray[2] is a hex-string because it is [0-9a-f]
byte[] magicHeader = HexStringToByteArray(encrypted.Substring(0, 32));
byte[] rgbIV = Convert.FromBase64String(strArray[1]);
byte[] cipherBytes = HexStringToByteArray(strArray[2]);
// setup the decrypter
SecureString str = new SecureString();
SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
ICryptoTransform transform = algorithm.CreateDecryptor(key, rgbIV);
using (var stream = new CryptoStream(new MemoryStream(cipherBytes), transform, CryptoStreamMode.Read))
{
// using this silly loop format to loop one char at a time
// so we never store the entire password naked in memory
int numRed = 0;
byte[] buffer = new byte[2]; // two bytes per unicode char
while( (numRed = stream.Read(buffer, 0, buffer.Length)) > 0 )
{
str.AppendChar(Encoding.Unicode.GetString(buffer).ToCharArray()[0]);
}
}
//
// non-production code
// recover the SecureString; just to check
// from http://stackoverflow.com/questions/818704/how-to-convert-securestring-to-system-string
//
IntPtr valuePtr = IntPtr.Zero;
string secureStringValue = "";
try
{
// get the string back
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(str);
secureStringValue = Marshal.PtrToStringUni(valuePtr);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
return str;
}
// from http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa
public static byte[] HexStringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
public static SecureString DecryptPassword( string psPasswordFile, byte[] key )
{
if( ! File.Exists(psPasswordFile)) throw new ArgumentException("file does not exist: " + psPasswordFile);
string formattedCipherText = File.ReadAllText( psPasswordFile );
return ConvertToSecureString(formattedCipherText, key);
}
According to the docs on ConvertFrom-SecureString the AES encryption algorithm is used:
If an encryption key is specified by using the Key or SecureKey
parameters, the Advanced Encryption Standard (AES) encryption
algorithm is used. The specified key must have a length of 128, 192,
or 256 bits because those are the key lengths supported by the AES
encryption algorithm. If no key is specified, the Windows Data
Protection API (DPAPI) is used to encrypt the standard string
representation.
Look at the DecryptStringFromBytes_Aes example in the MSDN docs.
BTW an easy option would be to use the PowerShell engine from C# to execute the ConvertTo-SecureString cmdlet to do the work. Otherwise, it looks like the initialization vector is embedded somewhere in the ConvertFrom-SecureString output and may or may not be easy to extract.
How do I accomplish the ConvertTo-SecureString -key (1..16) portion in C#/.NET?
Please see the following code:
private static SecureString ConvertToSecureString(string encrypted, string header, byte[] key)
{
string[] strArray = Encoding.Unicode.GetString(Convert.FromBase64String(encrypted.Substring(header.Length, encrypted.Length - header.Length))).Split(new[] {'|'});
SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
int num2 = strArray[2].Length/2;
var bytes = new byte[num2];
for (int i = 0; i < num2; i++)
bytes[i] = byte.Parse(strArray[2].Substring(2*i, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
ICryptoTransform transform = algorithm.CreateDecryptor(key, Convert.FromBase64String(strArray[1]));
using (var stream = new CryptoStream(new MemoryStream(bytes), transform, CryptoStreamMode.Read))
{
var buffer = new byte[bytes.Length];
int num = stream.Read(buffer, 0, buffer.Length);
var data = new byte[num];
for (int i = 0; i < num; i++) data[i] = buffer[i];
var str = new SecureString();
for (int j = 0; j < data.Length/2; j++) str.AppendChar((char) ((data[(2*j) + 1]*0x100) + data[2*j]));
return str;
}
}
Example:
encrypted = "76492d1116743f0423413b16050a5345MgB8ADcAbgBiAGoAVQBCAFIANABNADgAYwBSAEoAQQA1AGQAZgAvAHYAYwAvAHcAPQA9AHwAZAAzADQAYwBhADYAOQAxAGIAZgA2ADgAZgA0AGMANwBjADQAYwBiADkAZgA1ADgAZgBiAGQAMwA3AGQAZgAzAA==";
header = "76492d1116743f0423413b16050a5345";
If you want to get decrypted characters, please check data in the method.
I found the easiest and simplest way was to call the ConvertTo-SecureString PowerShell command directly from C#. That way there's no difference in the implementation and the output is exactly what it would be if you called it from PowerShell directly.
string encryptedPassword = RunPowerShellCommand("\""
+ password
+ "\" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString", null);
public static string RunPowerShellCommand(string command,
Dictionary<string, object> parameters)
{
using (PowerShell powerShellInstance = PowerShell.Create())
{
// Set up the running of the script
powerShellInstance.AddScript(command);
// Add the parameters
if (parameters != null)
{
foreach (var parameter in parameters)
{
powerShellInstance.AddParameter(parameter.Key, parameter.Value);
}
}
// Run the command
Collection<PSObject> psOutput = powerShellInstance.Invoke();
StringBuilder stringBuilder = new StringBuilder();
if (powerShellInstance.Streams.Error.Count > 0)
{
foreach (var errorMessage in powerShellInstance.Streams.Error)
{
if (errorMessage != null)
{
throw new InvalidOperationException(errorMessage.ToString());
}
}
}
foreach (var outputLine in psOutput)
{
if (outputLine != null)
{
stringBuilder.Append(outputLine);
}
}
return stringBuilder.ToString();
}
}
Adding on to Cheng's answer - I found I had to change:
byte[] magicHeader = HexStringToByteArray(encrypted.Substring(0, 32));
to
byte[] magicHeader = HexStringToByteArray(psProtectedString.Substring(0, 32));
and
SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
to
SymmetricAlgorithm algorithm = Aes.Create();
but it otherwise works wonderfully.
I am trying to write a function to take a string and sha512 it like so?
public string SHA512(string input)
{
string hash;
~magic~
return hash;
}
What should the magic be?
Your code is correct, but you should dispose of the SHA512Managed instance:
using (SHA512 shaM = new SHA512Managed())
{
hash = shaM.ComputeHash(data);
}
512 bits are 64 bytes.
To convert a string to a byte array, you need to specify an encoding. UTF8 is okay if you want to create a hash code:
var data = Encoding.UTF8.GetBytes("text");
using (...
This is from one of my projects:
public static string SHA512(string input)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(input);
using (var hash = System.Security.Cryptography.SHA512.Create())
{
var hashedInputBytes = hash.ComputeHash(bytes);
// Convert to text
// StringBuilder Capacity is 128, because 512 bits / 8 bits in byte * 2 symbols for byte
var hashedInputStringBuilder = new System.Text.StringBuilder(128);
foreach (var b in hashedInputBytes)
hashedInputStringBuilder.Append(b.ToString("X2"));
return hashedInputStringBuilder.ToString();
}
}
Please, note:
SHA512 object is disposed ('using' section), so we do not have any resource leaks.
StringBuilder is used for efficient hex string building.
512/8 = 64, so 64 is indeed the correct size. Perhaps you want to convert it to hexadecimal after the SHA512 algorithm.
See also: How do you convert Byte Array to Hexadecimal String, and vice versa?
You might try these lines:
public static string GenSHA512(string s, bool l = false)
{
string r = "";
try
{
byte[] d = Encoding.UTF8.GetBytes(s);
using (SHA512 a = new SHA512Managed())
{
byte[] h = a.ComputeHash(d);
r = BitConverter.ToString(h).Replace("-", "");
}
r = (l ? r.ToLowerInvariant() : r);
}
catch
{
}
return r;
}
It is disposed at the end
It's safe
Supports lower case
Instead of WinCrypt-API using System.Security.Cryptography, you can also use BouncyCastle:
public static byte[] SHA512(string text)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(text);
Org.BouncyCastle.Crypto.Digests.Sha512Digest digester = new Org.BouncyCastle.Crypto.Digests.Sha512Digest();
byte[] retValue = new byte[digester.GetDigestSize()];
digester.BlockUpdate(bytes, 0, bytes.Length);
digester.DoFinal(retValue, 0);
return retValue;
}
If you need the HMAC-version (to add authentication to the hash)
public static byte[] HmacSha512(string text, string key)
{
byte[] bytes = Encoding.UTF8.GetBytes(text);
var hmac = new Org.BouncyCastle.Crypto.Macs.HMac(new Org.BouncyCastle.Crypto.Digests.Sha512Digest());
hmac.Init(new Org.BouncyCastle.Crypto.Parameters.KeyParameter(System.Text.Encoding.UTF8.GetBytes(key)));
byte[] result = new byte[hmac.GetMacSize()];
hmac.BlockUpdate(bytes, 0, bytes.Length);
hmac.DoFinal(result, 0);
return result;
}
Keeping it simple:
using (SHA512 sha512 = new SHA512Managed())
{
password = Encoding.UTF8.GetString(sha512.ComputeHash(Encoding.UTF8.GetBytes(password)));
}
I'm not sure why you are expecting 128.
8 bits in a byte. 64 bytes. 8 * 64 = 512 bit hash.
From the MSDN Documentation:
The hash size for the SHA512Managed algorithm is 512 bits.
You could use the System.Security.Cryptography.SHA512 class
MSDN on SHA512
Here is an example, straigt from the MSDN
byte[] data = new byte[DATA_SIZE];
byte[] result;
SHA512 shaM = new SHA512Managed();
result = shaM.ComputeHash(data);
UnicodeEncoding UE = new UnicodeEncoding();
byte[] message = UE.GetBytes(password);
SHA512Managed hashString = new SHA512Managed();
string hexNumber = "";
byte[] hashValue = hashString.ComputeHash(message);
foreach (byte x in hashValue)
{
hexNumber += String.Format("{0:x2}", x);
}
string hashData = hexNumber;
I used the following
public static string ToSha512(this string inputString)
{
if (string.IsNullOrWhiteSpace(inputString)) return string.Empty;
using (SHA512 shaM = new SHA512Managed())
{
return Convert.ToBase64String(shaM.ComputeHash(Encoding.UTF8.GetBytes(inputString)));
}
}
Made it into an extension method in my ExtensionUtility.cs class
public static string SHA512(this string plainText)
{
using (SHA512 shaM = new SHA512Managed())
{
var buffer = Encoding.UTF8.GetBytes(plainText);
var hashedInputBytes = shaM.ComputeHash(buffer);
return BitConverter.ToString(hashedInputBytes).Replace("-", "");
}
}
I am trying to listen to the Foxycart XML Datafeed in C# and running into an issue which boils down to encryption.
In short, they send over their data as encoded and encrypted XML using RC4 encryption.
To test, they have some (user submitted) sample code to test this with C#. I tried using this sample RC4 decryption code provided by one of the users but it doesn't seem to work and their support staff thinks its down with the C# RC4 algorithm. Since they are not C# experts, i figured i would ask here. Here is the post on the FoxyCart forum
Anyway, here is the code that (tries to) simulate the response by encrypting an XML file and posting it to a URL (NOTE that DataFeedKey is a string that i have stored as a member variable):
public ActionResult TestDataFeed()
{
string transactionData = (new StreamReader(#"D:\SampleFeed.xml")).ReadToEnd();
string encryptedTransactionData = RC4.Encrypt(DataFeedKey, transactionData, false);
string encodedTransactionData = HttpUtility.UrlEncode(encryptedTransactionData, Encoding.GetEncoding(1252));
string postData = "FoxyData=" + encodedTransactionData;
var req = (HttpWebRequest)WebRequest.Create("http://localhost:3396/FoxyCart/RecieveDataFeed");
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
var sw = new StreamWriter(req.GetRequestStream(), Encoding.ASCII);
sw.Write(postData);
sw.Close();
HttpWebResponse resp = null;
try
{
resp = (HttpWebResponse)req.GetResponse();
string r = new StreamReader(resp.GetResponseStream()).ReadToEnd();
}
catch (WebException ex)
{
string err = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
}
return null;
}
and here is the callback method that receives the response.
[ValidateInput(false)]
public ActionResult RecieveDataFeed(FormCollection collection)
{
string unencodedFeed = HttpUtility.UrlDecode(collection["FoxyData"], Encoding.GetEncoding(1252));
string transaction = RC4.Decrypt(DataFeedKey, unencodedFeed, false);
return Content("foxy");
}
Instead of posting the whole RC4 class inline in this question, here is a link to the code of this RC4 class.
As i posted in the above link at the top of the question, the issue is when i check the variable transaction inside the
RecieveDataFeed()
method, i should have the regular XML back but instead i see this:
É?xø´ v´“Û·8êUŸí¥MïSÅJÖó5Cå7ã…ÄlÞ&þòG·¶ÝÙ3<ÍÖ¡«úüF¿¿ßìNµ>4¦Äu÷¼Â;£-w¤ƒûÊyL¹®½èíYö½’é(µJŒ~»»=3¼]F‡•=±Ùí]'鳫"øPç{Ù^yyéå–°ñ…5ðWF$zÉnÄ^_”Xë’ï%œ-5á
ÒÛ€jŠt`Â9œÇÞLU&¼~ç2îžúo/¢¶5,º*öOqÝ—‘.ó®šuf™å5G—õC®‰ÁéiÇúW®¦ÝÚ•Z±:„Q\p"p
ôÔiÛ!\D"ÉÂX3]ƒ°è€Œ«DQE‡kÝ#àö`gpöŽ÷nÛ={µÏßKQKüå(ö%¯¯Ü–9}¨¬°£7yo,«”ÜëCÍ/+…†ÕËî‘‹‰AÚmÇÙå©&©¡xÙkŒföX¯ÃX&×°S|kÜ6Ô°Üú\Ätóü-äUƆÈáÅ\ ’E8‚¤âÈ4Ž¾«ãΚ_Sï£y‰xJº•bm*jo›‰ÜW–[ô†ÆJÐà$½…9½šžˆ_ÙÜù/®öÁVhzŠ¥ú(ñ£²6ˆb6¢ëße¤oáIðZuK}ÆÙ]"T¼*åZêñß5K—½òQSåRN Çë'Å¡
ÕyiÈX •bØðIk¿WxwNàäx®‹?cv+X™¥E!gd4â¤nÔ‹¢½Ð”ªÊQ!‚.e8s
Gyª4¼ò,}Yœ‚¹”±E‡Jy}Sæ
ƒ¦ýK'Ð}~B¦E3!0°ú´A–5Þ³£9$–8äÏ©?
œ‡8GÂø
The code looks right:
Encrypt
Encode
Decode
Decrypt
but it doesn't seem to be working. Any suggestions on what might be wrong above?
I am a bit surprised by the code in the CR4 class. I can't see how it would work reliably.
The code uses windows-1252 encoding to encode characters into bytes, then it encrypts the bytes and tries to decode the bytes into characters. That won't work reliably, as you can only decode bytes that comes from encoding characters.
The method takes a string and returns a string, but it should take a byte array and return a byte array, similar to how all the encryption classes in the framework does it.
Here is a version that works like that:
public class RC4 {
public static byte[] Encrypt(byte[] pwd, byte[] data) {
int a, i, j, k, tmp;
int[] key, box;
byte[] cipher;
key = new int[256];
box = new int[256];
cipher = new byte[data.Length];
for (i = 0; i < 256; i++) {
key[i] = pwd[i % pwd.Length];
box[i] = i;
}
for (j = i = 0; i < 256; i++) {
j = (j + box[i] + key[i]) % 256;
tmp = box[i];
box[i] = box[j];
box[j] = tmp;
}
for (a = j = i = 0; i < data.Length; i++) {
a++;
a %= 256;
j += box[a];
j %= 256;
tmp = box[a];
box[a] = box[j];
box[j] = tmp;
k = box[((box[a] + box[j]) % 256)];
cipher[i] = (byte)(data[i] ^ k);
}
return cipher;
}
public static byte[] Decrypt(byte[] pwd, byte[] data) {
return Encrypt(pwd, data);
}
}
Example:
string data = "This is a test.";
byte[] key = { 1, 2, 3, 4, 5 };
// encrypt
byte[] enc = RC4.Encrypt(key, Encoding.UTF8.GetBytes(data));
// turn into base64 for convenient transport as form data
string base64 = Convert.ToBase64String(enc);
Console.WriteLine(base64);
// turn back into byte array
byte[] code = Convert.FromBase64String(base64);
// decrypt
string dec = Encoding.UTF8.GetString(RC4.Decrypt(key, code));
Console.WriteLine(dec);
Output:
5lEKdtBUswet4yYveWU2
This is a test.
Although this is more shooting in the dark... I am rather certain that the class implementing RC4 looks like it is assuming everyting is either ASCII or CodePage 1252 - both is wrong because I assume that the XML supplied is UTF-8 and .NET string represantion in memory is UTF16...
If my assumption is right the data is already scrambled when you get it back from encryption...
EDIT - some links to working RC4 code in C#:
http://tofuculture.com/Blog/post/RC4-Encryption-in-C.aspx
http://dotnet-snippets.com/dns/rc4-encryption-SID577.aspx
http://www.codeproject.com/KB/recipes/rc4csharp.aspx
http://icodesnip.com/snippet/csharp/rc4-encryption-code-snippets