Menu Close

Calculate the SHA-256 hash of a string in JavaScript without library

The SHA-256 (Secure Hash Algorithm 256) is a widely-used cryptographic hash function that produces a fixed-size output for any given input. It is commonly used to verify the integrity of data. In this post, we will learn how to implement the SHA-256 hash function in JavaScript without using any external libraries.

function sha256(string) {
  // Initialize the SHA-256 hash
  var hash = new Uint32Array(8);
  hash[0] = 0x6a09e667;
  hash[1] = 0xbb67ae85;
  hash[2] = 0x3c6ef372;
  hash[3] = 0xa54ff53a;
  hash[4] = 0x510e527f;
  hash[5] = 0x9b05688c;
  hash[6] = 0x1f83d9ab;
  hash[7] = 0x5be0cd19;

  // Convert the string to a byte array
  var stringBytes = toUTF8Bytes(string);

  // Pad the byte array to a multiple of 64 bytes
  var paddedBytes = padToMultipleOf(stringBytes, 64);

  // Process the padded byte array in blocks of 64 bytes
  for (var i = 0; i < paddedBytes.length; i += 64) {
    processBlock(paddedBytes.slice(i, i + 64), hash);
  }

  // Return the final hash as a hexadecimal string
  return toHexString(hash);
}

The hexadecimal values 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, and 0x5be0cd19 are the initial values of the eight 32-bit words that are used in the SHA-256 algorithm. These values are defined in the SHA-2 standard and are used as the starting state of the hash calculation. They are commonly referred to as the “initial hash values” or the “initial digest”.

This function calculates the SHA-256 hash of a given string by first initializing the hash with the default initial values, then converting the string to a byte array, padding the byte array to a multiple of 64 bytes, and finally processing the padded byte array in blocks of 64 bytes.

The toUTF8Bytes, padToMultipleOf, processBlock, and toHexString functions are helper functions that are used to convert the string to a byte array, pad the byte array, process the blocks of bytes, and convert the final hash to a hexadecimal string, respectively.

Here are the implementations of these helper functions:

function toUTF8Bytes(str) {
  var bytes = [];
  for (var i = 0; i < str.length; i++) {
    var codePoint = str.charCodeAt(i);
    if (codePoint < 0x80) {
      bytes.push(codePoint);
    } else if (codePoint < 0x800) {
      bytes.push(0xc0 | codePoint >> 6);
      bytes.push(0x80 | codePoint & 0x3f);
    } else if (codePoint < 0x10000) {
      bytes.push(0xe0 | codePoint >> 12);
      bytes.push(0x80 | codePoint >> 6 & 0x3f);
      bytes.push(0x80 | codePoint & 0x3f);
    } else {
      bytes.push(0xf0 | codePoint >> 18);
      bytes.push(0x80 | codePoint >> 12 & 0x3f);
      bytes.push(0x80 | codePoint >> 6 & 0x3f);
      bytes.push(0x80 | codePoint & 0x3f);
    }
  }
  return bytes;
}

This function converts the given string to a byte array encoded in UTF-8 by iterating over the string and converting each character to a code point using the charCodeAt method. It then encodes the code point as a sequence of bytes in the byte array, depending on the value of the code point. If the code point is less than 0x80, it is encoded as a single byte. If the code point is between 0x80 and 0x800, it is encoded as two bytes using the UTF-8 two-byte sequence. If the code point is between 0x800 and 0x10000, it is encoded as three bytes using the UTF-8 three-byte sequence. Otherwise, it is encoded as four bytes using the UTF-8 four-byte sequence. Finally, the function returns the resulting byte array.

Here is the complete implementation of the padToMultipleOf and processBlock

function padToMultipleOf(bytes, multiple) {
  var padding = bytes.length % multiple;
  if (padding > 0) {
    padding = multiple - padding;
  }
  for (var i = 0; i < padding; i++) {
    bytes.push(i === 0 ? 0x80 : 0x00);
  }
  return bytes;
}

function processBlock(bytes, hash) {
  // Initialize the word array
  var words = new Uint32Array(64);
  for (var i = 0; i < 64; i++) {
    words[i] = bytes[i * 4] << 24 | bytes[i * 4 + 1] << 16 | bytes[i * 4 + 2] << 8 | bytes[i * 4 + 3];
  }

  // Initialize the working variables
  var a = hash[0];
  var b = hash[1];
  var c = hash[2];
  var d = hash[3];
  var e = hash[4];
  var f = hash[5];
  var g = hash[6];
  var h = hash[7];

  // Process the words in the block
  for (var i = 0; i < 64; i++) {
    var s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
    var maj = (a & b) ^ (a & c) ^ (b & c);
    var t2 = s0 + maj;
    var s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
    var ch = (e & f) ^ (~e & g);
    var t1 = h + s1 + ch + K[i] + words[i];

    h = g;
    g = f;
    f = e;
    e = d + t1;
    d = c;
    c = b;
    b = a;
    a = t1 + t2;
  }

  // Update the hash with the final values of the working variables
  hash[0] += a;
  hash[1] += b;
  hash[2] += c;
  hash[3] += d;
  hash[4] += e;
  hash[5] += f;
  hash[6] += g;
  hash[7] += h;
}

The padToMultipleOf function pads the given byte array with additional bytes so that its length is a multiple of the specified multiple. It first calculates the amount of padding that is required by taking the modulus of the length of the byte array and the specified multiple. If the padding is non-zero, it adds the required padding to the end of the byte array. The first padding byte is set to 0x80 and the rest of the padding bytes are set to 0x00. Finally, it returns the padded byte array.

function padToMultipleOf(bytes, multiple) {
  var padding = bytes.length % multiple;
  if (padding > 0) {
    padding = multiple - padding;
  }
  for (var i = 0; i < padding; i++) {
    bytes.push(i === 0 ? 0x80 : 0x00);
  }
  return bytes;
}

implementation of the toHexString helper function

function toHexString(hash) {
  var hex = "";
  for (var i = 0; i < hash.length; i++) {
    hex += (hash[i] >>> 0).toString(16);
  }
  return hex;
}

The toHexString function converts the given hash (which is an array of 32-bit unsigned integers) to a hexadecimal string by iterating over the array and converting each element to a hexadecimal value using the toString method.

Here is an example of how the sha256 function can be used to calculate the SHA-256 hash of a given string:

var hash = sha256("Hello, world!");
// The value of "hash" is now "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"

Leave a Reply

Your email address will not be published. Required fields are marked *