How to convert a hexadecimal string to Uint8Array and back in JavaScript?

18,013

Solution 1

Vanilla JS:

const fromHexString = hexString =>
  new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));

const toHexString = bytes =>
  bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');

console.log(toHexString(new Uint8Array([0, 1, 2, 42, 100, 101, 102, 255])))
console.log(fromHexString('0001022a646566ff'))

Note: this method trusts its input. If the provided input has a length of 0, an error will be thrown. If the length of the hex-encoded buffer is not divisible by 2, the final byte will be parsed as if it were prepended with a 0 (e.g. aaa is interpreted as aa0a).

If the hex is potentially malformed or empty (e.g. user input), check its length and handle the error before calling this method, e.g.:

const missingLetter = 'abc';
if(missingLetter.length === 0 || missingLetter.length % 2 !== 0){
  throw new Error(`The string "${missingLetter}" is not valid hex.`)
}
fromHexString(missingLetter);

Source: the Libauth library (hexToBin method)

Solution 2

Node.js

For JavaScript running on Node you can do this:

const hexString = 'bada55';

const hex = Uint8Array.from(Buffer.from(hexString, 'hex'));

const backToHexString = Buffer.from(hex).toString('hex');

(source: this answer by @Teneff, shared with permission)

Solution 3

Here's a solution in native JavaScript:

var string = 'bada55';
var bytes = new Uint8Array(Math.ceil(string.length / 2));
for (var i = 0; i < bytes.length; i++) bytes[i] = parseInt(string.substr(i * 2, 2), 16);
console.log(bytes);

var convertedBack = '';
for (var i = 0; i < bytes.length; i++) {
  if (bytes[i] < 16) convertedBack += '0';
  convertedBack += bytes[i].toString(16);
}
console.log(convertedBack);

Solution 4

Browser (mimic NodeJS behavior acurately)

I had a need to mimic the NodeJS Buffer.from(x, 'hex') behavior in the browser environment and This is what I came up with.

It's pretty fast when I ran it through some benchmarks.

const HEX_STRINGS = "0123456789abcdef";
const MAP_HEX = {
  0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6,
  7: 7, 8: 8, 9: 9, a: 10, b: 11, c: 12, d: 13,
  e: 14, f: 15, A: 10, B: 11, C: 12, D: 13,
  E: 14, F: 15
};

// Fast Uint8Array to hex
function toHex(bytes) {
  return Array.from(bytes || [])
    .map((b) => HEX_STRINGS[b >> 4] + HEX_STRINGS[b & 15])
    .join("");
}

// Mimics Buffer.from(x, 'hex') logic
// Stops on first non-hex string and returns
// https://github.com/nodejs/node/blob/v14.18.1/src/string_bytes.cc#L246-L261
function fromHex(hexString) {
  const bytes = new Uint8Array(Math.floor((hexString || "").length / 2));
  let i;
  for (i = 0; i < bytes.length; i++) {
    const a = MAP_HEX[hexString[i * 2]];
    const b = MAP_HEX[hexString[i * 2 + 1]];
    if (a === undefined || b === undefined) {
      break;
    }
    bytes[i] = (a << 4) | b;
  }
  return i === bytes.length ? bytes : bytes.slice(0, i);
}
Share:
18,013
David Braun
Author by

David Braun

David is a full stack software engineer experienced as an entrepreneur in Silicon Valley. With two years of management training and strong communication skills, he excels at bridging business requirements with modern technology. His EECS degree from U. C. Berkeley is evidence of his technical depth.

Updated on June 03, 2022

Comments

  • David Braun
    David Braun almost 2 years

    I want to convert a hexadecimal string like bada55 into a Uint8Array and back again.