Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalent version of SHA256 ComputeHash (from C#) for React Native/JS

Tags:

I'm trying to build an equivalent version of SHA256 ComputeHash (from C#, the EXACT same output from the below sample), to React Native/JavaScript. This is the following C#:

public static string Hash(string input)
{
    if (string.IsNullOrWhiteSpace(input)) return "";

    using (SHA256 hasher = SHA256.Create())
    {
        // Convert the input string to a byte array and compute the hash.
        byte[] data = hasher.ComputeHash(Encoding.Unicode.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 $"0x{sBuilder.ToString().ToLower()}";
    }
}

I tried the following, but it doesn't generate the same Hash:

import * as Crypto from 'expo-crypto';

const hash = await Crypto.digestStringAsync(
    Crypto.CryptoDigestAlgorithm.SHA256,
    "StringIWantToHash"
);

Would anyone know, either what's wrong with the JavaScript, or if there is an exact equivalent version of the C# one?

like image 819
Kevin Jensen Petersen Avatar asked May 30 '20 16:05

Kevin Jensen Petersen


2 Answers

Expo React Native:

Solution 1: install sha256 by

yarn add sha256
import React, { Component } from "react";
import { Text, StyleSheet, View } from "react-native";
const sha256 = require("sha256");

const isNullOrWhitespace = (input) => {
  if (typeof input === "undefined" || input == null) return true;
  return input.replace(/\s/g, "").length < 1;
};

const getByteArray = (input) => {
  let bytes = [];
  for (var i = 0, k = 0; i < input.length; i++, k += 2) {
    bytes[k] = input.charCodeAt(i);
    bytes[k + 1] = 0;
  }
  return bytes;
};


const hash = async (input) => {
  if (isNullOrWhitespace(input)) {
    return "";
  }
  var bytes = getByteArray(input);
  const hashString = "0x" + sha256(bytes, { asBytes: false });
  return hashString;
};

export default class App extends Component {
  state = {
    encodedString: "",
  };
  async UNSAFE_componentWillMount() {
    const encodedString = await hash("test2"); //0x39a2272982dc7e6e5d109ab36ec280f6cd3b4b7440af5c739ed808d4ec02aae4
    this.setState({ encodedString: encodedString });
  }
  render() {
    const { encodedString } = this.state;
    return (
      <View style={styles.container}>
        <Text>{encodedString}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
});

Solution 2:

you are using ToString("X2") in c# which means you have to convert hash to HEX (base 16)

here id demo: https://snack.expo.io/@nomi9995/expo-crypto

you need to convert hash to HEX (base 16) like this

await hash.toString(16);

try this it will give the same result as c#

Code :

  import React, { Component } from "react";
  import { Text, StyleSheet, View } from "react-native";

  import * as Crypto from "expo-crypto";

  const isNullOrWhitespace = (input) => {
    if (typeof input === "undefined" || input == null) return true;
    return input.replace(/\s/g, "").length < 1;
  };

  const hash = async (input) => {
    if (isNullOrWhitespace(input)) {
      return "";
    }
    let hash = await Crypto.digestStringAsync(
      Crypto.CryptoDigestAlgorithm.SHA256,
      input
    );
    const sBuilder = await hash.toString(16);

    return `0x${sBuilder.toLowerCase()}`;
  };

  export default class App extends Component {
    state = {
      encodedString: "",
    };
    async UNSAFE_componentWillMount() {
      const result = await hash("StringIWantToHash"); //here you can pass your string
      this.setState({ encodedString: result });
    }
    render() {
      const { encodedString } = this.state;
      return (
        <View style={styles.container}>
          <Text>{encodedString}</Text>
        </View>
      );
    }
  }

  const styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: "center",
      alignItems: "center",
    },
  });

Node js:

install crypto-js by

yarn add crypto-js

try this it will give the same result as c#

var CryptoJS = require("crypto-js");

const isNullOrWhitespace = (input) => {
  if (typeof input === "undefined" || input == null) return true;
  return input.replace(/\s/g, "").length < 1;
};

const hash = (input) => {
  if (isNullOrWhitespace(input)) {
    return "";
  }
  let hash = CryptoJS.SHA256(input);
  const sBuilder=hash.toString(CryptoJS.enc.Hex);

  return `0x${sBuilder.toLowerCase()}`;
};


const result=hash("StringIWantToHash");
console.log(result,"result"); // it will give the same result as C#

enter image description here

like image 167
Muhammad Numan Avatar answered Sep 21 '22 11:09

Muhammad Numan


Solution 1: Use UTF8 instead of Unicode

Well, this is an Encoding problem.

Encoding.Unicode is Microsoft's misleading name for UTF-16 (a double-wide encoding, used in the Windows world for historical reasons but not used by anyone else). http://msdn.microsoft.com/en-us/library/system.text.encoding.unicode.aspx (see this answer)

You should be using Encoding.UTF8.GetBytes instead.

Using js-sha256 library like this:

const jssha = require('js-sha256')

function hash(input)
{
    const hashString = "0x" + jssha.sha256(input)
    return hashString;
}

const hashResult = hash("StringIWantToHash")
// Output: 0x29c506d0d69a16e413d63921b7de79525c43715931d8d93127dbeb46eacda2f9

We can achieve pretty similar in C# just using UTF8 encoding:

public static string Hash(string input)
{
    if (string.IsNullOrWhiteSpace(input)) return "";

    using (SHA256 hasher = SHA256.Create())
    {
        // Convert the input string to a byte array and compute the hash.
        byte[] data = hasher.ComputeHash(Encoding.UTF8.GetBytes(input)); // Note that UTF8 here

        // 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 $"0x{sBuilder.ToString().ToLower()}"; 
    }
}

static void Main()
{
    var hashResult = Hash("StringIWantToHash");
    // Output: 0x29c506d0d69a16e413d63921b7de79525c43715931d8d93127dbeb46eacda2f9
}

Also, I believe that other JS/React Native libraries that help to compute SHA256 hash use UTF8 encoding too, so I think you can use any other crypto libraries for that.

Solution 2: What if you need to use Unicode?

In that case, you need to manually represent C# encoding in JS code.
While you are using Unicode encoding, after every single byte in the string goes '0' byte if it is a plain Latin character. For other symbols (with more than 255 number) Unicode required 2 bytes for that.

var input = "StringIWantToHash";
var encodedInput = Encoding.Unicode.GetBytes(input);
// Output: [83, 0, 116, 0, 114, 0, 105, 0, 110, 0, ...]

So we need to represent that in our JS code:

const jssha = require('js-sha256')

function hash(input)
{
    var bytes = [];
    for (var i = 0; i < input.length; i++)
    {
        const code = input.charCodeAt(i);
        bytes = bytes.concat([code & 0xff, code / 256 >>> 0]);
    }

    const hashString = "0x" + jssha.sha256(bytes)
    return hashString;
}

const hashResult = hash("StringIWantToHash")
// Output: 0x029dbc4b54b39bed6d684175b2d76cc5622c60fe91f0bde9865b977d0d9a531d
like image 29
picolino Avatar answered Sep 22 '22 11:09

picolino