I have an algorithm in C# running on server side which hashes a base64-encoded string.
byte[] salt = Convert.FromBase64String(serverSalt); // Step 1
SHA256Managed sha256 = new SHA256Managed(); // Step 2
byte[] hash = sha256.ComputeHash(salt); // Step 3
Echo("String b64: " + Convert.ToBase64String(hash)); // Step 4
The hash is then checked against a database list of hashes.
I'd love to achieve the same with javascript, using the serverSalt as it is transmitted from C# through a websocket.
I know SHA-256 hashes different between C# and Javascript because C# and Javascript have different string encodings.
But I know I can pad zeros in the byte array to make Javascript behave as C# (step 1 above is solved).
var newSalt = getByteArrayFromCSharpString(salt); // Pad zeros where needed
function getByteArrayFromCSharpString(inString)
{
var bytes = [];
for (var i = 0; i < inString.length; ++i)
{
bytes.push(inString.charCodeAt(i));
bytes.push(0);
}
return bytes;
}
Could anyone provide some insight on which algorithms I could use to reproduce steps 2, 3 and 4?
PS: there are already questions and answers around but not a single code snippet.
Here's the solution, I really hope this could help other people in the same situation.
In the html file, load crypto-js library
<!-- library for doing password hashing, base64 eoncoding / decoding -->
<script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/components/core-min.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/components/enc-base64-min.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/sha256.js"></script>
In the javascript, do the following
// This function takes a base64 string, hashes it with the SHA256 algorithm
// and returns a base64 string.
function hashBase64StringAndReturnBase64String(str)
{
// Take the base64 string and parse it into a javascript variable
var words = CryptoJS.enc.Base64.parse(str);
// Create the hash using the CryptoJS implementation of the SHA256 algorithm
var hash = CryptoJS.SHA256(words);
var outString = hash.toString(CryptoJS.enc.Base64)
// Display what you just got and return it
console.log("Output string is: " + outString);
return outString;
}
check Java script SHA256 implementation on the following URL
http://www.movable-type.co.uk/scripts/sha256.html
Related
I am trying to calculate the AMP script hash for our site's AMP scripts so we do not have to update them manually each time something in the script changes. We do not use node.js or I would simply use the given solution on the amp-script documentation page (https://amp.dev/documentation/components/amp-script/). I have tried following the steps for the algorithm listed on the doc page but I haven't been able to get the hash to match the one that is actually needed for the meta link. Here is my code so far...
// Compute sha384 sum of script contents
var sha384 = SHA384.Create();
var hashBytes = sha384.ComputeHash(Encoding.UTF8.GetBytes(model.ScriptContents));
// Express hash sum as hexadecimal
var hashHex = BitConverter.ToString(hashBytes);
// Base64url-encode the result
var base64 = Convert.ToBase64String(Encoding.ASCII.GetBytes(hashHex)).TrimEnd('=').Replace('+', '-').Replace('/', '_');
// Prefix with 'sha384-'
var hash = "sha384-" + base64;
You're not actually meant to return the Base64-of-Base16-of-hash; just the Base64-of-hash. I think the part about "This sum should be expressed in hexadecimal." is throwing you off: The instructions on that page are written for JS devs using the toolbox-script-csp NPM module, though I agree the person writing it failed to consider non-JS developers.
Compute the SHA384 hash sum of the script's contents. This sum should be expressed in hexadecimal.
Instead, take a look at the reference implementation in toolbox-script-csp:
https://github.com/ampproject/amp-toolbox/blob/main/packages/script-csp/lib/calculateHash.js
function calculateHash(src, {algorithm = DEFAULT_ALGORITHM} = {}) {
const algo = algorithm.toLowerCase();
if (!SUPPORTED_ALGORITHMS.has(algo)) {
throw new Error(`Unsupported algorithm for CSP: ${algo}`);
}
if (typeof src === 'string') {
src = Buffer.from(src, 'utf8');
}
const hash = crypto.createHash(algo);
const data = hash.update(src);
const base64 = base64URLFormat(data.digest('base64'));
return `${algo}-${base64}`;
}
function base64URLFormat(base64) {
return base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
}
This can be converted over to C#/.NET in a straightforward manner, however....
IMPORTANT NOTE: Do not use String to represent a JS script file that you'll be hashing and serving: the hash of a String is often different than the original file due to meta information-loss during decoding and re-encoding, even if the script's textual content is identical. For example, non-normalized Unicode bytes being normalized when decoded, missing the leading byte-order-mark (which Encoding.UTF8 renders by default, btw!) or even line-break conversions from \n to \r\n (or vice-versa) depending on how your environment is set-up.
...for that reason, the code below uses ReadOnlySpan<Byte> to represent the JS script, not System.String.
The equivalent in .NET Core and .NET 5+ is this:
public static async Task<String> CalculateHashForGoogleAmpAsync( FileInfo jsFile )
{
// DO NOT read the JS file into a String (and then re-encode it to Byte[] to get the hash)! Doing so will apply a potentially lossy Unicode transformation such that the resultant re-encoded bytes will be different to the source file bytes and so cause the hash to not match the source file.
// Instead just read the file's raw bytes and hash that (it's much simpler too!)
Byte[] bytes = await File.ReadAllBytesAsync( jsFile.FullName ).ConfigureAwait(false);
return CalculateHashForGoogleAmp( js: bytes );
}
public static String CalculateHashForGoogleAmp( ReadOnlySpan<Byte> js )
{
Byte[] hash = SHA384.HashData( js );
String hashBase64 = Convert.ToBase64String( hash );
String hashBase64Url = hashBase64.TrimEnd('=').Replace('+', '-').Replace('/', '_'); // Or use `Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode`
return "sha384-" + hashBase64Url;
}
In .NET Framework 4.x and .NET Standard 2.0 the ReadAllBytesAsync and static Byte[] HashData() methods are unavailable, but it's straightforward to workaround by reimplementing those methods... which is beyond the scope of this question, so here's a non-async version:
public static String CalculateHashForGoogleAmp( FileInfo jsFile )
{
Byte[] sha384Hash;
using( SHA384 algo = SHA384.Create() )
using( FileStream fs = File.OpenRead( jsFile.FullName ) )
{
sha384Hash = algo.ComputeHash( fs );
}
return FormatHashForGoogleAmp( sha384Hash );
}
private static String FormatHashForGoogleAmp( Byte[] sha384 )
{
String hashBase64 = Convert.ToBase64String( sha384 );
String hashBase64Url = hashBase64.TrimEnd('=').Replace('+', '-').Replace('/', '_'); // Or use `Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode`
return "sha384-" + hashBase64Url;
}
I'm working on rewriting a piece of PHP code to C#. This code is used for password hashing. In the first step it produces a string like "password{salt}", than hashes it via sha512 hash algorithm. After that a loop is hashing the combination of the first hash and the salt again for 5000 iterations.
The PHP Code looks like this:
<?php
$password = 'abc';
$salt = 'def';
$salted = $password.'{'.$salt.'}';
$digest = hash('sha512', $salted, true);
for ($i=1; $i<5000; $i++) {
$digest = hash('sha512', $digest.$salted, true);
}
$encodedPassword = base64_encode($digest);
//$encodedPassword contains the final hash code
I was able to get it working without the loop (with just the first hash() call). So the main hashing and base64 encoding is done correctly. I found out that this part is what I cannot manage to rewrite in C#:
$digest.$salted
$digest seems to be a binary representation since PHP's hash() function was used with "true" as the last parameter (see PHP hash - manual). $salted is a string. Both get somehow magically combined by PHP's dot / concat operator. I guess there will be some sort of standard conversion from binary to string under the hood when using the dot operator with a non-string operand.
This is my code so far:
void Main()
{
string password = "abc";
string salt = "def";
string salted = String.Format("{0}{{{1}}}", password, salt);
byte[] digest = hash(salted);
for(int i = 1; i < 1; i++)
{
digest = hash(String.Format("{0}{1}", System.Text.Encoding.UTF8.GetString(digest), salted));
}
var encodedPassword = System.Convert.ToBase64String(digest);
//$encodedPassword should contain the final hash code
}
static byte[] hash(string toHash)
{
System.Security.Cryptography.SHA512 sha512 = new System.Security.Cryptography.SHA512Managed();
return sha512.ComputeHash(System.Text.Encoding.UTF8.GetBytes(toHash));
}
As you see I tried to convert the hash bytes back to a string with System.Text.Encoding.UTF8.GetString() and then append the salt but that doesn't produce the same output as the PHP code.
I would be very happy if someone could help me on this. Thank you very much.
In the PHP version you loop 4999 times, while in the C# version 0. The second problem is that the returned bytes from hash() have no encoding at all.
This should give you the same result as the PHP version:
System.Security.Cryptography.SHA512 sha512 = new System.Security.Cryptography.SHA512Managed();
var saltedUtf8Bytes = System.Text.Encoding.UTF8.GetBytes(salted);
for(int i = 1; i < 5000; i++)
{
digest = sha512.ComputeHash(digest.Concat(saltedUtf8Bytes).ToArray());
}
I'm trying to recreate the functionallity of
slappasswd -h {md5}
on .Net
I have this code on Perl
use Digest::MD5;
use MIME::Base64;
$ctx = Digest::MD5->new;
$ctx->add('fredy');
print "Line $.: ", $ctx->clone->hexdigest, "\n";
print "Line $.: ", $ctx->digest, "\n";
$hashedPasswd = '{MD5}' . encode_base64($ctx->digest,'');
print $hashedPasswd . "\n";
I've tried to do the same on VB.Net , C# etc etc , but only works the
$ctx->clone->hexdigest # result : b89845d7eb5f8388e090fcc151d618c8
part in C# using the MSDN Sample
static string GetMd5Hash(MD5 md5Hash, string input)
{
// Convert the input string to a byte array and compute the hash.
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
// Create a new Stringbuilder to collect the bytes
// and create a string.
StringBuilder sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
With this code in Console App :
string source = "fredy";
using (MD5 md5Hash = MD5.Create())
{
string hash = GetMd5Hash(md5Hash, source);
Console.WriteLine("The MD5 hash of " + source + " is: " + hash + ".");
}
outputs : The MD5 hash of fredy is: b89845d7eb5f8388e090fcc151d618c8.
but i need to implement the $ctx->digest function, it outputs some binary data like
¸˜E×ë_ƒˆàüÁQÖÈ
this output happens on Linux and Windows with Perl.
Any ideas?
Thanks
As I already said in my comment above, you are mixing some things up. What the digest in Perl creates is a set of bytes. When those are printed, Perl will convert them automatically to a string-representation, because (simplified) it thinks if you print stuff it goes to a screen and you want to be able to read it. C# does not do that. That doesn't mean the Perl digest and the C# digest are not the same. Just their representation is different.
You have already established that they are equal if you convert both of them to a hexadecimal representation.
Now what you need to do to get output in C# that looks like the string that Perl prints when you do this:
print $ctx->digest; # output: ¸˜E×ë_ƒˆàüÁQÖÈ
... is to convert the C# byte[] data to a string of characters.
That has been answered before,f or example here: How to convert byte[] to string?
Using that technique, I believe your function to get it would look like this. Please note I am a Perl developer and I have no means of testing this. Consider it C#-like pseudo-code.
static string GetMd5PerlishString(MD5 md5Hash, string input)
{
// Convert the input string to a byte array and compute the hash.
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
string result = System.Text.Encoding.UTF8.GetString(data);
return result;
}
Now it should look the same.
Please also note that MD5 is not a secure hashing algorithm for passwords any more. Please do not store use it to store user passwords!
I'm trying to get a website Login page and a C# launcher to connect to a MySQL database, my C# code converts a string to SHA256 but in uppercase. So I figured it would be easier to change in PHP, so using strtoupper I pass the variable string for the encrypted password. It works great the only problem is this:
bec4c38f480db265e86e1650b1515216be5095f7a049852f76eea9934351b9ac - Original
BEC4C38F48DB265E86E1650B1515216BE5095F7A049852F76EEA9934351B9AC - C#
^ Right here there is meant to be a 0
I'm not sure what's gone wrong as both are using the exact same encryption method and it's odd that it's only one Character... Has anyone experienced this before?
PHP to encrypt text to SHA256 and then strtoupper:
$encrypt_password=(hash('sha256', $mypassword));
$s_password = strtoupper($encrypt_password);
C# Convert string to SHA256:
System.Security.Cryptography.SHA256 sha256 = new System.Security.Cryptography.SHA256Managed();
byte[] sha256Bytes = System.Text.Encoding.Default.GetBytes(txtpass.Text);
byte[] cryString = sha256.ComputeHash(sha256Bytes);
string sha256Str = string.Empty;
for (int i = 0; i < cryString.Length; i++)
{
sha256Str += cryString[i].ToString("X");
}
This is the only code that involves encrypting on both sides.
A value like 13 is just "D" not "0D" like would represented in the hash. You need to pad values that are less than 2 digits. Use "X2" as the format string.
I am trying to replicate the following code in PHP, It is example code for an API I have to interface with (The API & Example code is in C#, My app is in PHP 5.3). I'm not a C# developer and so am having trouble doing this.
// C# Code I am trying to replicate in PHP
var apiTokenId = 1887;
var apiToken = "E1024763-1234-5678-91E0-T32E4E7EB316";
// Used to authenticate our request by the API (which is in C#)
var stringToSign = string.Empty;
stringToSign += "POST"+"UserAgent"+"http://api.com/post";
// Here is the issue, How can I do the following 3 lines in PHP?
// No "secret key" provided?.. How do I do this in PHP?
var hmacsha1 = new HMACSHA1(new Guid(apiToken).toByteArray());
// Make a byte array with ASCII encoding.
byte[] byteArray = System.Text.Encoding.ASCII.GetBytes(stringToSign);
// Finally, 'computeHash' of the above (what does this do exactly?!)
var calculatedSignature = Convert.ToBase64String(hmacsha1.ComputeHash(byteArray));
I've tried many variations using pack() and other functions I've found online, but without anything to compare it to, I don't know if i've done it right or not.
Can any C# devs run the above code and post the values generated so I can use that to check/test against?
I've tried checking the MSDN to see what these methods do, but am stuck (and not sure if its correct, as I have nothing to compare it to).
PHP Pseudo Code
// Set vars
$apiToken = 'E1024763-1234-5678-91E0-T32E4E7EB316';
$apiTokenId = '1887';
$stringToSign = "POST"."UserAgent"."http://api.com/post";
// HowTo: Build a `byteArray` of our apiToken? (i think)
// C#: var hmacsha1 = new HMACSHA1(new Guid(apiToken).toByteArray());
// HowTo: Convert our $stringToSign to a ASCII encoded `byteArray`?
// C#: byte[] byteArray = System.Text.Encoding.ASCII.GetBytes(stringToSign);
// HowTo: Generate a base64 string of our (`hmacsha1`.ComputeHash(byteArray))
// C#: var calculatedSignature = Convert.ToBase64String(hmacsha1.ComputeHash(byteArray));
This sounds pretty simple and straightforwaard, but I'm not sure what a few of these C# methods do..
What do these C# methods do/return?
ComputeHash(byteArray) - Computed to what?.. what is returned?
System.Text.Encoding.ASCII.GetBytes(stringToSign); - What does this return?
new HMACSHA1(new Guid(apiToken).toByteArray()); No Secret Key?, what is the key used?
Any resources or help would be much appreciated.
I tried variations of other answers on SO, but no joy.
Can I run the 3 lines of code somewhere online (like JSFiddle but for C#?) so I can see the output of each line?
Update - Bounty Added
Still having trouble with this, I have managed to test the C# code in Visual Studio, but am having trouble getting the same hash generated in PHP.
I would like...
.. the above C# code (specifically, the 3 lines which create the SHA1 hash) to be converted into PHP (Check out the Pseudo Code I posted above). I should be able to match the C# hash using PHP.
If you have any other questions, please ask.
The issue is that the string form of the GUID reverses the order of the 2-character hexadecimal numbers in the first 3 segments of the GUID. For more information see the comments in the example at: http://msdn.microsoft.com/en-us/library/system.guid.tobytearray.aspx
The following code should work:
$apiTokenId = 1887;
$apiToken = "E1024763-1234-5678-91E0-FF2E4E7EB316";
$stringToSign = '';
$hexStr = str_replace('-','',$apiToken);
$c = explode('-',chunk_split($hexStr,2,'-'));
$hexArr = array($c[3],$c[2],$c[1],$c[0],$c[5],$c[4],$c[7],$c[6],$c[8],$c[9],$c[10],$c[11],$c[12],$c[13],$c[14],$c[15]);
$keyStr = '';
for ($i = 0; $i < 16; ++$i) {
$num = hexdec($hexArr[$i]);
$keyStr .= chr($num);
}
$stringToSign .= "POST" . "UserAgent" . "http://api.com/post";
$hmacsha1 = base64_encode(hash_hmac('sha1',$stringToSign,$keyStr,true));
I've tested this code against the C# code you provided above and the output was the same. However, the GUID specified in the original code is not valid so I had to change it slightly.
It's pretty easy, when i don't have to test the code :P
http://php.net/manual/en/function.hash-hmac.php - that's the equivalent of the HMACSHA1 c# class.
string hash_hmac (string $algo , string $data , string $key [, bool $raw_output = false ] )
So $algo = "sha1"
$data is your $stringToSign - since that is already an ascii string (i hope) - the C# was just taking the byte equivalent of the same.
new Guid(apiToken).toByteArray() -> that's a 16 byte (16*8 = 128) representation of the GUID - which is 32*4 = 128 bits. This is the key.
$key is a string so you need the ASCII string equivalent for your $apiToken (which is 32 hex chars - first strip / ignore the dashes in between) - E10247631234567891E0T32E4E7EB316 (correct the key - it cannot have a "T")
function hex2str($hex) {
for($i=0;$i<strlen($hex);$i+=2) $str .= chr(hexdec(substr($hex,$i,2)));
return $str;
}
$hexKey = hex2str($apiToken); //strip the dashes first
http://www.linux-support.com/cms/php-convert-hex-strings-to-ascii-strings/
So the method call now works :
$almostResult = hash_hmac ("sha1" , $stringToSign, $hexKey, true)
This returns a binary string - which you need to convert to base64 encoding.
$final = base64_encode ($almostResult)
That should do it...enjoy :)
I faced almost the same problem and after some googling i found this post:
https://www.reddit.com/r/PHP/comments/2k9tol/string_to_byte_array_using_utf8_encoding/
In PHP strings are already byte arrays. What is the specific problem you are having?
For me the solution was just base64_encode('apikey')