javascriptc#cryptojs

Convert code to generate hash from CryptoJS to C#


I want to convert this JS code to C# to use it in an ASP.NET app.

JavaScript code:

var to_md5 = "Some Text Here"
var hash = CryptoJS.SHA1(CryptoJS.MD5(to_md5.toUpperCase()).toString());
var result = CryptoJS.enc.Hex.stringify(hash);
console.log(result);

I tried the following in C#:

var to_md5 = "Some Text Here".ToUpper();
var hashString = gSHA1(gMD5(to_md5));

private string gMD5(string input)
{
    MD5 md5 = MD5.Create();
    Byte[] inputBytes = Encoding.ASCII.GetBytes(input);
    Byte[] hash = md5.ComputeHash(inputBytes);

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < hash.Length; i++)
    {
        sb.Append(hash[i].ToString("X2"));
    }
    return sb.ToString();
}

private string gSHA1(string s)
{
    Byte[] bytes = Encoding.UTF8.GetBytes(s);
    var sha1 = SHA1.Create();
    Byte[] hashBytes = sha1.ComputeHash(bytes);
    return HexStringFromBytes(hashBytes);
}

private string HexStringFromBytes(Byte[] bytes)
{
    var sb = new StringBuilder();
    foreach (byte b in bytes)
    {
        var hex = b.ToString("x2");
        sb.Append(hex);
    }
    return sb.ToString();
}

The results in both cases are different, what is wrong in my code?


Solution

  • There are numerous issues in the code. As already stated in the comments, having a well-known encoding is important, as well consistent casing. One can get away with that in many cases, but a hash function will produce unexpected results. Additionally, note that the .NET crypto classes usually implement IDisposable, so need to be instantiated alongside appropriate using statements (or cleaned up manually), if you don't want to risk memory/resource leaks.

    Finally, a note regarding correct use of crypto: MD5 and SHA1 are both no longer considered state-of-the-art, and should only be used for compatibility with legacy applications. Chaining them does not provide some magic security guarantee. Additionally, as mentioned before, chaining two hash functions with a flaky byte-hex-byte conversion in between is dangerous and may lead to unexpected errors or even security issues. It is better to stick to raw bytes arrays when passing around stuff between cryptographic primitives.

    If this is a new project and not a port of an old legacy application, I'd recommend to use a modern hash function like SHA2 or SHA3, and refrain from manual hash chaining, even if this is not security-related code.

    For the sake of completeness, the fixed program:

    using System.Security.Cryptography;
    using System.Text;
    
    var toMd5 = "Some Text Here".ToUpper();
    var hashString = ComputeSHA1(ComputeMD5(toMd5));
    Console.WriteLine(hashString);
    
    string ComputeMD5(string input)
    {
        using var md5 = MD5.Create();
        byte[] inputBytes = Encoding.UTF8.GetBytes(input);
        byte[] hash = md5.ComputeHash(inputBytes);
    
        return HexStringFromBytes(hash);
    }
    
    string ComputeSHA1(string input)
    {
        using var sha1 = SHA1.Create();
        byte[] inputBytes = Encoding.UTF8.GetBytes(input);
        byte[] hash = sha1.ComputeHash(inputBytes);
        
        return HexStringFromBytes(hash);
    }
    
    string HexStringFromBytes(byte[] bytes)
    {
        var sb = new StringBuilder();
        foreach (byte b in bytes) 
            sb.Append(b.ToString("x2"));
        
        return sb.ToString();
    }
    

    ...which outputs the same hash value as the JavaScript code:

    6a2b72d89102c35d34bd2981a3a863b18e647da8