// Copyright 2005 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview SHA-1 cryptographic hash. * Variable names follow the notation in FIPS PUB 180-3: * http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf. * * Usage: * var sha1 = new goog.crypt.sha1(); * sha1.update(bytes); * var hash = sha1.digest(); * * Performance: * Chrome 23: ~400 Mbit/s * Firefox 16: ~250 Mbit/s * */ goog.provide('goog.crypt.Sha1'); goog.require('goog.crypt.Hash'); /** * SHA-1 cryptographic hash constructor. * * The properties declared here are discussed in the above algorithm document. * @constructor * @extends {goog.crypt.Hash} * @final * @struct */ goog.crypt.Sha1 = function() { goog.crypt.Sha1.base(this, 'constructor'); this.blockSize = 512 / 8; /** * Holds the previous values of accumulated variables a-e in the compress_ * function. * @type {!Array} * @private */ this.chain_ = []; /** * A buffer holding the partially computed hash result. * @type {!Array} * @private */ this.buf_ = []; /** * An array of 80 bytes, each a part of the message to be hashed. Referred to * as the message schedule in the docs. * @type {!Array} * @private */ this.W_ = []; /** * Contains data needed to pad messages less than 64 bytes. * @type {!Array} * @private */ this.pad_ = []; this.pad_[0] = 128; for (var i = 1; i < this.blockSize; ++i) { this.pad_[i] = 0; } /** * @private {number} */ this.inbuf_ = 0; /** * @private {number} */ this.total_ = 0; this.reset(); }; goog.inherits(goog.crypt.Sha1, goog.crypt.Hash); /** @override */ goog.crypt.Sha1.prototype.reset = function() { this.chain_[0] = 0x67452301; this.chain_[1] = 0xefcdab89; this.chain_[2] = 0x98badcfe; this.chain_[3] = 0x10325476; this.chain_[4] = 0xc3d2e1f0; this.inbuf_ = 0; this.total_ = 0; }; /** * Internal compress helper function. * @param {!Array|!Uint8Array|string} buf Block to compress. * @param {number=} opt_offset Offset of the block in the buffer. * @private */ goog.crypt.Sha1.prototype.compress_ = function(buf, opt_offset) { if (!opt_offset) { opt_offset = 0; } var W = this.W_; // get 16 big endian words if (goog.isString(buf)) { for (var i = 0; i < 16; i++) { // TODO(user): [bug 8140122] Recent versions of Safari for Mac OS and iOS // have a bug that turns the post-increment ++ operator into pre-increment // during JIT compilation. We have code that depends heavily on SHA-1 for // correctness and which is affected by this bug, so I've removed all uses // of post-increment ++ in which the result value is used. We can revert // this change once the Safari bug // (https://bugs.webkit.org/show_bug.cgi?id=109036) has been fixed and // most clients have been updated. W[i] = (buf.charCodeAt(opt_offset) << 24) | (buf.charCodeAt(opt_offset + 1) << 16) | (buf.charCodeAt(opt_offset + 2) << 8) | (buf.charCodeAt(opt_offset + 3)); opt_offset += 4; } } else { for (var i = 0; i < 16; i++) { W[i] = (buf[opt_offset] << 24) | (buf[opt_offset + 1] << 16) | (buf[opt_offset + 2] << 8) | (buf[opt_offset + 3]); opt_offset += 4; } } // expand to 80 words for (var i = 16; i < 80; i++) { var t = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; W[i] = ((t << 1) | (t >>> 31)) & 0xffffffff; } var a = this.chain_[0]; var b = this.chain_[1]; var c = this.chain_[2]; var d = this.chain_[3]; var e = this.chain_[4]; var f, k; // TODO(user): Try to unroll this loop to speed up the computation. for (var i = 0; i < 80; i++) { if (i < 40) { if (i < 20) { f = d ^ (b & (c ^ d)); k = 0x5a827999; } else { f = b ^ c ^ d; k = 0x6ed9eba1; } } else { if (i < 60) { f = (b & c) | (d & (b | c)); k = 0x8f1bbcdc; } else { f = b ^ c ^ d; k = 0xca62c1d6; } } var t = (((a << 5) | (a >>> 27)) + f + e + k + W[i]) & 0xffffffff; e = d; d = c; c = ((b << 30) | (b >>> 2)) & 0xffffffff; b = a; a = t; } this.chain_[0] = (this.chain_[0] + a) & 0xffffffff; this.chain_[1] = (this.chain_[1] + b) & 0xffffffff; this.chain_[2] = (this.chain_[2] + c) & 0xffffffff; this.chain_[3] = (this.chain_[3] + d) & 0xffffffff; this.chain_[4] = (this.chain_[4] + e) & 0xffffffff; }; /** @override */ goog.crypt.Sha1.prototype.update = function(bytes, opt_length) { // TODO(johnlenz): tighten the function signature and remove this check if (bytes == null) { return; } if (!goog.isDef(opt_length)) { opt_length = bytes.length; } var lengthMinusBlock = opt_length - this.blockSize; var n = 0; // Using local instead of member variables gives ~5% speedup on Firefox 16. var buf = this.buf_; var inbuf = this.inbuf_; // The outer while loop should execute at most twice. while (n < opt_length) { // When we have no data in the block to top up, we can directly process the // input buffer (assuming it contains sufficient data). This gives ~25% // speedup on Chrome 23 and ~15% speedup on Firefox 16, but requires that // the data is provided in large chunks (or in multiples of 64 bytes). if (inbuf == 0) { while (n <= lengthMinusBlock) { this.compress_(bytes, n); n += this.blockSize; } } if (goog.isString(bytes)) { while (n < opt_length) { buf[inbuf] = bytes.charCodeAt(n); ++inbuf; ++n; if (inbuf == this.blockSize) { this.compress_(buf); inbuf = 0; // Jump to the outer loop so we use the full-block optimization. break; } } } else { while (n < opt_length) { buf[inbuf] = bytes[n]; ++inbuf; ++n; if (inbuf == this.blockSize) { this.compress_(buf); inbuf = 0; // Jump to the outer loop so we use the full-block optimization. break; } } } } this.inbuf_ = inbuf; this.total_ += opt_length; }; /** @override */ goog.crypt.Sha1.prototype.digest = function() { var digest = []; var totalBits = this.total_ * 8; // Add pad 0x80 0x00*. if (this.inbuf_ < 56) { this.update(this.pad_, 56 - this.inbuf_); } else { this.update(this.pad_, this.blockSize - (this.inbuf_ - 56)); } // Add # bits. for (var i = this.blockSize - 1; i >= 56; i--) { this.buf_[i] = totalBits & 255; totalBits /= 256; // Don't use bit-shifting here! } this.compress_(this.buf_); var n = 0; for (var i = 0; i < 5; i++) { for (var j = 24; j >= 0; j -= 8) { digest[n] = (this.chain_[i] >> j) & 255; ++n; } } return digest; };