browserSha1.js
  1  var Buffer = require('buffer/').Buffer;
  2  var hashUtils = require('./browserHashUtils');
  3  
  4  var BLOCK_SIZE = 64;
  5  
  6  var DIGEST_LENGTH = 20;
  7  
  8  var KEY = new Uint32Array([
  9      0x5a827999,
 10      0x6ed9eba1,
 11      0x8f1bbcdc | 0,
 12      0xca62c1d6 | 0
 13  ]);
 14  
 15  var INIT = [
 16      0x6a09e667,
 17      0xbb67ae85,
 18      0x3c6ef372,
 19      0xa54ff53a,
 20      0x510e527f,
 21      0x9b05688c,
 22      0x1f83d9ab,
 23      0x5be0cd19,
 24  ];
 25  
 26  var MAX_HASHABLE_LENGTH = Math.pow(2, 53) - 1;
 27  
 28  /**
 29   * @api private
 30   */
 31  function Sha1() {
 32      this.h0 = 0x67452301;
 33      this.h1 = 0xEFCDAB89;
 34      this.h2 = 0x98BADCFE;
 35      this.h3 = 0x10325476;
 36      this.h4 = 0xC3D2E1F0;
 37      // The first 64 bytes (16 words) is the data chunk
 38      this.block = new Uint32Array(80);
 39      this.offset = 0;
 40      this.shift = 24;
 41      this.totalLength = 0;
 42  }
 43  
 44  /**
 45   * @api private
 46   */
 47  module.exports = exports = Sha1;
 48  
 49  Sha1.BLOCK_SIZE = BLOCK_SIZE;
 50  
 51  Sha1.prototype.update = function (data) {
 52      if (this.finished) {
 53          throw new Error('Attempted to update an already finished hash.');
 54      }
 55  
 56      if (hashUtils.isEmptyData(data)) {
 57          return this;
 58      }
 59  
 60      data = hashUtils.convertToBuffer(data);
 61  
 62      var length = data.length;
 63      this.totalLength += length * 8;
 64      for (var i = 0; i < length; i++) {
 65          this.write(data[i]);
 66      }
 67  
 68      return this;
 69  };
 70  
 71  Sha1.prototype.write = function write(byte) {
 72      this.block[this.offset] |= (byte & 0xff) << this.shift;
 73      if (this.shift) {
 74          this.shift -= 8;
 75      } else {
 76          this.offset++;
 77          this.shift = 24;
 78      }
 79  
 80      if (this.offset === 16) this.processBlock();
 81  };
 82  
 83  Sha1.prototype.digest = function (encoding) {
 84      // Pad
 85      this.write(0x80);
 86      if (this.offset > 14 || (this.offset === 14 && this.shift < 24)) {
 87        this.processBlock();
 88      }
 89      this.offset = 14;
 90      this.shift = 24;
 91  
 92      // 64-bit length big-endian
 93      this.write(0x00); // numbers this big aren't accurate in javascript anyway
 94      this.write(0x00); // ..So just hard-code to zero.
 95      this.write(this.totalLength > 0xffffffffff ? this.totalLength / 0x10000000000 : 0x00);
 96      this.write(this.totalLength > 0xffffffff ? this.totalLength / 0x100000000 : 0x00);
 97      for (var s = 24; s >= 0; s -= 8) {
 98          this.write(this.totalLength >> s);
 99      }
100      // The value in state is little-endian rather than big-endian, so flip
101      // each word into a new Uint8Array
102      var out = new Buffer(DIGEST_LENGTH);
103      var outView = new DataView(out.buffer);
104      outView.setUint32(0, this.h0, false);
105      outView.setUint32(4, this.h1, false);
106      outView.setUint32(8, this.h2, false);
107      outView.setUint32(12, this.h3, false);
108      outView.setUint32(16, this.h4, false);
109  
110      return encoding ? out.toString(encoding) : out;
111  };
112  
113  Sha1.prototype.processBlock = function processBlock() {
114      // Extend the sixteen 32-bit words into eighty 32-bit words:
115      for (var i = 16; i < 80; i++) {
116        var w = this.block[i - 3] ^ this.block[i - 8] ^ this.block[i - 14] ^ this.block[i - 16];
117        this.block[i] = (w << 1) | (w >>> 31);
118      }
119  
120      // Initialize hash value for this chunk:
121      var a = this.h0;
122      var b = this.h1;
123      var c = this.h2;
124      var d = this.h3;
125      var e = this.h4;
126      var f, k;
127  
128      // Main loop:
129      for (i = 0; i < 80; i++) {
130        if (i < 20) {
131          f = d ^ (b & (c ^ d));
132          k = 0x5A827999;
133        }
134        else if (i < 40) {
135          f = b ^ c ^ d;
136          k = 0x6ED9EBA1;
137        }
138        else if (i < 60) {
139          f = (b & c) | (d & (b | c));
140          k = 0x8F1BBCDC;
141        }
142        else {
143          f = b ^ c ^ d;
144          k = 0xCA62C1D6;
145        }
146        var temp = (a << 5 | a >>> 27) + f + e + k + (this.block[i]|0);
147        e = d;
148        d = c;
149        c = (b << 30 | b >>> 2);
150        b = a;
151        a = temp;
152      }
153  
154      // Add this chunk's hash to result so far:
155      this.h0 = (this.h0 + a) | 0;
156      this.h1 = (this.h1 + b) | 0;
157      this.h2 = (this.h2 + c) | 0;
158      this.h3 = (this.h3 + d) | 0;
159      this.h4 = (this.h4 + e) | 0;
160  
161      // The block is now reusable.
162      this.offset = 0;
163      for (i = 0; i < 16; i++) {
164          this.block[i] = 0;
165      }
166  };