OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / sodium-hmac / index.js

103 lines · javascript ref:
const b4a = require('b4a')

const sha256 = require('./hash/sha256')
const sha512 = require('./hash/sha512')

class HMAC {
  constructor (hash, key) {
    this.hash = hash
    this.length = hash.BYTES

    this._blocksize = 2 * hash.BYTES

    this._initialised = false

    this.state = b4a.alloc(hash.STATEBYTES)
    this.pad = b4a.alloc(this._blocksize)

    if (key) this.init(key)
  }

  init (key) {
    if (this._initialised) {
      throw new Error('Already initialised, call final first')
    }

    if (key.length > this._blocksize) {
      this.hash.init(this.state)
      this.hash.update(this.state, key)
      this.hash.final(this.state, this.pad.subarray(0, this.length))
    } else {
      this.pad.set(key)
    }

    bufferByteXor(this.pad, 0x36)

    this.hash.init(this.state)
    this.hash.update(this.state, this.pad)

    bufferByteXor(this.pad, 0x5c ^ 0x36)

    this._initialised = true

    return this
  }

  update (data) {
    if (!this._initialised) {
      throw new Error('First initialise with a key')
    }

    this.hash.update(this.state, data)
    return this
  }

  final (output = b4a.alloc(this.length)) {
    if (!this._initialised) {
      throw new Error('First initialise with a key')
    }

    const out = output.byteLength === this.length
      ? output
      : output.subarray(0, this.hash.BYTES)

    this.hash.final(this.state, out)

    this.hash.init(this.state)
    this.hash.update(this.state, this.pad)
    this.hash.update(this.state, out)
    this.hash.final(this.state, out)

    this.pad.fill(0)
    this.state.fill(0)

    this._initialised = false

    return out
  }

  static sha256 (data, key, output) {
    const hmac = new HMAC(sha256, key)
    return hmac.update(data).final(output)
  }

  static sha512 (data, key, output) {
    const hmac = new HMAC(sha512, key)
    return hmac.update(data).final(output)
  }
}

module.exports = {
  HMAC,
  sha256,
  sha512
}

function bufferByteXor (output, byte) {
  for (let i = 0; i < output.byteLength; i++) {
    output[i] ^= byte
  }

  return output
}