Home Reference Source Test

src/hasher/md5.mjs

'use strict';

import Hasher32le from "./hasher32le";
import {rotateLeft} from "../tools/tools";

// Transform constants
/** @type {number[]} */
const S = [
  [7, 12, 17, 22],
  [5, 9, 14, 20],
  [4, 11, 16, 23],
  [6, 10, 15, 21]
];
/** @type {number[]} */
const T = new Array(64);
for (let i = 0; i < 64; i++) {
  T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0;
}

/**
 * Calculates [MD5](https://tools.ietf.org/html/rfc1321) hash
 *
 * @example <caption>Calculates MD5 hash from string "message" - ES6 style</caption>
 * import Md5 from "crypto-api/src/hasher/md5";
 * import {toHex} from "crypto-api/src/encoder/hex";
 *
 * let hasher = new Md5();
 * hasher.update('message');
 * console.log(toHex(hasher.finalize()));
 *
 * @example <caption>Calculates MD5 hash from UTF string "message" - ES6 style</caption>
 * import Md5 from "crypto-api/src/hasher/md5";
 * import {toHex} from "crypto-api/src/encoder/hex";
 * import {fromUtf} from "crypto-api/src/encoder/utf";
 *
 * let hasher = new Md5();
 * hasher.update(fromUtf('message'));
 * console.log(toHex(hasher.finalize()));
 *
 * @example <caption>Calculates MD5 hash from string "message" - ES5 style</caption>
 * <script src="https://nf404.github.io/crypto-api/crypto-api.min.js"></script>
 * <script>
 *   var hasher = CryptoApi.getHasher('md5');
 *   hasher.update('message');
 *   console.log(CryptoApi.encoder.toHex(hasher.finalize()));
 * </script>
 *
 * @example <caption>Calculates MD5 hash from UTF string "message" - ES5 style</caption>
 * <script src="https://nf404.github.io/crypto-api/crypto-api.min.js"></script>
 * <script>
 *   console.log(CryptoApi.hash('md5', 'message'));
 * </script>
 */
class Md5 extends Hasher32le {
  /**
   * Reset hasher to initial state
   */
  reset() {
    super.reset();
    this.state.hash = [
      0x67452301 | 0,
      0xefcdab89 | 0,
      0x98badcfe | 0,
      0x10325476 | 0
    ];
  }

  /**
   * @protected
   * @ignore
   * @param {number} x
   * @param {number} y
   * @param {number} z
   * @returns {number}
   */
  static FF(x, y, z) {
    return (x & y) | (~x & z);
  }

  /**
   * @protected
   * @ignore
   * @param {number} x
   * @param {number} y
   * @param {number} z
   * @returns {number}
   */
  static GG(x, y, z) {
    return (x & z) | (y & ~z);
  }

  /**
   * @protected
   * @ignore
   * @param {number} x
   * @param {number} y
   * @param {number} z
   * @returns {number}
   */
  static HH(x, y, z) {
    return x ^ y ^ z;
  }

  /**
   * @protected
   * @ignore
   * @param {number} x
   * @param {number} y
   * @param {number} z
   * @returns {number}
   */
  static II(x, y, z) {
    return y ^ (x | ~z);
  }

  /**
   * @protected
   * @ignore
   * @param {function} f
   * @param {number} k
   * @param {number} a
   * @param {number} x
   * @param {number} y
   * @param {number} z
   * @param {number} m
   * @param {number} s
   * @returns {number}
   */
  static CC(f, k, a, x, y, z, m, s) {
    return (rotateLeft((a + f(x, y, z) + m + k), s) + x) | 0;
  }

