Let's assume I have the following in NodeJS:
import {
gzip
} from "node-gzip";
const samlToken = fs.readFileSync(
"./localDevToken.txt",
"utf8",
);
const bufferSamlToken = Buffer.from(
samlToken.replace("\r\n", ""),
"utf8",
);
const gZipToken = await gzip(bufferSamlToken);
localDevToken = gZipToken
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
And I want to do the same in the frontend. How can I achieve it ?
This is what I've tried using the Pako library from https://github.com/nodeca/pako
function convertSamlToken(input) {
var samlToken = input.replace("\r\n", "");
samlToken = pako.gzip(samlToken, {
to: 'string'
});
samlToken = btoa(samlToken)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
return samlToken;
}
But the output is different. What is wrong ?
You are using pako incorrectly, there is no {to: 'string'} option and so the output is a Uint8Array. Here is how your function should look:
function convertSamlToken(input) {
const samlToken = input.replace("\r\n", "");
const bytes = pako.gzip(samlToken);
// Convert Uint8Array to base64 in a safe way
// See https://stackoverflow.com/a/9458996/7448536
let binary = "";
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
}
I've tested this function on random UTF-8 data and it produces the exact same output except for the 10th byte since that is the OS ID. This is because node-gzip sets it to 0x0A/TOPS-20 and pako uses 0x03/Unix. If that is a problem then just add bytes[9] = 0x0a; after line 3.
Related
I'm trying to add Spotify auth to my single page react application following the doc from their api.
So far this is how I generate the codes based on solutions I found online:
const generateVerifier = () => {
return crypto.randomBytes(64).toString('hex');
}
const getChallenge = verifier => {
return crypto.createHash('sha256')
.update(verifier)
.digest('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')
}
An example of a pair of codes I created using that technique:
verifier: e8c3745e93a9c25ce5c2653ee36f5b4fa010b4f4df8dfbad7055f4d88551dd960fb5b7602cdfa61088951eac36429862946e86d20b15250a8f0159f1ad001605
challenge: CxF5ZvoXa6Cz6IcX3VyRHxMPRXYbv4PADxko3dwPF-I
An example of an old pair of codes I created:
verifier: 1jp6ku6-16xxjfi-1uteidc-9gjfso-1mcc0wn-tju0lh-tr2d8k-1auq4zk
challenge: SRvuz5GW2HhXzHs6b3O_wzJq4sWN0W2ma96QBx_Z77s
I then get a response from the API saying "code_verifier was incorrect." What am I doing wrong here?
Try following this guide for generating code for generating code challenge and verifier
Here are the important parts:
Generate Code Verifier
// GENERATING CODE VERIFIER
function dec2hex(dec) {
return ("0" + dec.toString(16)).substr(-2);
}
function generateCodeVerifier() {
var array = new Uint32Array(56 / 2);
window.crypto.getRandomValues(array);
return Array.from(array, dec2hex).join("");
}
Generate code challenge from code verifier
function sha256(plain) {
// returns promise ArrayBuffer
const encoder = new TextEncoder();
const data = encoder.encode(plain);
return window.crypto.subtle.digest("SHA-256", data);
}
function base64urlencode(a) {
var str = "";
var bytes = new Uint8Array(a);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
str += String.fromCharCode(bytes[i]);
}
return btoa(str)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
async function generateCodeChallengeFromVerifier(v) {
var hashed = await sha256(v);
var base64encoded = base64urlencode(hashed);
return base64encoded;
}
Here's a working example
You can also check the validity of the codes here
I took this snippet from the passport oauth2 library to generate code verifier and code challenge.
const code_verifier = base64url(crypto.pseudoRandomBytes(32));
const code_challenge = crypto
.createHash("sha256")
.update(code_verifier)
.digest();
Fully working and verified example:
const {randomBytes, createHash} = require("node:crypto");
// OR: import {randomBytes, createHash} from "crypto";
function generatePKCEPair() {
const NUM_OF_BYTES = 22; // Total of 44 characters (1 Bytes = 2 char) (standard states that: 43 chars <= verifier <= 128 chars)
const HASH_ALG = "sha256";
const randomVerifier = randomBytes(NUM_OF_BYTES).toString('hex')
const hash = createHash(HASH_ALG).update(randomVerifier).digest('base64');
const challenge = hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); // Clean base64 to make it URL safe
return {verifier: randomVerifier, challenge}
}
Run example:
generatePKCEPair();
// Result:
{
verifier: '3e2727957a1bd9f47b11ff347fca362b6060941decb4',
challange: '1SF5UEwYplIjmAwHUwcitzp9qz8zv98uYflt-tBmwLc'
}
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?
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#
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
after two days of research it works perfectly! Two different codes give the same result.
js
const sha1 = require('sha1');
const getHash = str =>{
const hashingBytes = Buffer.from(sha1(str), "hex");
const base64Value = Buffer.from(hashingBytes).toString('base64');
return base64Value;
}
c#
System.Security.Cryptography.SHA1 sha = new System.Security.Cryptography.SHA1CryptoServiceProvider();
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(str);
byte[] hashingbytes = sha.ComputeHash(bytes);
var hash = Convert.ToBase64String(hashingbytes);
How to convert from Hex string to ASCII string in JavaScript?
Ex:
32343630 it will be 2460
function hex2a(hexx) {
var hex = hexx.toString();//force conversion
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
hex2a('32343630'); // returns '2460'
Another way to do it (if you use Node.js):
var input = '32343630';
const output = Buffer.from(input, 'hex');
log(input + " -> " + output); // Result: 32343630 -> 2460
For completeness sake the reverse function:
function a2hex(str) {
var arr = [];
for (var i = 0, l = str.length; i < l; i ++) {
var hex = Number(str.charCodeAt(i)).toString(16);
arr.push(hex);
}
return arr.join('');
}
a2hex('2460'); //returns 32343630
You can use this..
var asciiVal = "32343630".match(/.{1,2}/g).map(function(v){
return String.fromCharCode(parseInt(v, 16));
}).join('');
document.write(asciiVal);
** for Hexa to String**
let input = '32343630';
Note : let output = new Buffer(input, 'hex'); // this is deprecated
let buf = Buffer.from(input, "hex");
let data = buf.toString("utf8");
I found a useful function present in web3 library.
var hexString = "0x1231ac"
string strValue = web3.toAscii(hexString)
Update: Newer version of web3 has this function in utils
The functions now resides in utils:
var hexString = "0x1231ac"
string strValue = web3.utils.hexToAscii(hexString)
I've found that the above solution will not work if you have to deal with control characters like 02 (STX) or 03 (ETX), anything under 10 will be read as a single digit and throw off everything after. I ran into this problem trying to parse through serial communications. So, I first took the hex string received and put it in a buffer object then converted the hex string into an array of the strings like so:
buf = Buffer.from(data, 'hex');
l = Buffer.byteLength(buf,'hex');
for (i=0; i<l; i++){
char = buf.toString('hex', i, i+1);
msgArray.push(char);
}
Then .join it
message = msgArray.join('');
then I created a hexToAscii function just like in #Delan Azabani's answer above...
function hexToAscii(str){
hexString = str;
strOut = '';
for (x = 0; x < hexString.length; x += 2) {
strOut += String.fromCharCode(parseInt(hexString.substr(x, 2), 16));
}
return strOut;
}
then called the hexToAscii function on 'message'
message = hexToAscii(message);
This approach also allowed me to iterate through the array and slice into the different parts of the transmission using the control characters so I could then deal with only the part of the data I wanted.
Hope this helps someone else!
console.log(
"68656c6c6f20776f726c6421".match(/.{1,2}/g).reduce((acc,char)=>acc+String.fromCharCode(parseInt(char, 16)),"")
)
An optimized version of the implementation of the reverse function proposed by #michieljoris (according to the comments of #Beterraba and #Mala):
function a2hex(str) {
var hex = '';
for (var i = 0, l = str.length; i < l; i++) {
var hexx = Number(str.charCodeAt(i)).toString(16);
hex += (hexx.length > 1 && hexx || '0' + hexx);
}
return hex;
}
alert(a2hex('2460')); // display 32343630
I use this one, it seems more clear to me as I also receive data with spaces like '30 31 38 30 38 30' and the output is 018080
hexToString(hex: string): string {
return hex.split(' ').map(s => string.fromCharCode(parseInt(s,16))).join('');
}
For example, I have a string "this=20is=20a=20string" that I want to convert to "this is a string".
Use mimelib:
var mimelib = require("mimelib");
mimelib.decodeQuotedPrintable("this=20is=20a=20string") === "this is a string"
mimelib.decodeMimeWord("=?iso-8859-1?Q?=27text=27?=") === "'text'"
function decodeQuotedPrintable(data)
{
// normalise end-of-line signals
data = data.replace(/(\r\n|\n|\r)/g, "\n");
// replace equals sign at end-of-line with nothing
data = data.replace(/=\n/g, "");
// encoded text might contain percent signs
// decode each section separately
let bits = data.split("%");
for (let i = 0; i < bits.length; i ++)
{
// replace equals sign with percent sign
bits[i] = bits[i].replace(/=/g, "%");
// decode the section
bits[i] = decodeURIComponent(bits[i]);
}
// join the sections back together
return(bits.join("%"));
}
The quoted-printable package can be used for quoted printable encoding and decoding.
> var utf8 = require('utf8')
undefined
> var quotedPrintable = require('quoted-printable');
undefined
> var s = 'this=20is=20a=20string'
undefined
> utf8.decode(quotedPrintable.decode(s))
'this is a string'
> quotedPrintable.encode(utf8.encode('this is a string'))
'this is a string'
Here a variant where you can specify the charset:
function decodeQuotedPrintable(raw, charset='utf-8') {
const dc = new TextDecoder(charset);
return raw.replace(/[\t\x20]$/gm, "").replace(/=(?:\r\n?|\n)/g, "").replace(/((?:=[a-fA-F0-9]{2})+)/g, (m) => {
const cd = m.substring(1).split('='), uArr=new Uint8Array(cd.length);
for (let i = 0; i < cd.length; i++) {
uArr[i] = parseInt(cd[i], 16);
}
return dc.decode(uArr);
});
}
console.log(decodeQuotedPrintable('Freundliche Gr=C3=BCsse')); // "Freundliche Grüsse"
console.log(decodeQuotedPrintable('I love =F0=9F=8D=95')); // "I love 🍕"
console.log(decodeQuotedPrintable('Freundliche Gr=FCsse', 'ISO-8859-1')); // "Freundliche Grüsse"
console.log(decodeQuotedPrintable('Freundliche Gr=9Fsse', 'macintosh')); // "Freundliche Grüsse"
How to convert from Hex string to ASCII string in JavaScript?
Ex:
32343630 it will be 2460
function hex2a(hexx) {
var hex = hexx.toString();//force conversion
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
hex2a('32343630'); // returns '2460'
Another way to do it (if you use Node.js):
var input = '32343630';
const output = Buffer.from(input, 'hex');
log(input + " -> " + output); // Result: 32343630 -> 2460
For completeness sake the reverse function:
function a2hex(str) {
var arr = [];
for (var i = 0, l = str.length; i < l; i ++) {
var hex = Number(str.charCodeAt(i)).toString(16);
arr.push(hex);
}
return arr.join('');
}
a2hex('2460'); //returns 32343630
You can use this..
var asciiVal = "32343630".match(/.{1,2}/g).map(function(v){
return String.fromCharCode(parseInt(v, 16));
}).join('');
document.write(asciiVal);
** for Hexa to String**
let input = '32343630';
Note : let output = new Buffer(input, 'hex'); // this is deprecated
let buf = Buffer.from(input, "hex");
let data = buf.toString("utf8");
I found a useful function present in web3 library.
var hexString = "0x1231ac"
string strValue = web3.toAscii(hexString)
Update: Newer version of web3 has this function in utils
The functions now resides in utils:
var hexString = "0x1231ac"
string strValue = web3.utils.hexToAscii(hexString)
I've found that the above solution will not work if you have to deal with control characters like 02 (STX) or 03 (ETX), anything under 10 will be read as a single digit and throw off everything after. I ran into this problem trying to parse through serial communications. So, I first took the hex string received and put it in a buffer object then converted the hex string into an array of the strings like so:
buf = Buffer.from(data, 'hex');
l = Buffer.byteLength(buf,'hex');
for (i=0; i<l; i++){
char = buf.toString('hex', i, i+1);
msgArray.push(char);
}
Then .join it
message = msgArray.join('');
then I created a hexToAscii function just like in #Delan Azabani's answer above...
function hexToAscii(str){
hexString = str;
strOut = '';
for (x = 0; x < hexString.length; x += 2) {
strOut += String.fromCharCode(parseInt(hexString.substr(x, 2), 16));
}
return strOut;
}
then called the hexToAscii function on 'message'
message = hexToAscii(message);
This approach also allowed me to iterate through the array and slice into the different parts of the transmission using the control characters so I could then deal with only the part of the data I wanted.
Hope this helps someone else!
console.log(
"68656c6c6f20776f726c6421".match(/.{1,2}/g).reduce((acc,char)=>acc+String.fromCharCode(parseInt(char, 16)),"")
)
An optimized version of the implementation of the reverse function proposed by #michieljoris (according to the comments of #Beterraba and #Mala):
function a2hex(str) {
var hex = '';
for (var i = 0, l = str.length; i < l; i++) {
var hexx = Number(str.charCodeAt(i)).toString(16);
hex += (hexx.length > 1 && hexx || '0' + hexx);
}
return hex;
}
alert(a2hex('2460')); // display 32343630
I use this one, it seems more clear to me as I also receive data with spaces like '30 31 38 30 38 30' and the output is 018080
hexToString(hex: string): string {
return hex.split(' ').map(s => string.fromCharCode(parseInt(s,16))).join('');
}