mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
573 lines
17 KiB
JavaScript
573 lines
17 KiB
JavaScript
/** @fileOverview Javascript cryptography implementation.
|
|
*
|
|
* Crush to remove comments, shorten variable names and
|
|
* generally reduce transmission size.
|
|
*
|
|
* @author Emily Stark
|
|
* @author Mike Hamburg
|
|
* @author Dan Boneh
|
|
*/
|
|
|
|
"use strict";
|
|
/*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */
|
|
/*global document, window, escape, unescape */
|
|
|
|
/** @namespace The Stanford Javascript Crypto Library, top-level namespace. */
|
|
var sjcl = {
|
|
/** @namespace Symmetric ciphers. */
|
|
cipher: {},
|
|
|
|
/** @namespace Hash functions. Right now only SHA256 is implemented. */
|
|
hash: {},
|
|
|
|
/** @namespace Key exchange functions. Right now only SRP is implemented. */
|
|
keyexchange: {},
|
|
|
|
/** @namespace Block cipher modes of operation. */
|
|
mode: {},
|
|
|
|
/** @namespace Miscellaneous. HMAC and PBKDF2. */
|
|
misc: {},
|
|
|
|
/**
|
|
* @namespace Bit array encoders and decoders.
|
|
*
|
|
* @description
|
|
* The members of this namespace are functions which translate between
|
|
* SJCL's bitArrays and other objects (usually strings). Because it
|
|
* isn't always clear which direction is encoding and which is decoding,
|
|
* the method names are "fromBits" and "toBits".
|
|
*/
|
|
codec: {},
|
|
|
|
/** @namespace Exceptions. */
|
|
exception: {
|
|
/** @class Ciphertext is corrupt. */
|
|
corrupt: function(message) {
|
|
this.toString = function() { return "CORRUPT: "+this.message; };
|
|
this.message = message;
|
|
},
|
|
|
|
/** @class Invalid parameter. */
|
|
invalid: function(message) {
|
|
this.toString = function() { return "INVALID: "+this.message; };
|
|
this.message = message;
|
|
},
|
|
|
|
/** @class Bug or missing feature in SJCL. */
|
|
bug: function(message) {
|
|
this.toString = function() { return "BUG: "+this.message; };
|
|
this.message = message;
|
|
},
|
|
|
|
/** @class Something isn't ready. */
|
|
notReady: function(message) {
|
|
this.toString = function() { return "NOT READY: "+this.message; };
|
|
this.message = message;
|
|
}
|
|
}
|
|
};
|
|
/** @fileOverview Low-level AES implementation.
|
|
*
|
|
* This file contains a low-level implementation of AES, optimized for
|
|
* size and for efficiency on several browsers. It is based on
|
|
* OpenSSL's aes_core.c, a public-domain implementation by Vincent
|
|
* Rijmen, Antoon Bosselaers and Paulo Barreto.
|
|
*
|
|
* An older version of this implementation is available in the public
|
|
* domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
|
|
* Stanford University 2008-2010 and BSD-licensed for liability
|
|
* reasons.
|
|
*
|
|
* @author Emily Stark
|
|
* @author Mike Hamburg
|
|
* @author Dan Boneh
|
|
*/
|
|
|
|
/**
|
|
* Schedule out an AES key for both encryption and decryption. This
|
|
* is a low-level class. Use a cipher mode to do bulk encryption.
|
|
*
|
|
* @constructor
|
|
* @param {Array} key The key as an array of 4, 6 or 8 words.
|
|
*
|
|
* @class Advanced Encryption Standard (low-level interface)
|
|
*/
|
|
sjcl.cipher.aes = function (key) {
|
|
if (!this._tables[0][0][0]) {
|
|
this._precompute();
|
|
}
|
|
|
|
var i, j, tmp,
|
|
encKey, decKey,
|
|
sbox = this._tables[0][4], decTable = this._tables[1],
|
|
keyLen = key.length, rcon = 1;
|
|
|
|
if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
|
|
throw new sjcl.exception.invalid("invalid aes key size");
|
|
}
|
|
|
|
this._key = [encKey = key.slice(0), decKey = []];
|
|
|
|
// schedule encryption keys
|
|
for (i = keyLen; i < 4 * keyLen + 28; i++) {
|
|
tmp = encKey[i-1];
|
|
|
|
// apply sbox
|
|
if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) {
|
|
tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255];
|
|
|
|
// shift rows and add rcon
|
|
if (i%keyLen === 0) {
|
|
tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24;
|
|
rcon = rcon<<1 ^ (rcon>>7)*283;
|
|
}
|
|
}
|
|
|
|
encKey[i] = encKey[i-keyLen] ^ tmp;
|
|
}
|
|
|
|
// schedule decryption keys
|
|
for (j = 0; i; j++, i--) {
|
|
tmp = encKey[j&3 ? i : i - 4];
|
|
if (i<=4 || j<4) {
|
|
decKey[j] = tmp;
|
|
} else {
|
|
decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^
|
|
decTable[1][sbox[tmp>>16 & 255]] ^
|
|
decTable[2][sbox[tmp>>8 & 255]] ^
|
|
decTable[3][sbox[tmp & 255]];
|
|
}
|
|
}
|
|
};
|
|
|
|
sjcl.cipher.aes.prototype = {
|
|
// public
|
|
/* Something like this might appear here eventually
|
|
name: "AES",
|
|
blockSize: 4,
|
|
keySizes: [4,6,8],
|
|
*/
|
|
|
|
/**
|
|
* Encrypt an array of 4 big-endian words.
|
|
* @param {Array} data The plaintext.
|
|
* @return {Array} The ciphertext.
|
|
*/
|
|
encrypt:function (data) { return this._crypt(data,0); },
|
|
|
|
/**
|
|
* Decrypt an array of 4 big-endian words.
|
|
* @param {Array} data The ciphertext.
|
|
* @return {Array} The plaintext.
|
|
*/
|
|
decrypt:function (data) { return this._crypt(data,1); },
|
|
|
|
/**
|
|
* The expanded S-box and inverse S-box tables. These will be computed
|
|
* on the client so that we don't have to send them down the wire.
|
|
*
|
|
* There are two tables, _tables[0] is for encryption and
|
|
* _tables[1] is for decryption.
|
|
*
|
|
* The first 4 sub-tables are the expanded S-box with MixColumns. The
|
|
* last (_tables[01][4]) is the S-box itself.
|
|
*
|
|
* @private
|
|
*/
|
|
_tables: [[[],[],[],[],[]],[[],[],[],[],[]]],
|
|
|
|
/**
|
|
* Expand the S-box tables.
|
|
*
|
|
* @private
|
|
*/
|
|
_precompute: function () {
|
|
var encTable = this._tables[0], decTable = this._tables[1],
|
|
sbox = encTable[4], sboxInv = decTable[4],
|
|
i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec;
|
|
|
|
// Compute double and third tables
|
|
for (i = 0; i < 256; i++) {
|
|
th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i;
|
|
}
|
|
|
|
for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
|
|
// Compute sbox
|
|
s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4;
|
|
s = s>>8 ^ s&255 ^ 99;
|
|
sbox[x] = s;
|
|
sboxInv[s] = x;
|
|
|
|
// Compute MixColumns
|
|
x8 = d[x4 = d[x2 = d[x]]];
|
|
tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100;
|
|
tEnc = d[s]*0x101 ^ s*0x1010100;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8;
|
|
decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8;
|
|
}
|
|
}
|
|
|
|
// Compactify. Considerable speedup on Firefox.
|
|
for (i = 0; i < 5; i++) {
|
|
encTable[i] = encTable[i].slice(0);
|
|
decTable[i] = decTable[i].slice(0);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Encryption and decryption core.
|
|
* @param {Array} input Four words to be encrypted or decrypted.
|
|
* @param dir The direction, 0 for encrypt and 1 for decrypt.
|
|
* @return {Array} The four encrypted or decrypted words.
|
|
* @private
|
|
*/
|
|
_crypt:function (input, dir) {
|
|
if (input.length !== 4) {
|
|
throw new sjcl.exception.invalid("invalid aes block size");
|
|
}
|
|
|
|
var key = this._key[dir],
|
|
// state variables a,b,c,d are loaded with pre-whitened data
|
|
a = input[0] ^ key[0],
|
|
b = input[dir ? 3 : 1] ^ key[1],
|
|
c = input[2] ^ key[2],
|
|
d = input[dir ? 1 : 3] ^ key[3],
|
|
a2, b2, c2,
|
|
|
|
nInnerRounds = key.length/4 - 2,
|
|
i,
|
|
kIndex = 4,
|
|
out = [0,0,0,0],
|
|
table = this._tables[dir],
|
|
|
|
// load up the tables
|
|
t0 = table[0],
|
|
t1 = table[1],
|
|
t2 = table[2],
|
|
t3 = table[3],
|
|
sbox = table[4];
|
|
|
|
// Inner rounds. Cribbed from OpenSSL.
|
|
for (i = 0; i < nInnerRounds; i++) {
|
|
a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex];
|
|
b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1];
|
|
c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2];
|
|
d = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3];
|
|
kIndex += 4;
|
|
a=a2; b=b2; c=c2;
|
|
}
|
|
|
|
// Last round.
|
|
for (i = 0; i < 4; i++) {
|
|
out[dir ? 3&-i : i] =
|
|
sbox[a>>>24 ]<<24 ^
|
|
sbox[b>>16 & 255]<<16 ^
|
|
sbox[c>>8 & 255]<<8 ^
|
|
sbox[d & 255] ^
|
|
key[kIndex++];
|
|
a2=a; a=b; b=c; c=d; d=a2;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
};
|
|
|
|
/** @fileOverview Arrays of bits, encoded as arrays of Numbers.
|
|
*
|
|
* @author Emily Stark
|
|
* @author Mike Hamburg
|
|
* @author Dan Boneh
|
|
*/
|
|
|
|
/** @namespace Arrays of bits, encoded as arrays of Numbers.
|
|
*
|
|
* @description
|
|
* <p>
|
|
* These objects are the currency accepted by SJCL's crypto functions.
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* Most of our crypto primitives operate on arrays of 4-byte words internally,
|
|
* but many of them can take arguments that are not a multiple of 4 bytes.
|
|
* This library encodes arrays of bits (whose size need not be a multiple of 8
|
|
* bits) as arrays of 32-bit words. The bits are packed, big-endian, into an
|
|
* array of words, 32 bits at a time. Since the words are double-precision
|
|
* floating point numbers, they fit some extra data. We use this (in a private,
|
|
* possibly-changing manner) to encode the number of bits actually present
|
|
* in the last word of the array.
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* Because bitwise ops clear this out-of-band data, these arrays can be passed
|
|
* to ciphers like AES which want arrays of words.
|
|
* </p>
|
|
*/
|
|
sjcl.bitArray = {
|
|
/**
|
|
* Array slices in units of bits.
|
|
* @param {bitArray a} The array to slice.
|
|
* @param {Number} bstart The offset to the start of the slice, in bits.
|
|
* @param {Number} bend The offset to the end of the slice, in bits. If this is undefined,
|
|
* slice until the end of the array.
|
|
* @return {bitArray} The requested slice.
|
|
*/
|
|
bitSlice: function (a, bstart, bend) {
|
|
a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1);
|
|
return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart);
|
|
},
|
|
|
|
/**
|
|
* Extract a number packed into a bit array.
|
|
* @param {bitArray} a The array to slice.
|
|
* @param {Number} bstart The offset to the start of the slice, in bits.
|
|
* @param {Number} length The length of the number to extract.
|
|
* @return {Number} The requested slice.
|
|
*/
|
|
extract: function(a, bstart, blength) {
|
|
// FIXME: this Math.floor is not necessary at all, but for some reason
|
|
// seems to suppress a bug in the Chromium JIT.
|
|
var x, sh = Math.floor((-bstart-blength) & 31);
|
|
if ((bstart + blength - 1 ^ bstart) & -32) {
|
|
// it crosses a boundary
|
|
x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh);
|
|
} else {
|
|
// within a single word
|
|
x = a[bstart/32|0] >>> sh;
|
|
}
|
|
return x & ((1<<blength) - 1);
|
|
},
|
|
|
|
/**
|
|
* Concatenate two bit arrays.
|
|
* @param {bitArray} a1 The first array.
|
|
* @param {bitArray} a2 The second array.
|
|
* @return {bitArray} The concatenation of a1 and a2.
|
|
*/
|
|
concat: function (a1, a2) {
|
|
if (a1.length === 0 || a2.length === 0) {
|
|
return a1.concat(a2);
|
|
}
|
|
|
|
var out, i, last = a1[a1.length-1], shift = sjcl.bitArray.getPartial(last);
|
|
if (shift === 32) {
|
|
return a1.concat(a2);
|
|
} else {
|
|
return sjcl.bitArray._shiftRight(a2, shift, last|0, a1.slice(0,a1.length-1));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Find the length of an array of bits.
|
|
* @param {bitArray} a The array.
|
|
* @return {Number} The length of a, in bits.
|
|
*/
|
|
bitLength: function (a) {
|
|
var l = a.length, x;
|
|
if (l === 0) { return 0; }
|
|
x = a[l - 1];
|
|
return (l-1) * 32 + sjcl.bitArray.getPartial(x);
|
|
},
|
|
|
|
/**
|
|
* Truncate an array.
|
|
* @param {bitArray} a The array.
|
|
* @param {Number} len The length to truncate to, in bits.
|
|
* @return {bitArray} A new array, truncated to len bits.
|
|
*/
|
|
clamp: function (a, len) {
|
|
if (a.length * 32 < len) { return a; }
|
|
a = a.slice(0, Math.ceil(len / 32));
|
|
var l = a.length;
|
|
len = len & 31;
|
|
if (l > 0 && len) {
|
|
a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1);
|
|
}
|
|
return a;
|
|
},
|
|
|
|
/**
|
|
* Make a partial word for a bit array.
|
|
* @param {Number} len The number of bits in the word.
|
|
* @param {Number} x The bits.
|
|
* @param {Number} [0] _end Pass 1 if x has already been shifted to the high side.
|
|
* @return {Number} The partial word.
|
|
*/
|
|
partial: function (len, x, _end) {
|
|
if (len === 32) { return x; }
|
|
return (_end ? x|0 : x << (32-len)) + len * 0x10000000000;
|
|
},
|
|
|
|
/**
|
|
* Get the number of bits used by a partial word.
|
|
* @param {Number} x The partial word.
|
|
* @return {Number} The number of bits used by the partial word.
|
|
*/
|
|
getPartial: function (x) {
|
|
return Math.round(x/0x10000000000) || 32;
|
|
},
|
|
|
|
/**
|
|
* Compare two arrays for equality in a predictable amount of time.
|
|
* @param {bitArray} a The first array.
|
|
* @param {bitArray} b The second array.
|
|
* @return {boolean} true if a == b; false otherwise.
|
|
*/
|
|
equal: function (a, b) {
|
|
if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) {
|
|
return false;
|
|
}
|
|
var x = 0, i;
|
|
for (i=0; i<a.length; i++) {
|
|
x |= a[i]^b[i];
|
|
}
|
|
return (x === 0);
|
|
},
|
|
|
|
/** Shift an array right.
|
|
* @param {bitArray} a The array to shift.
|
|
* @param {Number} shift The number of bits to shift.
|
|
* @param {Number} [carry=0] A byte to carry in
|
|
* @param {bitArray} [out=[]] An array to prepend to the output.
|
|
* @private
|
|
*/
|
|
_shiftRight: function (a, shift, carry, out) {
|
|
var i, last2=0, shift2;
|
|
if (out === undefined) { out = []; }
|
|
|
|
for (; shift >= 32; shift -= 32) {
|
|
out.push(carry);
|
|
carry = 0;
|
|
}
|
|
if (shift === 0) {
|
|
return out.concat(a);
|
|
}
|
|
|
|
for (i=0; i<a.length; i++) {
|
|
out.push(carry | a[i]>>>shift);
|
|
carry = a[i] << (32-shift);
|
|
}
|
|
last2 = a.length ? a[a.length-1] : 0;
|
|
shift2 = sjcl.bitArray.getPartial(last2);
|
|
out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1));
|
|
return out;
|
|
},
|
|
|
|
/** xor a block of 4 words together.
|
|
* @private
|
|
*/
|
|
_xor4: function(x,y) {
|
|
return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];
|
|
}
|
|
};
|
|
/** @fileOverview CBC mode implementation
|
|
*
|
|
* @author Emily Stark
|
|
* @author Mike Hamburg
|
|
* @author Dan Boneh
|
|
*/
|
|
|
|
/** @namespace
|
|
* Dangerous: CBC mode with PKCS#5 padding.
|
|
*
|
|
* @author Emily Stark
|
|
* @author Mike Hamburg
|
|
* @author Dan Boneh
|
|
*/
|
|
sjcl.mode.cbc = {
|
|
/** The name of the mode.
|
|
* @constant
|
|
*/
|
|
name: "cbc",
|
|
|
|
/** Encrypt in CBC mode with PKCS#5 padding.
|
|
* @param {Object} prp The block cipher. It must have a block size of 16 bytes.
|
|
* @param {bitArray} plaintext The plaintext data.
|
|
* @param {bitArray} iv The initialization value.
|
|
* @param {bitArray} [adata=[]] The authenticated data. Must be empty.
|
|
* @return The encrypted data, an array of bytes.
|
|
* @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits, or if any adata is specified.
|
|
*/
|
|
encrypt: function(prp, plaintext, iv, adata) {
|
|
if (adata && adata.length) {
|
|
throw new sjcl.exception.invalid("cbc can't authenticate data");
|
|
}
|
|
if (sjcl.bitArray.bitLength(iv) !== 128) {
|
|
throw new sjcl.exception.invalid("cbc iv must be 128 bits");
|
|
}
|
|
var i,
|
|
w = sjcl.bitArray,
|
|
xor = w._xor4,
|
|
bl = w.bitLength(plaintext),
|
|
bp = 0,
|
|
output = [];
|
|
|
|
if (bl&7) {
|
|
throw new sjcl.exception.invalid("pkcs#5 padding only works for multiples of a byte");
|
|
}
|
|
|
|
for (i=0; bp+128 <= bl; i+=4, bp+=128) {
|
|
/* Encrypt a non-final block */
|
|
iv = prp.encrypt(xor(iv, plaintext.slice(i,i+4)));
|
|
output.splice(i,0,iv[0],iv[1],iv[2],iv[3]);
|
|
}
|
|
|
|
/* Construct the pad. */
|
|
bl = (16 - ((bl >> 3) & 15)) * 0x1010101;
|
|
|
|
/* Pad and encrypt. */
|
|
iv = prp.encrypt(xor(iv,w.concat(plaintext,[bl,bl,bl,bl]).slice(i,i+4)));
|
|
output.splice(i,0,iv[0],iv[1],iv[2],iv[3]);
|
|
return output;
|
|
},
|
|
|
|
/** Decrypt in CBC mode.
|
|
* @param {Object} prp The block cipher. It must have a block size of 16 bytes.
|
|
* @param {bitArray} ciphertext The ciphertext data.
|
|
* @param {bitArray} iv The initialization value.
|
|
* @param {bitArray} [adata=[]] The authenticated data. It must be empty.
|
|
* @return The decrypted data, an array of bytes.
|
|
* @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits, or if any adata is specified.
|
|
* @throws {sjcl.exception.corrupt} if if the message is corrupt.
|
|
*/
|
|
decrypt: function(prp, ciphertext, iv, adata) {
|
|
if (adata && adata.length) {
|
|
throw new sjcl.exception.invalid("cbc can't authenticate data");
|
|
}
|
|
if (sjcl.bitArray.bitLength(iv) !== 128) {
|
|
throw new sjcl.exception.invalid("cbc iv must be 128 bits");
|
|
}
|
|
if ((sjcl.bitArray.bitLength(ciphertext) & 127) || !ciphertext.length) {
|
|
throw new sjcl.exception.corrupt("cbc ciphertext must be a positive multiple of the block size");
|
|
}
|
|
var i,
|
|
w = sjcl.bitArray,
|
|
xor = w._xor4,
|
|
bi, bo,
|
|
output = [];
|
|
|
|
adata = adata || [];
|
|
|
|
for (i=0; i<ciphertext.length; i+=4) {
|
|
bi = ciphertext.slice(i,i+4);
|
|
bo = xor(iv,prp.decrypt(bi));
|
|
output.splice(i,0,bo[0],bo[1],bo[2],bo[3]);
|
|
iv = bi;
|
|
}
|
|
|
|
/* check and remove the pad */
|
|
bi = output[i-1] & 255;
|
|
if (bi == 0 || bi > 16) {
|
|
throw new sjcl.exception.corrupt("pkcs#5 padding corrupt");
|
|
}
|
|
bo = bi * 0x1010101;
|
|
if (!w.equal(w.bitSlice([bo,bo,bo,bo], 0, bi*8),
|
|
w.bitSlice(output, output.length*32 - bi*8, output.length*32))) {
|
|
throw new sjcl.exception.corrupt("pkcs#5 padding corrupt");
|
|
}
|
|
|
|
return w.bitSlice(output, 0, output.length*32 - bi*8);
|
|
}
|
|
};
|