  /**
   * Process ready blocks
   *
   * @protected
   * @ignore
   * @param {number[]} block - Block
   */
  processBlock(block) {
    // Working variables
    let a = this.state.hash[0] | 0;
    let b = this.state.hash[1] | 0;
    let c = this.state.hash[2] | 0;
    let d = this.state.hash[3] | 0;

    // Round 1
    a = Md5.CC(Md5.FF, T[0], a, b, c, d, block[0], S[0][0]);
    d = Md5.CC(Md5.FF, T[1], d, a, b, c, block[1], S[0][1]);
    c = Md5.CC(Md5.FF, T[2], c, d, a, b, block[2], S[0][2]);
    b = Md5.CC(Md5.FF, T[3], b, c, d, a, block[3], S[0][3]);
    a = Md5.CC(Md5.FF, T[4], a, b, c, d, block[4], S[0][0]);
    d = Md5.CC(Md5.FF, T[5], d, a, b, c, block[5], S[0][1]);
    c = Md5.CC(Md5.FF, T[6], c, d, a, b, block[6], S[0][2]);
    b = Md5.CC(Md5.FF, T[7], b, c, d, a, block[7], S[0][3]);
    a = Md5.CC(Md5.FF, T[8], a, b, c, d, block[8], S[0][0]);
    d = Md5.CC(Md5.FF, T[9], d, a, b, c, block[9], S[0][1]);
    c = Md5.CC(Md5.FF, T[10], c, d, a, b, block[10], S[0][2]);
    b = Md5.CC(Md5.FF, T[11], b, c, d, a, block[11], S[0][3]);
    a = Md5.CC(Md5.FF, T[12], a, b, c, d, block[12], S[0][0]);
    d = Md5.CC(Md5.FF, T[13], d, a, b, c, block[13], S[0][1]);
    c = Md5.CC(Md5.FF, T[14], c, d, a, b, block[14], S[0][2]);
    b = Md5.CC(Md5.FF, T[15], b, c, d, a, block[15], S[0][3]);

    // Round 2
    a = Md5.CC(Md5.GG, T[16], a, b, c, d, block[1], S[1][0]);
    d = Md5.CC(Md5.GG, T[17], d, a, b, c, block[6], S[1][1]);
    c = Md5.CC(Md5.GG, T[18], c, d, a, b, block[11], S[1][2]);
    b = Md5.CC(Md5.GG, T[19], b, c, d, a, block[0], S[1][3]);
    a = Md5.CC(Md5.GG, T[20], a, b, c, d, block[5], S[1][0]);
    d = Md5.CC(Md5.GG, T[21], d, a, b, c, block[10], S[1][1]);
    c = Md5.CC(Md5.GG, T[22], c, d, a, b, block[15], S[1][2]);
    b = Md5.CC(Md5.GG, T[23], b, c, d, a, block[4], S[1][3]);
    a = Md5.CC(Md5.GG, T[24], a, b, c, d, block[9], S[1][0]);
    d = Md5.CC(Md5.GG, T[25], d, a, b, c, block[14], S[1][1]);
    c = Md5.CC(Md5.GG, T[26], c, d, a, b, block[3], S[1][2]);
    b = Md5.CC(Md5.GG, T[27], b, c, d, a, block[8], S[1][3]);
    a = Md5.CC(Md5.GG, T[28], a, b, c, d, block[13], S[1][0]);
    d = Md5.CC(Md5.GG, T[29], d, a, b, c, block[2], S[1][1]);
    c = Md5.CC(Md5.GG, T[30], c, d, a, b, block[7], S[1][2]);
    b = Md5.CC(Md5.GG, T[31], b, c, d, a, block[12], S[1][3]);

    // Round 3
    a = Md5.CC(Md5.HH, T[32], a, b, c, d, block[5], S[2][0]);
    d = Md5.CC(Md5.HH, T[33], d, a, b, c, block[8], S[2][1]);
    c = Md5.CC(Md5.HH, T[34], c, d, a, b, block[11], S[2][2]);
    b = Md5.CC(Md5.HH, T[35], b, c, d, a, block[14], S[2][3]);
    a = Md5.CC(Md5.HH, T[36], a, b, c, d, block[1], S[2][0]);
    d = Md5.CC(Md5.HH, T[37], d, a, b, c, block[4], S[2][1]);
    c = Md5.CC(Md5.HH, T[38], c, d, a, b, block[7], S[2][2]);
    b = Md5.CC(Md5.HH, T[39], b, c, d, a, block[10], S[2][3]);
    a = Md5.CC(Md5.HH, T[40], a, b, c, d, block[13], S[2][0]);
    d = Md5.CC(Md5.HH, T[41], d, a, b, c, block[0], S[2][1]);
    c = Md5.CC(Md5.HH, T[42], c, d, a, b, block[3], S[2][2]);
    b = Md5.CC(Md5.HH, T[43], b, c, d, a, block[6], S[2][3]);
    a = Md5.CC(Md5.HH, T[44], a, b, c, d, block[9], S[2][0]);
    d = Md5.CC(Md5.HH, T[45], d, a, b, c, block[12], S[2][1]);
    c = Md5.CC(Md5.HH, T[46], c, d, a, b, block[15], S[2][2]);
    b = Md5.CC(Md5.HH, T[47], b, c, d, a, block[2], S[2][3]);

    // Round 4
    a = Md5.CC(Md5.II, T[48], a, b, c, d, block[0], S[3][0]);
    d = Md5.CC(Md5.II, T[49], d, a, b, c, block[7], S[3][1]);
    c = Md5.CC(Md5.II, T[50], c, d, a, b, block[14], S[3][2]);
    b = Md5.CC(Md5.II, T[51], b, c, d, a, block[5], S[3][3]);
    a = Md5.CC(Md5.II, T[52], a, b, c, d, block[12], S[3][0]);
    d = Md5.CC(Md5.II, T[53], d, a, b, c, block[3], S[3][1]);
    c = Md5.CC(Md5.II, T[54], c, d, a, b, block[10], S[3][2]);
    b = Md5.CC(Md5.II, T[55], b, c, d, a, block[1], S[3][3]);
    a = Md5.CC(Md5.II, T[56], a, b, c, d, block[8], S[3][0]);
    d = Md5.CC(Md5.II, T[57], d, a, b, c, block[15], S[3][1]);
    c = Md5.CC(Md5.II, T[58], c, d, a, b, block[6], S[3][2]);
    b = Md5.CC(Md5.II, T[59], b, c, d, a, block[13], S[3][3]);
    a = Md5.CC(Md5.II, T[60], a, b, c, d, block[4], S[3][0]);
    d = Md5.CC(Md5.II, T[61], d, a, b, c, block[11], S[3][1]);
    c = Md5.CC(Md5.II, T[62], c, d, a, b, block[2], S[3][2]);
    b = Md5.CC(Md5.II, T[63], b, c, d, a, block[9], S[3][3]);

    this.state.hash[0] = (this.state.hash[0] + a) | 0;
    this.state.hash[1] = (this.state.hash[1] + b) | 0;
    this.state.hash[2] = (this.state.hash[2] + c) | 0;
    this.state.hash[3] = (this.state.hash[3] + d) | 0;
  }

  /**
   * Finalize hash and return result
   *
   * @returns {string}
   */
  finalize() {
    this.addPaddingISO7816(
      this.state.message.length < 56 ?
        56 - this.state.message.length | 0 :
        120 - this.state.message.length | 0);
    this.addLengthBits();
    this.process();
    return this.getStateHash();
  }
}

export default Md5;