|
@@ -0,0 +1,1697 @@
|
|
|
+
|
|
|
+
|
|
|
+/*--------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+ MIT license.
|
|
|
+
|
|
|
+ Copyright 2017 Aaron Flin
|
|
|
+
|
|
|
+ Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
+ of this software and associated documentation files (the "Software"), to
|
|
|
+ deal in the Software without restriction, including without limitation the
|
|
|
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
+ sell copies of the Software, and to permit persons to whom the Software is
|
|
|
+ furnished to do so, subject to the following conditions:
|
|
|
+
|
|
|
+ The above copyright notice and this permission notice shall be included in
|
|
|
+ all copies or substantial portions of the Software.
|
|
|
+
|
|
|
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
+ IN THE SOFTWARE.
|
|
|
+
|
|
|
+
|
|
|
+----------------------------------------------------------------------------------------------*/
|
|
|
+import forge from 'node-forge';
|
|
|
+
|
|
|
+//start collecting random data for forge RNG
|
|
|
+var d = new Date();
|
|
|
+
|
|
|
+if (typeof document !== 'undefined') {
|
|
|
+ forge.random.collect(d.getTime(), 32);
|
|
|
+ //in normal version, we can get the mouse movements and add that as random data.
|
|
|
+ document.onmousemove = function (e) {
|
|
|
+ forge.random.collectInt(e.clientX, 16);
|
|
|
+ forge.random.collectInt(e.clientY, 16);
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+export var aescrypt = (function () {
|
|
|
+
|
|
|
+ var b2h = forge.util.bytesToHex;
|
|
|
+
|
|
|
+ function a2h(a) {
|
|
|
+ return forge.util.bytesToHex(arrayToString(a));
|
|
|
+ }
|
|
|
+
|
|
|
+ var toType = function (obj) {
|
|
|
+ return ({}).toString.call(obj).match(/\s([a-zA-Z0-9]+)/)[1].toLowerCase();
|
|
|
+ }
|
|
|
+
|
|
|
+ /* copy string or uint8array into a uint8array
|
|
|
+ start is where in output array to start copying
|
|
|
+ max is max bytes to copy from input string/array
|
|
|
+
|
|
|
+ does not check if a is big enough to take s
|
|
|
+ */
|
|
|
+
|
|
|
+ function copyToArray(s, a, start, max) {
|
|
|
+ if (max === undefined) max = s.length;
|
|
|
+ if (start === undefined) start = 0;
|
|
|
+ var t = toType(s);
|
|
|
+
|
|
|
+ if (t == "arraybuffer") {
|
|
|
+ s = new Uint8Array(s);
|
|
|
+ t = 'uint8array';
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (t) {
|
|
|
+ case 'array':
|
|
|
+ case 'uint8array':
|
|
|
+ //for (var i=0; i<max;i++)
|
|
|
+ // a[i+start]=s[i];
|
|
|
+ // not much of an improvement using .set(), if any at all
|
|
|
+ if (max >= s.length) {
|
|
|
+ a.set(s, start);
|
|
|
+ return s.length;
|
|
|
+ } else if (t == 'array') {
|
|
|
+ a.set(s.slice(0, max), start);
|
|
|
+ } else {
|
|
|
+ a.set(s.subarray(0, max), start);
|
|
|
+ }
|
|
|
+ return max;
|
|
|
+ case 'string':
|
|
|
+ for (var i = 0; i < max; i++)
|
|
|
+ a[i + start] = s.charCodeAt(i);
|
|
|
+ return i;
|
|
|
+ default:
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /* convert a uint8array or arraybuffer to a string */
|
|
|
+ function arrayToString(array) {
|
|
|
+ var ret = "";
|
|
|
+ if (toType(array) == 'arraybuffer')
|
|
|
+ array = new Uint8Array(array);
|
|
|
+ return String.fromCharCode.apply(null, array);
|
|
|
+ }
|
|
|
+
|
|
|
+ function toU8(data, nostrings) {
|
|
|
+ switch (toType(data)) {
|
|
|
+ case 'uint8array':
|
|
|
+ return data;
|
|
|
+ case 'arraybuffer':
|
|
|
+ return (new Uint8Array(data));
|
|
|
+ case 'string':
|
|
|
+ if (nostrings) return;
|
|
|
+ //no break
|
|
|
+ case 'array':
|
|
|
+ // is this the best way to do this, given encfile could be large?
|
|
|
+ var x = new Uint8Array(data.length);
|
|
|
+ copyToArray(data, x, 0);
|
|
|
+ return x;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function isblank(a, start, len) {
|
|
|
+ var end = start + len;
|
|
|
+ for (var i = start; i < end; i++)
|
|
|
+ if (a[i] != 0) return false;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ function to16(str) {
|
|
|
+ var out, i, len, c, c2;
|
|
|
+ var char2, char3;
|
|
|
+
|
|
|
+ out = "";
|
|
|
+ len = str.length;
|
|
|
+ i = 0;
|
|
|
+ while (i < len) {
|
|
|
+ c = str.charCodeAt(i++);
|
|
|
+ c2 = c >>> 8;
|
|
|
+ c = c & 0xFF;
|
|
|
+ out += String.fromCharCode(c);
|
|
|
+ out += String.fromCharCode(c2);
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ function ivpasstokey(iv, pass) {
|
|
|
+ var hashbuf = new Uint8Array(32);
|
|
|
+ var hashstr, hashstr2;
|
|
|
+ var p = to16(pass);
|
|
|
+ copyToArray(iv, hashbuf, 0);
|
|
|
+ hashstr = arrayToString(hashbuf);
|
|
|
+ hashstr2 = hashstr;
|
|
|
+ // do this outside the loop, its expensive, and many times more expensive in chrome than firefox
|
|
|
+ var md = forge.md.sha256.create();
|
|
|
+ for (var i = 0; i < 8192; i++) {
|
|
|
+ //start() is not expensive, but allows us to start over with same object
|
|
|
+ md.start();
|
|
|
+ md.update(hashstr + p);
|
|
|
+ hashstr = md.digest().data;
|
|
|
+ }
|
|
|
+ return hashstr;
|
|
|
+ }
|
|
|
+
|
|
|
+ function xor16(a, b) {
|
|
|
+ var x = new Uint8Array(16);
|
|
|
+ for (i = 0; i < 16; i++)
|
|
|
+ x[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
|
+ return arrayToString(x);
|
|
|
+ }
|
|
|
+
|
|
|
+ function makeext(x) {
|
|
|
+ var lenbytes = new String;
|
|
|
+ var t = new String;
|
|
|
+ var len = 0;
|
|
|
+ if (typeof x == 'string') {
|
|
|
+ len = x.length + 1;
|
|
|
+ t = x + String.fromCharCode(0);
|
|
|
+ } else {
|
|
|
+ //assume an array of strings
|
|
|
+ for (var i = 0; i < x.length; i++) {
|
|
|
+ len += x[i].length + 1;
|
|
|
+ t += x[i] + String.fromCharCode(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return String.fromCharCode(len >> 8) + String.fromCharCode(len & 0xFF) + t;
|
|
|
+ }
|
|
|
+
|
|
|
+ function cipherblock(key, iv, block) {
|
|
|
+ var cipher = forge.cipher.createCipher('AES-CBC', key);
|
|
|
+ cipher.start({ iv: iv });
|
|
|
+ cipher.update(forge.util.createBuffer(block));
|
|
|
+ cipher.finish();
|
|
|
+ var mod = block.length % 16;
|
|
|
+ //console.log(mod);
|
|
|
+ if (mod != 0)
|
|
|
+ return cipher.output.data;
|
|
|
+ // don't pad file that otherwise fits exactly into 16 byte blocks.
|
|
|
+ // forge needs the extra room for encoding a padding number in the plaintext
|
|
|
+ // aescrypt format has the padding number outside the encrypted text.
|
|
|
+ else
|
|
|
+ return cipher.output.data.slice(0, -16);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /* decipher some crypttext. skipend fixes the truncation problem that prompted much of the commented out code below */
|
|
|
+
|
|
|
+ function decipherblock(key, iv, block, skipend) {
|
|
|
+ var decipher = forge.cipher.createDecipher('AES-CBC', key);
|
|
|
+ decipher.start({ iv: iv });
|
|
|
+ decipher.update(forge.util.createBuffer(block));
|
|
|
+ //too many negatives for a normal human brain (well, at least mine)
|
|
|
+ // skip end of file padding if skipend = true;
|
|
|
+ if (skipend !== true) decipher.finish();
|
|
|
+ return decipher.output.data;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* converting a large file to a string chokes, we need to do this a bit at a time */
|
|
|
+ /* if its big, pass a Uint8Array instead of string */
|
|
|
+ var chunksize = 16 * 1024;
|
|
|
+ function hmacblock(key, block) {
|
|
|
+ var hmac = forge.hmac.create();
|
|
|
+ hmac.start('sha256', key);
|
|
|
+ if (toType(block) == 'uint8array') {
|
|
|
+ var l = block.length;
|
|
|
+ //console.log(l);
|
|
|
+ for (var i = 0; i < l; i += chunksize) {
|
|
|
+ var end = ((i + chunksize) < l) ? i + chunksize : l;
|
|
|
+ var s = arrayToString(block.subarray(i, end));
|
|
|
+ //console.log("updating with:"+s);
|
|
|
+ //console.log(s.length);
|
|
|
+ hmac.update(s);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ hmac.update(block);
|
|
|
+ }
|
|
|
+ return hmac.digest().data;
|
|
|
+ }
|
|
|
+
|
|
|
+ //same thing, but use existing hmac, and don't finish
|
|
|
+ function hmacchunkblock(hmac, block) {
|
|
|
+ if (toType(block) == 'uint8array') {
|
|
|
+ var l = block.length, s;
|
|
|
+ //console.log(l);
|
|
|
+ for (var i = 0; i < l; i += chunksize) {
|
|
|
+ //var end = ( (i+chunksize)< l) ? i+chunksize: l;
|
|
|
+ //var s=arrayToString(block.subarray(i,end));
|
|
|
+ //console.log("updating with:"+s);
|
|
|
+ //console.log(s.length);
|
|
|
+ s = arrayToString(block.subarray(i, i + chunksize));
|
|
|
+ hmac.update(s);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ hmac.update(block);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /* get a key and fileiv from a 96 byte credential block */
|
|
|
+ /* cb is an Uint8Array containing the credential block and pass is used to decrypt it */
|
|
|
+
|
|
|
+ function getkey(cb, pass) {
|
|
|
+ var keyiv, bufi = 0, enckeyblk, hdat, hashkey, hdatcomp, keyblock, fileiv, key, i;
|
|
|
+ keyiv = arrayToString(cb.subarray(bufi, bufi + 16));
|
|
|
+ bufi += 16;
|
|
|
+ enckeyblk = arrayToString(cb.subarray(bufi, bufi + 48));
|
|
|
+ bufi += 48;
|
|
|
+ hdat = arrayToString(cb.subarray(bufi, bufi + 32));
|
|
|
+ bufi += 32;
|
|
|
+ hashkey = ivpasstokey(keyiv, pass);
|
|
|
+ hdatcomp = hmacblock(hashkey, enckeyblk);
|
|
|
+ //console.log ("keyiv=" + forge.util.bytesToHex(keyiv));console.log ("enckeyblk=" + forge.util.bytesToHex(enckeyblk));console.log ("hdat=" + forge.util.bytesToHex(hdat));console.log ("hashkey=" + forge.util.bytesToHex(hashkey));console.log("hdatcomp=" + forge.util.bytesToHex(hdatcomp));
|
|
|
+ if (hdat != hdatcomp) {
|
|
|
+ //return error
|
|
|
+ //console.log("hmac does not match. Bad password or file corruption");
|
|
|
+ return { key: "", fileiv: "", error: "hmac does not match. Bad password or file corruption" };
|
|
|
+ }
|
|
|
+ i = 0;
|
|
|
+ keyblock = decipherblock(hashkey, keyiv, enckeyblk, true);
|
|
|
+ if (keyblock.length != 48) {
|
|
|
+ console.log("This shouldn't happen anymore. Please report this.");
|
|
|
+ }
|
|
|
+ fileiv = keyblock.slice(0, 16);
|
|
|
+ key = keyblock.slice(16, 48);
|
|
|
+ //console.log("keyblock.length="+keyblock.length);console.log ("key=" + forge.util.bytesToHex(key));console.log ("fileiv=" + forge.util.bytesToHex(fileiv));
|
|
|
+ return { key: key, fileiv: fileiv, error: "" };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /* make the 96 byte credential block ( keyiv, encrypted fileiv+key, hmac (fileiv+key) ) */
|
|
|
+ /* pass required, will generate key and fileiv if not provided */
|
|
|
+ function newcredentialblock(pass, key, fileiv) {
|
|
|
+ var keyiv, hashkey, keyblock, enckeyblk, tblock;
|
|
|
+ var buffer = new Uint8Array(96);
|
|
|
+ var d = new Date();
|
|
|
+ forge.random.collectInt(d.getTime(), 32);
|
|
|
+
|
|
|
+ //encrypt the iv-key combination (those used to encrypt file data)
|
|
|
+ // and chop off the padding
|
|
|
+
|
|
|
+ keyiv = forge.random.getBytesSync(16);
|
|
|
+ hashkey = ivpasstokey(keyiv, pass);
|
|
|
+ if (fileiv === undefined) fileiv = forge.random.getBytesSync(16);
|
|
|
+ if (key === undefined) key = forge.random.getBytesSync(32);
|
|
|
+ keyblock = fileiv + key;
|
|
|
+
|
|
|
+ enckeyblk = cipherblock(hashkey, keyiv, keyblock).slice(0, 48);
|
|
|
+
|
|
|
+ //this appears to be no longer a problem when using decipherblock(,,,true)
|
|
|
+ //i.e. dont finish(), and skip padding removal which we don't have
|
|
|
+ tblock = decipherblock(hashkey, keyiv, enckeyblk, true);
|
|
|
+ if (tblock != keyblock) {
|
|
|
+ //console.log("failed to find keys and ivs that forge liked...");
|
|
|
+ //console.log("keyblock=" + forge.util.bytesToHex(keyblock));
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ //console.log("keyiv="+forge.util.bytesToHex(keyiv));console.log("hashkey=" + forge.util.bytesToHex(hashkey));console.log("keyblock=" + forge.util.bytesToHex(keyblock));console.log("fileiv=" + forge.util.bytesToHex(fileiv));console.log("key=" + forge.util.bytesToHex(key));console.log("aes-cbc enckeyblock ="+forge.util.bytesToHex(enckeyblk));console.log("encrypted iv+key length=" + enckeyblk.length);
|
|
|
+
|
|
|
+ // 16 + 48 + 32 bytes = 96 for entire record.
|
|
|
+ copyToArray((keyiv + enckeyblk + hmacblock(hashkey, enckeyblk)), buffer, 0)
|
|
|
+ return { buffer: buffer, key: key, fileiv: fileiv };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /* get the key from main keyblock or in credential blocks *
|
|
|
+ * takes a Unit8Array *
|
|
|
+ * if inextcbonly==true, we will only look for the pass in the *
|
|
|
+ * extended cb block *
|
|
|
+ * if offset is set, we start looking for extensions at that *
|
|
|
+ * location instead of 5 bytes in */
|
|
|
+
|
|
|
+ var getanykey = function (file, pass, inextcbonly, offset, deletekey) {
|
|
|
+ var file, bufi, error, credext, credsize, newblock,
|
|
|
+ key, encdata, data, i = 0, cb;
|
|
|
+
|
|
|
+ //console.log("file="+ forge.util.bytesToHex(arrayToString(file)));
|
|
|
+ //find our extension, skip past rest. Assume from start of file, but take offset if provided
|
|
|
+ bufi = (offset === undefined) ? 5 : offset;
|
|
|
+
|
|
|
+ while (bufi < file.length && (file[bufi] != 0 || file[bufi + 1] != 0)) {
|
|
|
+ var sig, size = (file[bufi] << 8) + file[bufi + 1];
|
|
|
+ if (size > 17) {
|
|
|
+ sig = arrayToString(file.subarray(bufi + 2, bufi + 17));
|
|
|
+ if (sig == "enckeyblk v 0.1") {
|
|
|
+ credext = bufi + 18; //2 size bytes plus ("enckeyblk v 0.1").length + 1 (0x00 at end)
|
|
|
+ credsize = size - 16;
|
|
|
+ //no need to go further, and we might not have a 0x0000 at the end anyway
|
|
|
+ if (inextcbonly) break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ bufi += size + 2;
|
|
|
+ if (bufi > file.length) {
|
|
|
+ //return error
|
|
|
+ //console.log("error finding end of extensions");
|
|
|
+ return { key: "", fileiv: "", error: "error finding end of extensions" };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if true skip and only look in extended credential block for key. Skip checking of main block for this password
|
|
|
+ if (inextcbonly !== true) {
|
|
|
+ bufi += 2;
|
|
|
+ //get keyiv, encrypted key and file iv and hmac of encrypted key and file iv (credential block)
|
|
|
+ cb = file.subarray(bufi, bufi + 96);
|
|
|
+ bufi += 96;
|
|
|
+ //console.log("looking in main block");
|
|
|
+ key = getkey(cb, pass);
|
|
|
+ if (key.error == "")
|
|
|
+ return { key: key.key, fileiv: key.fileiv, error: "", masterkey: true };
|
|
|
+
|
|
|
+ }
|
|
|
+ var isarray = false;
|
|
|
+ var indexarray;
|
|
|
+ if (credext !== undefined) {
|
|
|
+ // find non empty blocks and check for key
|
|
|
+ // also check for request to delete key
|
|
|
+ // if deletekey is an array, it should be an array of keyslot positions to delete
|
|
|
+ // otherwise if ===true, it should be deleted if pass matches
|
|
|
+ var j = 0;
|
|
|
+ if (deletekey && typeof deletekey == 'array') {
|
|
|
+ isarray = true;
|
|
|
+ indexarray = [];
|
|
|
+ }
|
|
|
+ for (var i = 0; i < credsize; i += 96) {
|
|
|
+ var start = i + credext;
|
|
|
+ if (!isblank(file, start, 96)) {
|
|
|
+ cb = file.subarray(start, start + 96);
|
|
|
+ //console.log("cb="+forge.util.bytesToHex(arrayToString(cb)));
|
|
|
+ // if we are deleting keys at certain positions
|
|
|
+ if (isarray) {
|
|
|
+ for (var k = 0; k < deletekey.length; k++) {
|
|
|
+ if (deletekey[k] == j) {
|
|
|
+ copyToArray(new Uint8Array(96), file, start);
|
|
|
+ indexarray.push(j);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //dont check for key. if using array and inextcbonly===true, password can be blank
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ //console.log("checking keyslot "+j);
|
|
|
+ key = getkey(cb, pass);
|
|
|
+ if (key.error == "") {
|
|
|
+ if (deletekey === true) {
|
|
|
+ copyToArray(new Uint8Array(96), file, start);
|
|
|
+ }
|
|
|
+ return { key: key.key, fileiv: key.fileiv, error: "", index: j, masterkey: false };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //else console.log("empty keyslot: "+j);
|
|
|
+ j++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (isarray)
|
|
|
+ return { key: "", fileiv: '', error: '', index: indexarray };
|
|
|
+ else
|
|
|
+ return { key: "", fileiv: '', error: "Key not found using this password" };
|
|
|
+ }
|
|
|
+
|
|
|
+ // an array of uint8arrays, with functions to add more and extract bytes
|
|
|
+ // argument file and argument input in put(input) must be uint8arrays
|
|
|
+ // no checking or error messages.
|
|
|
+ function newbytebuf(file) {
|
|
|
+ var buffer = [];
|
|
|
+
|
|
|
+ if (file !== undefined) {
|
|
|
+ file = toU8(file);
|
|
|
+
|
|
|
+ if (toType(file) == 'uint8array') buffer = [file];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function getarrayslength(arrays) {
|
|
|
+ var len = 0;
|
|
|
+ for (var i = 0; i < arrays.length; i++)
|
|
|
+ len += arrays[i].length;
|
|
|
+ return len;
|
|
|
+ }
|
|
|
+
|
|
|
+ function joinarray(arrays) {
|
|
|
+ var len = getarrayslength(arrays), ret = new Uint8Array(len);
|
|
|
+
|
|
|
+ if (len == 0) return ret;
|
|
|
+
|
|
|
+ len = 0;
|
|
|
+ for (var i = 0; i < arrays.length; i++) {
|
|
|
+ ret.set(arrays[i], len);
|
|
|
+ len += arrays[i].length;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return {
|
|
|
+ // read from this buffer (amount gotten with .get() )
|
|
|
+ read: 0,
|
|
|
+ // written to this buffer (amount pushed with put() )
|
|
|
+ written: getarrayslength(buffer),
|
|
|
+ eof: false,
|
|
|
+ buffers: buffer,
|
|
|
+ length: 0,
|
|
|
+ done: function () { this.eof = true; return this; },
|
|
|
+ // only use uint8arrays for input
|
|
|
+ put: function (input) {
|
|
|
+ if (this.eof) {
|
|
|
+ //don't accept more data after eof==true
|
|
|
+ //console.log("already done, can't take more data");
|
|
|
+ this.error = "already done, can't take more data";
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ input = toU8(input);
|
|
|
+
|
|
|
+ if (input !== undefined) {
|
|
|
+ this.written += input.length;
|
|
|
+ this.length = this.written - this.read;
|
|
|
+ this.buffers.push(input);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ this.error = "invalid input data in put()";
|
|
|
+ //console.log("invalid input data in put()");
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ },
|
|
|
+ getlength: function () {
|
|
|
+ this.length = getarrayslength(this.buffers);
|
|
|
+ return this.length;
|
|
|
+ },
|
|
|
+ // optimized if data is still in underlying arraybuffer, but we won't go crazy. If
|
|
|
+ // the unget data covers more than one buffer, then probably not worth it.
|
|
|
+ // if data is an int, we are putting back data that we just got, in order with no gaps.
|
|
|
+ // However, if that isn't possible, x should be the actual data so we have a fallback.
|
|
|
+ // if data is an uint8array, we just unshift the new data
|
|
|
+ unget: function (data, x) {
|
|
|
+ if ((typeof data == 'string' || typeof data == 'number') &&
|
|
|
+ Math.round(data) == data) {
|
|
|
+ if (data <= this.slice && this.buffers.length) {
|
|
|
+ this.slice -= data.length;
|
|
|
+ //reslice the first buffer
|
|
|
+ var wholebuf = new Uint8Array(this.buffers[0].buffer);
|
|
|
+ this.buffers[0] = wholebuf.subarray(this.slice);
|
|
|
+ this.read -= data;
|
|
|
+ this.length = this.written - this.read;
|
|
|
+ return this;
|
|
|
+ } else {
|
|
|
+ //overlap, we'll unshift the data instead
|
|
|
+ data = x;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ data = toU8(data);
|
|
|
+ this.read -= data.length;
|
|
|
+ this.buffers.unshift(data);
|
|
|
+ this.length = this.written - this.read;
|
|
|
+ return this;
|
|
|
+ },
|
|
|
+ // size=-1: get all data as one array
|
|
|
+ // size>0: shift size data from buffer. if buffer smaller than size, return undefined
|
|
|
+ // size==undefined: get a convenient amount of data out of the buffer; if no data left, return undefined
|
|
|
+ get: function (size) {
|
|
|
+ var filebuf = this.buffers;
|
|
|
+ if (!filebuf || !filebuf.length) return;
|
|
|
+ // get the first block of data if undefined, or if that was the size requested
|
|
|
+ if (size === undefined || size == filebuf[0].length) {
|
|
|
+ this.slice = 0;
|
|
|
+ this.read += size;
|
|
|
+ this.length = this.written - this.read;
|
|
|
+ return (filebuf.shift());
|
|
|
+ }
|
|
|
+ //return everything if -1
|
|
|
+ //if no data in buffer, return undefined;
|
|
|
+ if (size == -1 || size == 'all') {
|
|
|
+ var ret = joinarray(this.buffers);
|
|
|
+ this.buffers = [];
|
|
|
+ this.slice = 0;
|
|
|
+ this.read += ret.length;
|
|
|
+ this.length = this.written - this.read;
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ var endbuf = 0, tail;
|
|
|
+ var i = 0, len = 0, ret, jarray = [];
|
|
|
+
|
|
|
+ // is there enough data in the buffer for this request
|
|
|
+ for (i = 0; i < filebuf.length; i++) {
|
|
|
+ //tail is how much more we need from the next array
|
|
|
+ tail = size - len;
|
|
|
+ len += filebuf[i].length;
|
|
|
+ endbuf = i;
|
|
|
+ if (len >= size) break;
|
|
|
+ }
|
|
|
+ if (len < size)
|
|
|
+ //return undefined, just like shift above
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (endbuf == 0) {
|
|
|
+ // the easy case
|
|
|
+ ret = filebuf[0].subarray(0, size);
|
|
|
+ filebuf[0] = filebuf[0].subarray(size);
|
|
|
+ this.slice += size;
|
|
|
+ this.read += size;
|
|
|
+ this.length = this.written - this.read;
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < endbuf; i++)
|
|
|
+ jarray.push(filebuf.shift());
|
|
|
+
|
|
|
+ jarray.push(filebuf[0].subarray(0, tail));
|
|
|
+
|
|
|
+ filebuf[0] = filebuf[0].subarray(tail);
|
|
|
+ this.slice = tail;
|
|
|
+ ret = joinarray(jarray);
|
|
|
+ this.read += ret.length;
|
|
|
+ this.length = this.written - this.read;
|
|
|
+ return ret;
|
|
|
+ },
|
|
|
+
|
|
|
+ // Just like get, but don't advance or update read
|
|
|
+ // size=-1: get all data as one array
|
|
|
+ // size>0: shift size data from buffer. if buffer smaller than size, return undefined
|
|
|
+ // size==undefined: get a convenient amount of data out of the buffer; if no data left, return undefined
|
|
|
+ preview: function (size) {
|
|
|
+ var filebuf = this.buffers;
|
|
|
+ this.length = this.written - this.read;
|
|
|
+ if (!filebuf || !filebuf.length) return;
|
|
|
+ // get the first block of data if undefined, or if that was the size requested
|
|
|
+ if (size === undefined || size == filebuf[0].length) {
|
|
|
+ this.bufslice = 0;
|
|
|
+ return (filebuf[0]);
|
|
|
+ }
|
|
|
+ //return everything if -1
|
|
|
+ //if no data in buffer, return undefined;
|
|
|
+ if (size == -1)
|
|
|
+ return joinarray(this.buffers);
|
|
|
+
|
|
|
+ var endbuf = 0, tail;
|
|
|
+ var i = 0, len = 0, ret, jarray = [];
|
|
|
+
|
|
|
+ // is there enough data in the buffer for this request
|
|
|
+ for (i = 0; i < filebuf.length; i++) {
|
|
|
+ //tail is how much more we need from the last array
|
|
|
+ //from which we will grab data
|
|
|
+ tail = size - len;
|
|
|
+ len += filebuf[i].length;
|
|
|
+ endbuf = i;
|
|
|
+ if (len >= size) break;
|
|
|
+ }
|
|
|
+ if (len < size)
|
|
|
+ //return undefined, just like shift above
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (endbuf == 0)
|
|
|
+ return filebuf[0].subarray(0, size);
|
|
|
+
|
|
|
+ for (i = 0; i < endbuf; i++)
|
|
|
+ jarray.push(filebuf[i]);
|
|
|
+
|
|
|
+ jarray.push(filebuf[i].subarray(0, tail));
|
|
|
+
|
|
|
+ return joinarray(jarray);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* parsefile (file)
|
|
|
+ takes an arraybuffer or uint8array of encrypted file and returns position of items in head
|
|
|
+ and start of the body
|
|
|
+ also checks format of file and returns errors;
|
|
|
+ */
|
|
|
+
|
|
|
+ var parsefile = function (file) {
|
|
|
+ var bufi, credblock, credext, credextsize, head, body;
|
|
|
+
|
|
|
+ //make sure we have a uint8array
|
|
|
+ file = toU8(file);
|
|
|
+
|
|
|
+ if (file === undefined)
|
|
|
+ return { data: file, error: "bad input" };
|
|
|
+
|
|
|
+ //check first 4 bytes
|
|
|
+ if (!(file[0] == 0x41 && file[1] == 0x45 && file[2] == 0x53)) {
|
|
|
+ //return error
|
|
|
+ //console.log("bad magic");
|
|
|
+ return { data: file, error: "bad magic" }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (file[3] != 2) {
|
|
|
+ //return error
|
|
|
+ //console.log("wrong file version, only supports v2");
|
|
|
+ return { data: file, error: "wrong file version, only supports v2" }
|
|
|
+ }
|
|
|
+
|
|
|
+ //find our extension, skip past rest
|
|
|
+ bufi = 5;
|
|
|
+ while (file[bufi] != 0 || file[bufi + 1] != 0) {
|
|
|
+ var sig, size = (file[bufi] << 8) + file[bufi + 1];
|
|
|
+ if (size > 17) {
|
|
|
+ sig = arrayToString(file.subarray(bufi + 2, bufi + 17));
|
|
|
+ if (sig == "enckeyblk v 0.1") {
|
|
|
+ credext = bufi + 18;
|
|
|
+ credextsize = size - 16;
|
|
|
+ }
|
|
|
+ bufi += size + 2;
|
|
|
+ }
|
|
|
+ if (bufi > file.length) {
|
|
|
+ //return error
|
|
|
+ //console.log("error finding end of extensions");
|
|
|
+ return { data: file, error: "error finding end of extensions" };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ bufi += 2;
|
|
|
+ credblock = bufi;
|
|
|
+ return { data: file, credblock: credblock, credext: credext, credextsize: credextsize, datastart: credblock + 96, error: '' };
|
|
|
+ }
|
|
|
+ /** Delete passwords from the file, Not part of the aescrypt 02 format standard */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Delete passwords from aescrypt.js extended aescrypt 02 formatted file.
|
|
|
+ *
|
|
|
+ * Behavior differs from other functions. If error, still returns the encoded file back unaltered.
|
|
|
+ *
|
|
|
+ * @param encfile the bytes to encrypt (either encoded as String, one byte per
|
|
|
+ * character, or as an ArrayBuffer or a Uint8Array).
|
|
|
+ *
|
|
|
+ * @param pass password String that was used for encryption
|
|
|
+ *
|
|
|
+ * @param delblockarray array of ints between 0-15 specifying password-encrypted key slots to delete
|
|
|
+ * or array of strings containing passwords of passwords-encrypted key to delete.
|
|
|
+ *
|
|
|
+ * @param requirepass whether to use password to confirm ownership of file
|
|
|
+ * If set to false, blocks will be erased without confirming that pass will decrypt file
|
|
|
+ */
|
|
|
+
|
|
|
+ var delpass = function (encfile, pass, delblockarray, requirepass) {
|
|
|
+ var file, error, newblock, emptyblock,
|
|
|
+ key = "", encdata, data, i = 0, index = [], fileinfo
|
|
|
+ var blankarray = new Uint8Array(96);
|
|
|
+
|
|
|
+
|
|
|
+ //sanity check
|
|
|
+ if (toType(delblockarray) == 'array') {
|
|
|
+ var isnum = false;
|
|
|
+ var isstring = false;
|
|
|
+ for (var i = 0; i < delblockarray.length; i++) {
|
|
|
+ var x = delblockarray[i];
|
|
|
+ if (Math.round(x) == x) {
|
|
|
+ if (x > -1 && x < 682)
|
|
|
+ isnum = true;
|
|
|
+ //in case someone has a password of all digits (baaaaaad), lets hope the number is less than 683
|
|
|
+ else
|
|
|
+ isstring = true;
|
|
|
+ }
|
|
|
+ //we'll skip empty strings below
|
|
|
+ else if (x != '')
|
|
|
+ isstring = true;
|
|
|
+ }
|
|
|
+ // we will only handle an array of all strings or all numbers
|
|
|
+ if ((isnum && isstring) || (!isnum && !isstring))
|
|
|
+ return { data: file, error: "delblockarray must be an array of all numbers between 0 and 682 inclusive, or an array of all password strings" }
|
|
|
+ } else {
|
|
|
+ return { data: file, error: "delblockarray is not an array" };
|
|
|
+ }
|
|
|
+
|
|
|
+ //{data:file, credblock: credblock, credext: credext, credextsize: credextsize, datastart: credblock+96, error: ''}
|
|
|
+ //end of extensions tag (0x0000) is at credblock-2;
|
|
|
+ //begin of extended credblock extension, including size bytes is at credext-18;
|
|
|
+
|
|
|
+ fileinfo = parsefile(encfile);
|
|
|
+ if (fileinfo.error != '') return fileinfo;
|
|
|
+
|
|
|
+ file = fileinfo.data;
|
|
|
+ if (file === undefined)
|
|
|
+ return { data: file, error: "bad input" };
|
|
|
+
|
|
|
+ if (fileinfo.credext === undefined) {
|
|
|
+ //Nothing to do here
|
|
|
+ return { data: file, error: "" };
|
|
|
+ }
|
|
|
+
|
|
|
+ // default is to require password
|
|
|
+ if (requirepass !== false) {
|
|
|
+ key = getanykey(file, pass, false, fileinfo.credext - 18);
|
|
|
+ if (key.error != "")
|
|
|
+ return { data: file, error: key.error };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isstring) {
|
|
|
+ //find blocks matching passwords and delete it;
|
|
|
+ var x = [];
|
|
|
+ for (var i = 0; i < delblockarray.length; i++) {
|
|
|
+ var key = getanykey(file, delblockarray[i], true, fileinfo.credext - 18, true);//second true means delete password
|
|
|
+ if (key.error == '') {
|
|
|
+ x.push(key.index);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return { data: file, error: "", index: x };
|
|
|
+ }
|
|
|
+
|
|
|
+ // doesn't actually check for password, only deletes.
|
|
|
+ var key = getanykey(file, '', true, fileinfo.credext - 18, delblockarray);
|
|
|
+
|
|
|
+ return { data: file, error: "", index: key.index };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /** Add another password to the file, Not part of the aescrypt 02 format standard */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Add Password to aescrypt.js extended aescrypt 02 formatted file
|
|
|
+ *
|
|
|
+ * Behavior differs from aes[en|de]crypt. On error, return unaltered encrypted file
|
|
|
+ *
|
|
|
+ * @param encfile the bytes to encrypt (either encoded as String, one byte per
|
|
|
+ * character, or as an ArrayBuffer or a Uint8Array).
|
|
|
+ *
|
|
|
+ * @param pass password String that was used for encryption
|
|
|
+ *
|
|
|
+ * @param newpass password String to add to this file.
|
|
|
+ */
|
|
|
+
|
|
|
+ var addpass = function (encfile, pass, newpass) {
|
|
|
+ var file, error, newblock, emptyblock, fileinfo,
|
|
|
+ key, i = 0, j = 0, index;
|
|
|
+
|
|
|
+ fileinfo = parsefile(encfile);
|
|
|
+ if (fileinfo.error != '') return fileinfo;
|
|
|
+
|
|
|
+ //{data:file, credblock: credblock, credext: credext, credextsize: credextsize, datastart: credblock+96, error: ''}
|
|
|
+ //end of extensions tag (0x0000) is at credblock-2;
|
|
|
+ //begin of extended credblock extension, including size bytes is at credext-18;
|
|
|
+
|
|
|
+ file = fileinfo.data;
|
|
|
+
|
|
|
+ // get a (the) key using this password. Since we know the start of the extended credential extension, skip to that position
|
|
|
+ key = getanykey(file, pass, false, fileinfo.credext - 18);
|
|
|
+ if (key.error != "")
|
|
|
+ return { data: file, error: key.error };
|
|
|
+
|
|
|
+ newblock = newcredentialblock(newpass, key.key, key.fileiv);
|
|
|
+ if (newblock == "") return { data: file, error: "unable to make new password entry for file" };
|
|
|
+
|
|
|
+ if (fileinfo.credext === undefined) {
|
|
|
+ //we have to write a new file with room for extended credential block with 16 key slots (18 + (16*96))==1554
|
|
|
+ var endofext = fileinfo.credblock - 2;
|
|
|
+ var newfile = new Uint8Array(file.length + 1554);
|
|
|
+ // copy all of head up to but not including end of extension tag (0x0000)
|
|
|
+ copyToArray(file, newfile, 0, endofext);
|
|
|
+ //create a new credential block using newpass
|
|
|
+ //create entry for extended credential extension
|
|
|
+ copyToArray((String.fromCharCode(6) + String.fromCharCode(16) + "enckeyblk v 0.1" + String.fromCharCode(0)),
|
|
|
+ newfile, endofext);
|
|
|
+ //copy in the new credential block
|
|
|
+ copyToArray(newblock.buffer, newfile, endofext + 18);
|
|
|
+ //copy rest of data from original file starting with the end of extensions tag 0x0000
|
|
|
+ copyToArray(file.subarray(endofext), newfile, endofext + 1554);
|
|
|
+ return { data: newfile, index: 0, error: "" };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //find first empty block
|
|
|
+ j = 0;
|
|
|
+ for (var i = 0; i < fileinfo.credextsize; i += 96) {
|
|
|
+ var start = i + fileinfo.credext;
|
|
|
+ if (isblank(file, start, 96)) {
|
|
|
+ emptyblock = start;
|
|
|
+ index = j;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ j++;
|
|
|
+ }
|
|
|
+ // TODO: extend it again if full.
|
|
|
+ if (emptyblock === undefined)
|
|
|
+ return { data: file, error: "error: all keyslots are in use" };
|
|
|
+
|
|
|
+ copyToArray(newblock.buffer, file, emptyblock);
|
|
|
+ return { data: file, index: index, error: "" };
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Decrypt data in the aescrypt 02 format (ver 1 and 0 not supported) */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Decrypt encfile using password where encfile is in aescrypt 02 format
|
|
|
+ * Return an ArrayBuffer of the plaintext file or a binary string
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * @param encfile the bytes to encrypt (either encoded as String, one byte per
|
|
|
+ * character, or as an ArrayBuffer or Typed Array).
|
|
|
+ *
|
|
|
+ * @param pass password String that was used for encryption
|
|
|
+ *
|
|
|
+ * @param returnstring boolean where if true function returns a binary string
|
|
|
+ *
|
|
|
+ * @param cb, callback with results
|
|
|
+ */
|
|
|
+
|
|
|
+ var aesdecrypt = function (encfile, pass, returnstring, cb) {
|
|
|
+ var emptydata = new Uint8Array(0);
|
|
|
+ //var usemod=true;
|
|
|
+
|
|
|
+ //take a uint8array, arraybuffer or string for file
|
|
|
+ var file = toU8(encfile);
|
|
|
+ if (file === undefined)
|
|
|
+ return { data: emptydata, error: "bad input" };
|
|
|
+
|
|
|
+
|
|
|
+ /* testing:
|
|
|
+ {
|
|
|
+ //var filebuf=newbytebuf(file);
|
|
|
+ //or
|
|
|
+ var filebuf=newbytebuf(file).done();
|
|
|
+
|
|
|
+ var key=decryptparsehead( filebuf, pass );
|
|
|
+ var decryptor=decryptstart(key);
|
|
|
+ // end of string is set with .done() above, so only need one pass in decryptpayload();
|
|
|
+ //var decrypted=decryptpayload(filebuf,decryptor);
|
|
|
+ // while (decrypted>0) decrypted=decryptpayload(filebuf,decryptor);
|
|
|
+ //filebuf.done();
|
|
|
+ var decrypted=decryptpayload(filebuf,decryptor);
|
|
|
+ var error=decryptfinish(filebuf,decryptor);
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("test over");
|
|
|
+ */
|
|
|
+
|
|
|
+ var key = decrypthead(file, pass);
|
|
|
+ if (key.error != '') return { data: emptydata, error: key.error };
|
|
|
+
|
|
|
+ if (crypto && crypto.subtle && typeof cb == 'function') {
|
|
|
+ var cr = crypto.subtle;
|
|
|
+
|
|
|
+ function checkhmac(dec) {
|
|
|
+ cr.importKey(
|
|
|
+ 'raw',
|
|
|
+ toU8(key.key),
|
|
|
+ { name: "HMAC", hash: { name: "SHA-256" } },
|
|
|
+ false,
|
|
|
+ ["sign", "verify"]
|
|
|
+ ).then(function (k) {
|
|
|
+ cr.verify(
|
|
|
+ { name: "HMAC" },
|
|
|
+ k,
|
|
|
+ file.subarray(-32),
|
|
|
+ file.subarray(key.datastart, -33)
|
|
|
+ ).then(function (valid) {
|
|
|
+ if (valid) cb({ data: dec, error: '' });
|
|
|
+ else cb({ data: dec, error: "hmac does not match. Likely file corruption or tampering." })
|
|
|
+ }).catch(function (e) {
|
|
|
+ //console.log(e);
|
|
|
+ cb({ data: emptydata, error: e.message });
|
|
|
+ });;
|
|
|
+ }).catch(function (e) {
|
|
|
+ //console.log(e);
|
|
|
+ cb({ data: emptydata, error: e.message });
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+ cr.importKey(
|
|
|
+ 'raw',
|
|
|
+ toU8(key.key),
|
|
|
+ { name: "AES-CBC" },
|
|
|
+ false,
|
|
|
+ ["encrypt", "decrypt"]
|
|
|
+ ).then(function (k) {
|
|
|
+ var modbyte = file[file.length - 33];
|
|
|
+ var subfile = file.subarray(key.datastart, -33);
|
|
|
+ //console.log(modbyte);
|
|
|
+ if (modbyte == 0) {
|
|
|
+ //we can cheat our way out of the fact that the aescrypt file format fails to put padding on
|
|
|
+ //a size%16==0 sized file with forge api, but not with webcrypto api
|
|
|
+ //so just use forge instead.
|
|
|
+ cb(aesdecrypt(encfile, pass, returnstring));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ cr.decrypt(
|
|
|
+ { name: "AES-CBC", iv: toU8(key.fileiv) },
|
|
|
+ k,
|
|
|
+ subfile
|
|
|
+ ).then(function (decrypted) {
|
|
|
+ decrypted = (new Uint8Array(decrypted)).subarray(0, (modbyte - 16));
|
|
|
+ checkhmac(decrypted);
|
|
|
+ }).catch(function (e) {
|
|
|
+ //for (var x in e)
|
|
|
+ // console.log(x+'='+e[x]);
|
|
|
+ // in case we choke somewhere. Orignally was for above mentioned aescrypt padding problem.
|
|
|
+ cb(aesdecrypt(encfile, pass, returnstring)); //try with forge
|
|
|
+ })
|
|
|
+ ;
|
|
|
+ }).catch(function (e) {
|
|
|
+ //console.log(e);
|
|
|
+ cb({ data: emptydata, error: e.message });
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ var decryptor = decryptstart(key);
|
|
|
+
|
|
|
+ var decrypted = decryptpayload(file, decryptor, key.datastart);
|
|
|
+
|
|
|
+ if (decrypted == -1) return { data: new Uint8Array(0), error: "file corrupted (invalid length)" };
|
|
|
+
|
|
|
+ var error = decryptfinish(file, decryptor, true);
|
|
|
+ if (returnstring)
|
|
|
+ return { data: decryptor.decipher.output.data, error: error };
|
|
|
+ else {
|
|
|
+ var a = new Uint8Array(decryptor.decipher.output.data.length);
|
|
|
+ copyToArray(decryptor.decipher.output.data, a);
|
|
|
+ return { data: a, error: error };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // do head in one go
|
|
|
+ function decrypthead(encfile, pass) {
|
|
|
+ var file, fileinfo, key;
|
|
|
+
|
|
|
+ //{data:file, credblock: credblock, credext: credext, credextsize: credextsize, datastart: credblock+96, error: ''}
|
|
|
+ //end of extensions tag (0x0000) is at credblock-2;
|
|
|
+ //begin of extended credblock extension, including size bytes is at credext-18;
|
|
|
+
|
|
|
+ fileinfo = parsefile(encfile);
|
|
|
+ if (fileinfo.error != '') return fileinfo;
|
|
|
+
|
|
|
+ file = fileinfo.data;
|
|
|
+ if (file === undefined)
|
|
|
+ return { data: file, error: "bad input" };
|
|
|
+ if (fileinfo.credext) {
|
|
|
+ key = getanykey(file, pass, false, fileinfo.credext - 18);
|
|
|
+ } else if (fileinfo.credblock) {
|
|
|
+ key = getkey(file.subarray(fileinfo.credblock, fileinfo.credblock + 96), pass);
|
|
|
+ }
|
|
|
+ else return ({ error: "Could not parse encrypted file" });
|
|
|
+ key.datastart = fileinfo.datastart;
|
|
|
+
|
|
|
+ return key;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // parse head of file and get key
|
|
|
+ // can be done in stages as more data is put into filebuf
|
|
|
+ // cant use parsefile() if we really want to be able to handle incoming a byte at a time
|
|
|
+ function decryptparsehead(filebuf, pass, progress) {
|
|
|
+ if (progress === undefined) progress = { stage: 0 };
|
|
|
+ //check first 5 bytes
|
|
|
+ if (progress.stage == 0) {
|
|
|
+ //need 5 bytes for stage 1;
|
|
|
+ var file = filebuf.get(5);
|
|
|
+ if (file == undefined) return progress;
|
|
|
+
|
|
|
+ if (!(file[0] == 0x41 && file[1] == 0x45 && file[2] == 0x53)) {
|
|
|
+ //return error
|
|
|
+ //console.log("bad magic");
|
|
|
+ return { error: "bad magic" }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (file[3] != 2) {
|
|
|
+ //return error
|
|
|
+ //console.log("wrong file version, only supports v2");
|
|
|
+ return { error: "wrong file version, only supports v2" }
|
|
|
+ }
|
|
|
+ progress.stage = 1;
|
|
|
+ }
|
|
|
+ if (progress.stage == 1) {
|
|
|
+ file = filebuf.preview(2);
|
|
|
+ if (file === undefined) return progress;
|
|
|
+ while (file[0] != 0 || file[1] != 0) {
|
|
|
+ var sig = '';
|
|
|
+ var extsize = (file[0] << 8) + file[1] + 2;
|
|
|
+ var lengthbytes = file;
|
|
|
+ file = filebuf.get(extsize);
|
|
|
+ if (file === undefined) return progress;
|
|
|
+ // look for extended credential block
|
|
|
+ if (file.length > 17) {
|
|
|
+ sig = arrayToString(file.subarray(2, 17));
|
|
|
+ if (sig == "enckeyblk v 0.1") {
|
|
|
+ var k = getanykey(file, pass, true, 0);
|
|
|
+ if (k.error == "") progress.key = k;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ file = filebuf.preview(2);
|
|
|
+ if (file === undefined) return progress;
|
|
|
+ }
|
|
|
+ file = filebuf.get(2); //skip past the final 0x0000
|
|
|
+ progress.stage = 2;
|
|
|
+ }
|
|
|
+ if (progress.stage == 2) {
|
|
|
+ // this is our main credential block containing
|
|
|
+ // keyiv, encrypted key and file iv and hmac of ( encrypted key + file iv)
|
|
|
+ file = filebuf.get(96);
|
|
|
+ if (file === undefined) return progress;
|
|
|
+ if (progress.key) {
|
|
|
+ progress.key.stage = 3;
|
|
|
+ return progress.key
|
|
|
+ } else {
|
|
|
+ var key = getkey(file, pass);
|
|
|
+ key.stage = 3;
|
|
|
+ return key;
|
|
|
+ }
|
|
|
+ //this is the end of the header section
|
|
|
+ //encrypted data is next
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // set up decipher, hmac and an object to hold them an output array
|
|
|
+ function decryptstart(key) {
|
|
|
+ var decipher = forge.cipher.createDecipher('AES-CBC', key.key);
|
|
|
+ var hmac = forge.hmac.create();
|
|
|
+ decipher.start({ iv: key.fileiv });
|
|
|
+ hmac.start('sha256', key.key);
|
|
|
+ return { decipher: decipher, hmac: hmac, data: newbytebuf() };
|
|
|
+ }
|
|
|
+
|
|
|
+ // decrypt 64 bytes at a time
|
|
|
+ // either the buffer will need to contain the whole file, or we will
|
|
|
+ // probably need to feed buffer 128 bytes at a time until the end of file, or it will all break down
|
|
|
+ // if position is defined, we assume file is a uint8array and data starts at pos
|
|
|
+ function decryptpayload(filebuf, decryptor, pos) {
|
|
|
+
|
|
|
+ var block;
|
|
|
+ var len = filebuf.length;
|
|
|
+ // 49 bytes is the minimum payload size (16 bytes encrypted data + 1 mod byte + 32 byte hmac)
|
|
|
+ // if we are at the end of file, just encrypt what we have left.
|
|
|
+ // if filebuf is an uint8array, pos will/should be defined
|
|
|
+ if (filebuf.eof || pos !== undefined) {
|
|
|
+ //console.log("got eof, doing rest of file, len="+len);
|
|
|
+
|
|
|
+ //this should only be 33 or more.
|
|
|
+ if (len < 33) return -1;
|
|
|
+ if (len == 33) return 0;
|
|
|
+ //get all but the last 33 bytes
|
|
|
+ len -= 33;
|
|
|
+ if (pos !== undefined)
|
|
|
+ block = filebuf.subarray(pos, -33);
|
|
|
+ else
|
|
|
+ block = filebuf.get(len);
|
|
|
+ //console.log("starting decrypt");
|
|
|
+ decryptor.decipher.update(forge.util.createBuffer(block));
|
|
|
+ //decryptor.hmac.update(arrayToString(block));
|
|
|
+ //console.log("starting hmac calc");
|
|
|
+ hmacchunkblock(decryptor.hmac, block);
|
|
|
+ //console.log("done with decryption");
|
|
|
+ return (len);
|
|
|
+
|
|
|
+ // if no end of file, then leave at least 33 bytes after this round
|
|
|
+ } else if (len > 97) {
|
|
|
+ //console.log("no eof, leaving at least 33 in buf");
|
|
|
+ len = len - 33;
|
|
|
+ len = len - len % 64
|
|
|
+ //console.log("decrypting "+len+" bytes")
|
|
|
+ block = filebuf.get(len);
|
|
|
+ //now minimum in buffer is 33
|
|
|
+ decryptor.decipher.update(forge.util.createBuffer(block));
|
|
|
+ //decryptor.hmac.update(arrayToString(block));
|
|
|
+ hmacchunkblock(decryptor.hmac, block);
|
|
|
+ return (len);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // if pos == true, filebuf is uint8array
|
|
|
+ function decryptfinish(filebuf, decryptor, pos) {
|
|
|
+ var hdat, hdatcomp, modbyte;
|
|
|
+
|
|
|
+ if (pos !== true) {
|
|
|
+ modbyte = filebuf.get(1);
|
|
|
+ if (modbyte === undefined) return "file corrupted (no modbyte)";
|
|
|
+ modbyte = modbyte[0];
|
|
|
+ hdat = arrayToString(filebuf.get(-1));
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ pos = filebuf.length - 33;
|
|
|
+ modbyte = filebuf[pos];
|
|
|
+ hdat = arrayToString(filebuf.subarray(pos + 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!hdat || hdat.length != 32) return "file corrupted (invalid hmac block)";
|
|
|
+
|
|
|
+ hdatcomp = decryptor.hmac.digest().data
|
|
|
+ if (hdat != hdatcomp) {
|
|
|
+ return "hmac does not match. Likely file corruption or tampering.";
|
|
|
+ }
|
|
|
+ if (modbyte != 0)
|
|
|
+ decryptor.decipher.output.data = decryptor.decipher.output.data.slice(
|
|
|
+ 0, (modbyte - 16)
|
|
|
+ );
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /** Encrypt data in the aescrypt 02 format */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Encrypts filecontents with pass and returns an ArrayBuffer containing a file encrypted in the aescrypt format 02.
|
|
|
+ *
|
|
|
+ * @param filecontents the bytes to encrypt (either encoded as String, one byte per
|
|
|
+ * character, or as an ArrayBuffer or Typed Array).
|
|
|
+ *
|
|
|
+ * @param pass password String used for encryption
|
|
|
+ *
|
|
|
+ * @param returnstring boolean if true return a binary string instead of a uint8arrray
|
|
|
+ *
|
|
|
+ * @param slotn number of slots for extra passwords (default 16)
|
|
|
+ *
|
|
|
+ * @param cb callback to receive encrypted data
|
|
|
+ */
|
|
|
+
|
|
|
+ var aesencrypt = function (filecontents, pass, returnstring, slotn, cb) {
|
|
|
+
|
|
|
+ var emptydata = new Uint8Array(0);
|
|
|
+
|
|
|
+ if (crypto && crypto.subtle && typeof cb == 'function') {
|
|
|
+ var cr = crypto.subtle;
|
|
|
+ var sd = encryptstart(pass, slotn, true);
|
|
|
+ console.log("WEBCRYPTO");
|
|
|
+ function cryptofinish(enc, hdat, head, mod) {
|
|
|
+ var output, bufi,
|
|
|
+ modbyte = String.fromCharCode(mod);
|
|
|
+
|
|
|
+ output = newbytebuf();
|
|
|
+ output.put(head);
|
|
|
+ output.put(enc);
|
|
|
+ output.put(modbyte);
|
|
|
+ output.put(hdat);
|
|
|
+
|
|
|
+ output = output.get(-1);
|
|
|
+ //console.log(output.length);
|
|
|
+ return { data: output, error: "" };
|
|
|
+ }
|
|
|
+
|
|
|
+ function makehmac(enc) {
|
|
|
+ cr.importKey(
|
|
|
+ 'raw',
|
|
|
+ toU8(sd.key),
|
|
|
+ { name: "HMAC", hash: { name: "SHA-256" } },
|
|
|
+ false,
|
|
|
+ ["sign", "verify"]
|
|
|
+ ).then(function (k) {
|
|
|
+ //console.log("key imported for hmac");
|
|
|
+ var mod = filecontents.length % 16;
|
|
|
+ if (mod == 0) enc = enc.subarray(0, -16);
|
|
|
+ cr.sign(
|
|
|
+ { name: "HMAC" },
|
|
|
+ k,
|
|
|
+ enc
|
|
|
+ ).then(function (sig) {
|
|
|
+ //console.log("finishing file");
|
|
|
+ cb(cryptofinish(enc, sig, sd.head, mod));
|
|
|
+ })/*.catch(function(e) {
|
|
|
+ console.log(e);
|
|
|
+ cb({data:emptydata,error:e.message});
|
|
|
+ });*/;
|
|
|
+ }).catch(function (e) {
|
|
|
+ //for (var x in e)
|
|
|
+ // console.log(x+'='+e[x]);
|
|
|
+ cb({ data: emptydata, error: e.message });
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ cr.importKey(
|
|
|
+ 'raw',
|
|
|
+ toU8(sd.key),
|
|
|
+ { name: "AES-CBC" },
|
|
|
+ false,
|
|
|
+ ["encrypt", "decrypt"]
|
|
|
+ ).then(function (k) {
|
|
|
+ cr.encrypt(
|
|
|
+ { name: "AES-CBC", iv: toU8(sd.fileiv) },
|
|
|
+ k,
|
|
|
+ toU8(filecontents)
|
|
|
+ ).then(function (encrypted) {
|
|
|
+ //console.log(encrypted.byteLength);
|
|
|
+ makehmac(new Uint8Array(encrypted));
|
|
|
+ }).catch(function (e) {
|
|
|
+ //for (var x in e)
|
|
|
+ // console.log(x+'='+e[x]);
|
|
|
+ cb(aesencrypt(filecontents, pass, returnstring, slotn)); //try with forge
|
|
|
+ })
|
|
|
+ ;
|
|
|
+ }).catch(function (e) {
|
|
|
+ //console.log(e);
|
|
|
+ cb({ data: emptydata, error: e.message });
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // the forge version
|
|
|
+ var startdata = encryptstart(pass, slotn);
|
|
|
+ var mod = encryptupdate(startdata.cipher, startdata.hmac, filecontents);
|
|
|
+ mod %= 16;
|
|
|
+ return encryptfinish(startdata.cipher, startdata.hmac, startdata.head, mod, returnstring);
|
|
|
+ }
|
|
|
+
|
|
|
+ //* set up file header and return keys and iv
|
|
|
+ function encryptstart(pass, extrakeyslots, noforge) {
|
|
|
+ var headstart = new Uint8Array(5);
|
|
|
+ var endext = new Uint8Array(2);//0x0000 for no more extensions
|
|
|
+ var extensions = [];
|
|
|
+ var bufarray = new ArrayBuffer(96);
|
|
|
+ var bufi = 0;
|
|
|
+ var buffer = new Uint8Array(bufarray);
|
|
|
+ var cred = '';
|
|
|
+ var keyiv, extlen = 0, output,
|
|
|
+ i = 0, //blank=new String;
|
|
|
+ blank;
|
|
|
+ var emptydata = new ArrayBuffer(0);
|
|
|
+ var cipher, hmac, extrapasses = [];
|
|
|
+ //console.log(toType(pass));
|
|
|
+ if (toType(pass) == 'array') {
|
|
|
+ extrapasses = pass;
|
|
|
+ pass = extrapasses.shift();
|
|
|
+ //console.log(pass);
|
|
|
+ //console.log(extrapasses);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // TODO: support for other than 16, and for extending a block that is too small to fit another
|
|
|
+ // password (in addpass()). Right now, a code review of other projects would be required
|
|
|
+ // since they might rely on file header size being constant (dunno, hence need for review).
|
|
|
+ // if (extrakeyslots===undefined)
|
|
|
+ extrakeyslots = 16;
|
|
|
+ // max size of extension is 65536
|
|
|
+ if (extrakeyslots > 682) extrakeyslots = 682;
|
|
|
+ copyToArray("AES", headstart, 0);
|
|
|
+ headstart[3] = 2;
|
|
|
+
|
|
|
+ //headstart[4]=0; //aready 0;
|
|
|
+
|
|
|
+ // make credentials
|
|
|
+ //this is now mostly solved and should be successful on every attempt
|
|
|
+ var i = 0;
|
|
|
+ while (cred == '' && i++ < 9) {
|
|
|
+ //if(i>1) console.log("FAILED making new credential block");
|
|
|
+ cred = newcredentialblock(pass);
|
|
|
+ }
|
|
|
+ if (cred == '') {
|
|
|
+ //console.log("tried 8 time, bailing...");
|
|
|
+ return { head: emptydata, error: "could not make a new set of credentials" };
|
|
|
+ }
|
|
|
+
|
|
|
+ /* **** add extensions here **** */
|
|
|
+
|
|
|
+ extensions.push(makeext(["CREATED_BY", "aescrypt.js 0.1"]));
|
|
|
+
|
|
|
+ //add blank 128 byte extension
|
|
|
+
|
|
|
+ blank = new Uint8Array(130);
|
|
|
+ blank.set([128], 1);
|
|
|
+ extensions.push(blank);
|
|
|
+
|
|
|
+ //add extended credential block
|
|
|
+ //and add extra passwords, if available;
|
|
|
+ if (extrakeyslots > 0) {
|
|
|
+ var csize = 16 + (96 * extrakeyslots);
|
|
|
+ var h = String.fromCharCode(csize >>> 8) + String.fromCharCode(csize & 0xFF) + "enckeyblk v 0.1" + String.fromCharCode(0);
|
|
|
+ blank = new Uint8Array(csize + 2);
|
|
|
+ copyToArray(h, blank);
|
|
|
+ var len = (extrapasses.length < 16) ? extrapasses.length : 16;
|
|
|
+ for (var i = 0; i < len; i++) {
|
|
|
+ var newpass = extrapasses[i],
|
|
|
+ newblock = newcredentialblock(newpass, cred.key, cred.fileiv),
|
|
|
+ start = 18 + (i * 96);
|
|
|
+
|
|
|
+ copyToArray(newblock.buffer, blank, start);
|
|
|
+ }
|
|
|
+ extensions.push(blank);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ blank=String.fromCharCode(0)+String.fromCharCode(128);
|
|
|
+ for (var i=0;i<128;i++)
|
|
|
+ blank+=String.fromCharCode(0);
|
|
|
+ extensions.push(blank);
|
|
|
+
|
|
|
+ //add an area for 16 96-byte blocks for extra keyiv-enckeyblk-hmac combos ("credential blocks")
|
|
|
+ blank=String.fromCharCode(06)+String.fromCharCode(16)+"enckeyblk v 0.1"+String.fromCharCode(0);
|
|
|
+ for (var i=0;i<1536;i++)
|
|
|
+ blank+=String.fromCharCode(0);
|
|
|
+ extensions.push(blank);
|
|
|
+ */
|
|
|
+
|
|
|
+
|
|
|
+ /* **** end add extensions **** */
|
|
|
+
|
|
|
+ //the last extension - 0x0000 to mark end of extensions
|
|
|
+ extensions.push(endext);
|
|
|
+
|
|
|
+ // get length of all extensions
|
|
|
+ for (var i = 0; i < extensions.length; i++)
|
|
|
+ extlen += extensions[i].length;
|
|
|
+
|
|
|
+ output = new Uint8Array(
|
|
|
+ headstart.length +
|
|
|
+ extlen +
|
|
|
+ cred.buffer.length
|
|
|
+ );
|
|
|
+
|
|
|
+ bufi = 0;
|
|
|
+ bufi += copyToArray(headstart, output, bufi);
|
|
|
+ for (var i = 0; i < extensions.length; i++)
|
|
|
+ bufi += copyToArray(extensions[i], output, bufi);
|
|
|
+ bufi += copyToArray(cred.buffer, output, bufi);
|
|
|
+
|
|
|
+ if (!noforge) {
|
|
|
+ cipher = forge.cipher.createCipher('AES-CBC', cred.key);
|
|
|
+ cipher.start({ iv: cred.fileiv });
|
|
|
+
|
|
|
+ hmac = forge.hmac.create();
|
|
|
+ hmac.start('sha256', cred.key);
|
|
|
+ return { head: output, error: "", cipher: cipher, hmac: hmac };
|
|
|
+ }
|
|
|
+
|
|
|
+ return { head: output, error: "", fileiv: cred.fileiv, key: cred.key };
|
|
|
+ }
|
|
|
+
|
|
|
+ function encryptupdate(cipher, hmac, data, newiv) {
|
|
|
+ var encdata;
|
|
|
+ // newiv is a misnomer since cipher.start({iv:newiv}) doesn't work here (not sure why)
|
|
|
+ if (newiv !== false && newiv !== undefined) {
|
|
|
+ //cipher.start({iv:newiv});
|
|
|
+ cipher.output.data = newiv;
|
|
|
+ cipher.update(forge.util.createBuffer(data));
|
|
|
+ cipher.output.data = cipher.output.data.slice(16);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ cipher.update(forge.util.createBuffer(data));
|
|
|
+
|
|
|
+ hmac.update(cipher.output.data);
|
|
|
+
|
|
|
+ return (data.length);
|
|
|
+ }
|
|
|
+
|
|
|
+ function encryptfinish(cipher, hmac, head, mod, returnstring) {
|
|
|
+ var output, len, bufi, hdat,
|
|
|
+ modbyte = String.fromCharCode(mod),
|
|
|
+ hlen = cipher.output.data.length;
|
|
|
+
|
|
|
+ cipher.finish();
|
|
|
+ if (mod != 0)
|
|
|
+ len = cipher.output.data.length;
|
|
|
+
|
|
|
+ // don't pad file that otherwise fits exactly into 16 byte blocks.
|
|
|
+ // forge needs the extra room for encoding a padding number in the plaintext (Section 10.3 of [RFC2315], step 2)
|
|
|
+ // aescrypt format has the padding number outside the encrypted text.
|
|
|
+ else
|
|
|
+ len = cipher.output.data.length - 16;
|
|
|
+
|
|
|
+ //how much extra data left to hmac, negative number or zero
|
|
|
+ hlen -= len;
|
|
|
+
|
|
|
+ //update our hmac with extra data
|
|
|
+ if (hlen < 0)
|
|
|
+ hmac.update(cipher.output.data.slice(hlen));
|
|
|
+
|
|
|
+ hdat = hmac.digest().data;
|
|
|
+
|
|
|
+
|
|
|
+ if (returnstring) {
|
|
|
+ // do not use with chunking
|
|
|
+ return {
|
|
|
+ data: ("").concat(arrayToString(head), cipher.output.data.slice(0, len), modbyte, hdat),
|
|
|
+ error: ""
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ output = new Uint8Array(
|
|
|
+ head.length +
|
|
|
+ len +
|
|
|
+ modbyte.length +
|
|
|
+ hdat.length
|
|
|
+ );
|
|
|
+ // if getChunk is used below, head will be "" and cipher.output.data
|
|
|
+ // will have been moved off and also be "", or a tail portion of it
|
|
|
+ bufi = 0;
|
|
|
+ bufi += copyToArray(head, output, bufi);
|
|
|
+ bufi += copyToArray(cipher.output.data, output, bufi, len);
|
|
|
+ bufi += copyToArray(modbyte, output, bufi);
|
|
|
+ bufi += copyToArray(hdat, output, bufi);
|
|
|
+ return { data: output, error: "" };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ var chunkencrypt = function (pass) {
|
|
|
+ return {
|
|
|
+ start: function (pass) {
|
|
|
+ var encstart = encryptstart(pass);
|
|
|
+ this.hmac = encstart.hmac;
|
|
|
+ this.cipher = encstart.cipher;
|
|
|
+ this.head = arrayToString(encstart.head);
|
|
|
+ this.newiv = false;
|
|
|
+ this.length = 0;
|
|
|
+ //there really should be no errors
|
|
|
+ this.error = encstart.error;
|
|
|
+ },
|
|
|
+ update: function (data) {
|
|
|
+ this.length += encryptupdate(this.cipher, this.hmac, data, this.newiv);
|
|
|
+ this.newiv = false;
|
|
|
+ return this;
|
|
|
+ },
|
|
|
+ getChunk: function (size) {
|
|
|
+ //return size must be a multiple of size
|
|
|
+
|
|
|
+ var ret, retsize;
|
|
|
+
|
|
|
+ if (this.leftover) {
|
|
|
+ this.cipher.output.data = this.leftover + this.cipher.output.data;
|
|
|
+ delete this.leftover;
|
|
|
+ }
|
|
|
+
|
|
|
+ retsize = parseInt((this.head.length + this.cipher.output.data.length) / size) * size;
|
|
|
+ //console.log("retsize="+retsize+ " or "+size+ ' * ' + parseInt( (this.head.length+this.cipher.output.data.length)/size ) );
|
|
|
+
|
|
|
+ if (retsize == 0) return;
|
|
|
+
|
|
|
+ if (retsize == this.head.length) {
|
|
|
+ ret = this.head;
|
|
|
+ } else if (retsize < this.head.length) {
|
|
|
+ ret = this.head.slice(0, retsize);
|
|
|
+ this.leftover = this.head.slice(retsize);
|
|
|
+ } else {
|
|
|
+ // concat head to cipher output to make desired size
|
|
|
+ if (this.head.length) {
|
|
|
+ retsize -= this.head.length;
|
|
|
+ ret = this.head + this.cipher.output.data.slice(0, retsize);
|
|
|
+ } else {
|
|
|
+ ret = this.cipher.output.data.slice(0, retsize);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.leftover = this.cipher.output.data.slice(retsize); //just add this on next time.
|
|
|
+ this.newiv = this.cipher.output.data.slice(-16); //put 16 bytes back in mix for next cbc mode computation
|
|
|
+ this.cipher.output.data = "";
|
|
|
+ }
|
|
|
+
|
|
|
+ this.head = '';
|
|
|
+ var reta = new Uint8Array(ret.length);
|
|
|
+ copyToArray(ret, reta);
|
|
|
+ return { data: reta, error: "" };
|
|
|
+
|
|
|
+
|
|
|
+ /*-------------------------- old way------------------------------------------
|
|
|
+ ret = this.head +
|
|
|
+ this.cipher.output.data;
|
|
|
+
|
|
|
+ // only update if we at least as much data as requsted
|
|
|
+ if (size && size > ret.length)
|
|
|
+ return;
|
|
|
+
|
|
|
+ var reta=new Uint8Array(ret.length);
|
|
|
+
|
|
|
+ if( ret.length>0)
|
|
|
+ copyToArray(ret,reta);
|
|
|
+ else
|
|
|
+ return;
|
|
|
+
|
|
|
+ // cbc mode needs last 16 bytes to compute next 16
|
|
|
+ // and we'll make adjustments in next update()
|
|
|
+ if (this.cipher.output.data.length) {
|
|
|
+ this.newiv=this.cipher.output.data.slice(-16);
|
|
|
+ this.cipher.output.data="";
|
|
|
+ }
|
|
|
+
|
|
|
+ this.head="";
|
|
|
+
|
|
|
+ return {data: reta, error: ""};
|
|
|
+ ----------------------------*/
|
|
|
+ },
|
|
|
+ finish: function () {
|
|
|
+ if (this.leftover) {
|
|
|
+ this.cipher.output.data = this.leftover + this.cipher.output.data;
|
|
|
+ delete this.leftover;
|
|
|
+ }
|
|
|
+ var f = encryptfinish(this.cipher, this.hmac, this.head, (this.length % 16));
|
|
|
+ return f;
|
|
|
+ },
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /*
|
|
|
+ var aesdecrypt=function(encfile,pass,returnstring) {
|
|
|
+ var emptydata=new ArrayBuffer(0);
|
|
|
+ //var usemod=true;
|
|
|
+
|
|
|
+ //take a uint8array, arraybuffer or string for file
|
|
|
+ file=toU8(encfile);
|
|
|
+ if (file===undefined)
|
|
|
+ return {data:emptydata, error:"bad input"};
|
|
|
+ var filebuf=newbytebuf(file).done();
|
|
|
+ var key=decryptparsehead( filebuf, pass );
|
|
|
+ if (key.error!='') return {data:emptydata, error: key.error};
|
|
|
+ var decryptor=decryptstart(key);
|
|
|
+
|
|
|
+ // end of string is set with .done() above, so only need one pass in decryptpayload();
|
|
|
+ var decrypted=decryptpayload(filebuf,decryptor);
|
|
|
+ if (decrypted == -1) return {data:new Uint8Array(0),error:"file corrupted"};
|
|
|
+
|
|
|
+ var error=decryptfinish(filebuf,decryptor);
|
|
|
+ if(returnstring)
|
|
|
+ return {data:decryptor.decipher.output.data, error: error};
|
|
|
+ else {
|
|
|
+ var a=new Uint8Array(decryptor.decipher.output.data.length);
|
|
|
+ copyToArray(decryptor.decipher.output.data,a);
|
|
|
+ return {data: a, error: error};
|
|
|
+ }
|
|
|
+ }
|
|
|
+*/
|
|
|
+ //data is not required in start;
|
|
|
+ var chunkdecrypt = function () {
|
|
|
+ return {
|
|
|
+ start: function (pass) {
|
|
|
+ if (!pass || pass == "") {
|
|
|
+ this.error = "password missing";
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ this.pass = pass;
|
|
|
+ this.progress = { stage: 0 };
|
|
|
+ this.filebuf = newbytebuf();
|
|
|
+ this.output = newbytebuf();
|
|
|
+ this.error = "";
|
|
|
+ this.lasterror = "";
|
|
|
+ delete this.decryptor;
|
|
|
+ },
|
|
|
+ update: function (data) {
|
|
|
+ // go no further on unrecoverable error. error message should be in this.error;
|
|
|
+ this.newoutput = false;
|
|
|
+ if (this.progress.stage == -1)
|
|
|
+ return this;
|
|
|
+
|
|
|
+ // push our data into the buffer
|
|
|
+ this.filebuf.put(data);
|
|
|
+ if (this.progress.stage < 3)
|
|
|
+ this.progress = decryptparsehead(this.filebuf, this.pass, this.progress);
|
|
|
+
|
|
|
+ if (this.progress.error) {
|
|
|
+ this.pass = '';
|
|
|
+ this.error = this.progress.error;
|
|
|
+ this.progress.stage = -1;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.progress.stage == 3) {
|
|
|
+ // we should have our key in this.progress.key
|
|
|
+ // this stage requires no data from filebuf
|
|
|
+
|
|
|
+ //dont need password lying around;
|
|
|
+ delete this.pass;
|
|
|
+ this.decryptor = decryptstart(this.progress);
|
|
|
+ this.progress.stage = 4;
|
|
|
+ //dont need key anylonger
|
|
|
+ delete this.key;
|
|
|
+ }
|
|
|
+ // kinda unnecessary since stage 3 requires no data
|
|
|
+ // but might want to put error checking into decryptstart some day.
|
|
|
+ if (this.progress.stage == 4) {
|
|
|
+ // decrypt whatever is in the buffer in multiples of 64 bytes, leaving at least 33 at end
|
|
|
+ var decrypted = decryptpayload(this.filebuf, this.decryptor);
|
|
|
+ if (decrypted > 0) {
|
|
|
+ this.output.put(this.decryptor.decipher.output.data);
|
|
|
+ this.decryptor.decipher.output.data = "";
|
|
|
+ this.newoutput = true;
|
|
|
+ } else if (decrypted == -1) {
|
|
|
+ this.error = "file corrupted (invalid length)";
|
|
|
+ this.progress.stage = -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ },
|
|
|
+ getChunk: function (size) {
|
|
|
+ var ret;
|
|
|
+ // get all of the buffer if size undefined
|
|
|
+ if (size === undefined) size = -1;
|
|
|
+ if (size == 0 || !this.output)
|
|
|
+ return;
|
|
|
+
|
|
|
+ ret = this.output.get(size);
|
|
|
+ // return undefined if no data
|
|
|
+ if (!ret || ret.length == 0) return;
|
|
|
+
|
|
|
+ if (this.output.eof && this.output.length == 0)
|
|
|
+ delete this.output;
|
|
|
+
|
|
|
+ return { data: ret, error: this.error };
|
|
|
+ },
|
|
|
+ finish: function () {
|
|
|
+ // mark eof
|
|
|
+ this.filebuf.done();
|
|
|
+ // finish off whatever is in the buffer
|
|
|
+ var decrypted = decryptpayload(this.filebuf, this.decryptor);
|
|
|
+ if (decrypted > 0) {
|
|
|
+ //check hmac and truncate
|
|
|
+ this.error = decryptfinish(this.filebuf, this.decryptor);
|
|
|
+ //copy output
|
|
|
+ this.output.put(this.decryptor.decipher.output.data);
|
|
|
+ //empty forge buffer
|
|
|
+ this.decryptor.decipher.output.data = "";
|
|
|
+ this.newoutput = true;
|
|
|
+ this.output.eof = true;
|
|
|
+ } else if (decrypted == -1) {
|
|
|
+ this.error = "file corrupted (invalid length)";
|
|
|
+ } else if (decrypted == 0) {
|
|
|
+ delete this.output;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.progress.stage = -1;
|
|
|
+
|
|
|
+ delete this.filebuf;
|
|
|
+ delete this.decryptor;
|
|
|
+ var lastchunk = this.getChunk();
|
|
|
+ if (lastchunk && lastchunk.data)
|
|
|
+ lastchunk = lastchunk.data;
|
|
|
+ return { data: lastchunk, error: this.error };
|
|
|
+ },
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ return ({
|
|
|
+ chunkEncrypt: chunkencrypt,
|
|
|
+ chunkDecrypt: chunkdecrypt,
|
|
|
+ encrypt: aesencrypt,
|
|
|
+ decrypt: aesdecrypt,
|
|
|
+ addPassword: addpass,
|
|
|
+ delPassword: delpass,
|
|
|
+ util: {
|
|
|
+ copyToArray: copyToArray,
|
|
|
+ arrayToString: arrayToString,
|
|
|
+ toType: toType,
|
|
|
+ newbytebuf: newbytebuf
|
|
|
+ }
|
|
|
+ });
|
|
|
+})();
|