diff --git a/README.md b/README.md
index 6a86d46..664ca46 100644
--- a/README.md
+++ b/README.md
@@ -21,11 +21,6 @@ Requirements
- CryptoJS (https://cryptojs.gitbook.io/docs/)
- core.js
- - cipher-core.js
- - aes.js
- - hmac.js
- - sha256.js
- - pad-nopadding.js
- enc-utf16.js
- enc-uint8array.js (Custom Uint8Array encoding)
@@ -39,25 +34,22 @@ const aes = AesCrypt();
let fileSecret = document.getElementById("fileSecret").files[0];
-var password = "foopassword"
+let password = "foopassword"
// encryption/decryption
-// **IMPORTANT** Only for workers!
-let progress_callback = (current) => {let percent = current * 100;};
-
// encrypt typed array (Uint8Array)
-aes.encrypt(fileSecret, password, progress_callback).then((encrypted) => {
+aes.encrypt(fileSecret, password).then((encrypted) => {
console.log(encrypted);
});
let fileEncrypted = document.getElementById("fileEncrypted").files[0];
// decrypt typed array (Uint8Array)
-aes.decrypt(fileEncrypted, password, progress_callback).then((decrypted) => {
+aes.decrypt(fileEncrypted, password).then((decrypted) => {
// transform Uint8Array to Latin1 string
- let secret = aes.utils.bytes_to_latin1(decrypted);
+ let secret = aes.utils.bytes2str(decrypted);
console.log(secret);
});
diff --git a/demo/index.html b/demo/index.html
index 81c82b9..4a92e60 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -2,11 +2,6 @@
-
-
-
-
-
diff --git a/demo/libs/aes_crypt.min.js b/demo/libs/aes_crypt.min.js
index fe679db..d0096e8 100644
--- a/demo/libs/aes_crypt.min.js
+++ b/demo/libs/aes_crypt.min.js
@@ -1 +1 @@
-AesCrypt=function(){let e={version:"0.13a",bufferSize:32768,fileFormatVersion:2,maxPassLen:1024,AESBlockSize:16};var t=function(e){let t=0,r=e.size,o=new FileReader,i=e;async function a(e){return await function(e){return new Promise((n,r)=>{let a=i.slice(t,t+=e);o.onload=(()=>{n(new Uint8Array(o.result))}),o.onerror=r,o.readAsArrayBuffer(a)})}(e)}return{readByte:async function(){return(await a(1))[0]},readBytes:a,readBytesAsInt:async function(e){let t=await a(e);return n.arrToInt(t)},readBytesAsString:async function(e){let t=await a(e);return n.bytes_to_latin1(t)},getCurrentPosition:function(){return t},getLength:function(){return r}}},n={random_int:function(e,t){return Math.floor(Math.random()*(t-e)+e)},urandom:function(e){let t="";for(let n=0;n("00"+e.toString(16)).slice(-2)).join(""),16)},bytes_to_latin1:function(e){return CryptoJS.enc.Latin1.stringify(CryptoJS.enc.Uint8Arr.parse(e))},encode_to_words:function(e,t="Latin1"){return CryptoJS.enc[t].parse(e)}};let r=function(){let e=new Uint8Array([]);return{appendBytes:function(t){let n;if("number"==typeof t){let e=t.toString(16);n=new Uint8Array(e.match(/.{1,2}/g).map(e=>parseInt(e,16)))}else if("string"==typeof t){n=new Uint8Array(t.length);for(let e=0;eparseInt(e,16)))}else if("string"==typeof e){t=new Uint8Array(e.length);for(let n=0;n{})){if(s.length>e.maxPassLen)return console.warn("Password is too long."),!1;const p=n.urandom(e.AESBlockSize),c=a(s,p),d=n.urandom(e.AESBlockSize),y=n.urandom(32),u=o(y,d),S=CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256,n.encode_to_words(y)),f=o(c,p),g=f.process(n.encode_to_words(d+y)).toString(CryptoJS.enc.Latin1)+f.finalize().toString(CryptoJS.enc.Latin1),w=CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256,n.encode_to_words(c));return w.update(n.encode_to_words(g)),await async function(o,i,a,s,l,p,c){let d=r();d.appendBytes("AES"),d.appendBytes(e.fileFormatVersion),d.appendBytes(0);const y="jsAesCrypt "+e.version;d.appendBytes(0),d.appendBytes(1+("CREATED_BY"+y).length),d.appendBytes("CREATED_BY"),d.appendBytes(0),d.appendBytes(y),d.appendBytes([0,128]),d.appendBytes(n.fillArray(0,128)),d.appendBytes([0,0]),d.appendBytes(i),d.appendBytes(a),d.appendBytes(l.finalize().toString(CryptoJS.enc.Latin1));let u=String.fromCharCode(0),S=new t(o);Math.ceil(S.getLength()/e.bufferSize);let f=Math.ceil((S.getLength()-S.getCurrentPosition())/e.bufferSize),g=0;for(;S.getCurrentPosition(){})){if(s.length>e.maxPassLen)return console.warn("Password is too long."),!1;let p=t(o);if("AES"!==await p.readBytesAsString(3)||p.getLength()<136)return console.warn("File is corrupted or not an AES Crypt \n(or jsAesCrypt) file."),!1;if(await p.readByte()!==e.fileFormatVersion)return console.warn("jsAesCrypt is only compatible with version \n2 of the AES Crypt file format."),!1;for(await p.readByte();;){let e=await p.readBytes(2);if(e.length<2)return console.warn("File is corrupted."),!1;if(0==(e=+n.arrToInt(e)))break;await p.readBytes(e)}let c=await p.readBytesAsString(16);if(16!==c.length)return console.warn("File is corrupted."),!1;let d=a(s,c),y=await p.readBytesAsString(48);if(48!==y.length)return console.warn("File is corrupted."),!1;let u=await p.readBytesAsString(32);if(32!==u.length)return console.warn("File is corrupted."),!1;let S=CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256,n.encode_to_words(d));if(S.update(n.encode_to_words(y)),u!==CryptoJS.enc.Latin1.stringify(S.finalize()))return console.warn("Wrong password (or file is corrupted)."),!1;let f=i(d,c),g=f.process(n.encode_to_words(y)).toString(CryptoJS.enc.Latin1)+f.finalize().toString(CryptoJS.enc.Latin1),w=g.substr(0,e.AESBlockSize),C=g.substr(e.AESBlockSize,32),A=i(C,w),B=CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256,n.encode_to_words(C)),h=r(),_=Math.ceil((p.getLength()-p.getCurrentPosition()-32-1-e.AESBlockSize)/e.bufferSize)+1,J=0;for(;p.getCurrentPosition()("00"+e.toString(16)).slice(-2)).join(""),16)},bytes2str:function(e){return CryptoJS.enc.Latin1.stringify(CryptoJS.enc.Uint8Arr.parse(e))},str2bytes:function(e,t="Latin1"){return CryptoJS.enc.Uint8Arr.decode(CryptoJS.enc[t].parse(e))}},n={_createKey:async function(e,t,n){return await crypto.subtle.importKey("raw",e.buffer,t,!1,n)},_webHashHMAC:async function(e,t){let n=await this._createKey(t,{name:"HMAC",hash:{name:"SHA-256"}},["sign","verify"]);return new Uint8Array(await crypto.subtle.sign("HMAC",n,e))},_webEncryptAes:async function(e,t,n,r=!0){let a=await this._createKey(t,"AES-CBC",["encrypt","decrypt"]),i=new Uint8Array(await crypto.subtle.encrypt({name:"AES-CBC",iv:n},a,e));return e.length%this.info.AESBlockSize==0&&1==r&&(i=i.slice(0,i.length-this.info.AESBlockSize)),i},_webHashSHA256:async function(e){return new Uint8Array(await crypto.subtle.digest("SHA-256",e.buffer))},_webDecryptAes:async function(e,t,n,r=0){let i=await this._createKey(t,"AES-CBC",["encrypt","decrypt"]),s=a(e);if(0==r){let r=new Uint8Array(this.info.AESBlockSize);for(let t=0;t{let s=a.slice(t,t+=e);r.onload=(()=>{n(new Uint8Array(r.result))}),r.onerror=i,r.readAsArrayBuffer(s)})}(e)}return{readByte:async function(){return(await i(1))[0]},readBytes:i,getCurrentPosition:function(){return t},getLength:function(){return n}}},a=function(e=[]){let t=new Uint8Array(e);return{appendBytes:function(e){let n;if("number"==typeof e){let t=e.toString(16);n=new Uint8Array(t.match(/.{1,2}/g).map(e=>parseInt(e,16)))}else if("string"==typeof e){n=new Uint8Array(e.length);for(let t=0;te.maxPassLen)return console.warn("Password is too long."),!1;const c=t.urandom(e.AESBlockSize),l=await i(o,c),y=t.urandom(e.AESBlockSize),p=t.urandom(32);let u=a();u.appendBytes(y),u.appendBytes(p);const f=await n._webEncryptAes(u.finalize(),l,c),w=await n._webHashHMAC(f,l);return await async function(t,i,s,o,c,l){let y=a();y.appendBytes("AES"),y.appendBytes(e.fileFormatVersion),y.appendBytes(0);const p="jsAesCrypt "+e.version;y.appendBytes(0),y.appendBytes(1+("CREATED_BY"+p).length),y.appendBytes("CREATED_BY"),y.appendBytes(0),y.appendBytes(p),y.appendBytes([0,128]),y.appendBytes("\0".repeat(128)),y.appendBytes([0,0]),y.appendBytes(i),y.appendBytes(s),y.appendBytes(c);let u=new r(t),f=u.getLength(),w=a(await u.readBytes(u.getLength())),d=String.fromCharCode(f%e.AESBlockSize);return cText=await n._webEncryptAes(w.finalize(),o,l),hmac0=await n._webHashHMAC(cText,o),y.appendBytes(cText),y.appendBytes(d),y.appendBytes(hmac0),await y.finalize()}(s,c,f,p,w,y)},decrypt:async function(s,o){if(o.length>e.maxPassLen)return console.warn("Password is too long."),!1;let c=r(s);if("AES"!==t.bytes2str(await c.readBytes(3))||c.getLength()<136)return console.warn("File is corrupted or not an AES Crypt \n(or jsAesCrypt) file."),!1;if(await c.readByte()!==e.fileFormatVersion)return console.warn("jsAesCrypt is only compatible with version \n2 of the AES Crypt file format."),!1;for(await c.readByte();;){let e=await c.readBytes(2);if(e.length<2)return console.warn("File is corrupted."),!1;if(0==(e=+t.arrToInt(e)))break;await c.readBytes(e)}let l=await c.readBytes(16);if(16!==l.length)return console.warn("File is corrupted."),!1;let y=await i(o,l),p=await c.readBytes(48);if(48!==p.length)return console.warn("File is corrupted."),!1;let u=t.bytes2str(await c.readBytes(32));if(32!==u.length)return console.warn("File is corrupted."),!1;let f=await n._webHashHMAC(p,y);if(u!==t.bytes2str(f))return console.warn("Wrong password (or file is corrupted)."),!1;let w,d=await n._webDecryptAes(p,y,l,0),A=d.slice(0,e.AESBlockSize),B=d.slice(e.AESBlockSize,e.AESBlockSize+32),h=a(),S=a(await c.readBytes(c.getLength()-c.getCurrentPosition()-32-1)),g=t.arrToInt(await c.readBytes(1));hmac0Act=await n._webHashHMAC(S.finalize(),B);try{w=await n._webDecryptAes(S.finalize(),B,A,g)}catch{w=await n._webDecryptAes(S.finalize(),B,A,0);let t=e.AESBlockSize-g;w=w.slice(0,w.length-t)}h.appendBytes(w);let b=t.bytes2str(await c.readBytes(32));return 32!==b.length?(console.warn("File is corrupted."),!1):b!==t.bytes2str(hmac0Act)?(console.warn("Bad HMAC (file is corrupted)."),!1):h.finalize()},utils:t,info:e}};
\ No newline at end of file
diff --git a/demo/worker.js b/demo/worker.js
index ffb7b66..7da0586 100644
--- a/demo/worker.js
+++ b/demo/worker.js
@@ -3,11 +3,6 @@ let base_libs = "libs/";
self.importScripts(
base_cdn + "core.min.js",
- base_cdn + "cipher-core.min.js",
- base_cdn + "aes.min.js",
- base_cdn + "hmac.min.js",
- base_cdn + "sha256.min.js",
- base_cdn + "pad-nopadding.min.js",
base_cdn + "enc-utf16.min.js",
base_libs + "enc-uint8array.min.js",
base_libs + "aes_crypt.min.js",
diff --git a/src/aes_crypt.js b/src/aes_crypt.js
index 6eed124..b4d1f2d 100644
--- a/src/aes_crypt.js
+++ b/src/aes_crypt.js
@@ -1,7 +1,9 @@
+/** global: AesCrypt */
+
AesCrypt = function () {
let info = {
// jsAesCrypt version
- version: "0.13a",
+ version: "0.15",
// encryption/decryption buffer size - 32K
bufferSize: 32 * 1024,
@@ -30,20 +32,19 @@ AesCrypt = function () {
*
* @param fileObj file element object
* @param passw string password to decrypt
- * @callback_progress callback function (current) => { 0 < current <= 1 }
*/
- async function decrypt(fileObj, passw, callback_progress = (c) => {}) {
+ async function decrypt(fileObj, passw) {
if( passw.length > info.maxPassLen ) {
console.warn("Password is too long.");
return false;
}
// file bytes reader
- let file = fileReader(fileObj);
+ let file = fileBytesReader(fileObj);
// check if file is in AES Crypt format (also min length check)
- if( await file.readBytesAsString(3) !== "AES" || file.getLength() < 136 ) {
+ if( utils.bytes2str( await file.readBytes(3) ) !== "AES" || file.getLength() < 136 ) {
console.warn(
"File is corrupted or not an AES Crypt \n" +
"(or jsAesCrypt) file.");
@@ -83,128 +84,79 @@ AesCrypt = function () {
}
// read external iv
- let iv1 = await file.readBytesAsString(16);
+ let iv1 = await file.readBytes(16);
if( iv1.length !== 16 ) {
console.warn("File is corrupted.");
return false;
}
// _stretch password and iv
- let key = _stretch(passw, iv1);
+ let key = await _stretch(passw, iv1);
// read encrypted main iv and key
- let c_iv_key = await file.readBytesAsString(48);
+ let c_iv_key = await file.readBytes(48);
if( c_iv_key.length !== 48 ) {
console.warn("File is corrupted.");
return false;
}
// read HMAC-SHA256 of the encrypted iv and key
- let hmac1 = await file.readBytesAsString(32);
+ let hmac1 = utils.bytes2str( await file.readBytes(32) );
if( hmac1.length !== 32 ) {
console.warn("File is corrupted.");
return false;
}
- let hmac1Act = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, utils.encode_to_words(key));
- hmac1Act.update(
- utils.encode_to_words(c_iv_key)
- );
+ let hmac1Act = await webCryptSubtle._webHashHMAC(c_iv_key, key);
// HMAC check
- if( hmac1 !== CryptoJS.enc.Latin1.stringify(hmac1Act.finalize()) ) {
+ if( hmac1 !== utils.bytes2str(hmac1Act) ) {
console.warn("Wrong password (or file is corrupted).");
return false;
}
- // instantiate AES cipher
- let decryptor1 = _createDecryptor(key, iv1);
-
- // decrypt main iv and key
- let iv_key = decryptor1.process(utils.encode_to_words(c_iv_key)).toString(CryptoJS.enc.Latin1) + decryptor1.finalize().toString(CryptoJS.enc.Latin1);
+ let iv_key = await webCryptSubtle._webDecryptAes(c_iv_key, key, iv1, 0);
// get internal iv and key
- let iv0 = iv_key.substr(0, info.AESBlockSize);
- let intKey = iv_key.substr(info.AESBlockSize, 32);
-
- // instantiate another AES cipher
- let decryptor0 = _createDecryptor(intKey, iv0);
-
- // instantiate actual HMAC-SHA256 of the ciphertext
- let hmac0Act = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, utils.encode_to_words(intKey));
-
- let result = binaryArray();
- let total = Math.ceil( (file.getLength() - file.getCurrentPosition() - 32 - 1 - info.AESBlockSize) / info.bufferSize ) + 1;
- let counter = 0;
-
- // decrypt blocks
- while( file.getCurrentPosition() < file.getLength() - 32 - 1 - info.AESBlockSize ) {
- // read data
- let cText = utils.encode_to_words(
- await file.readBytes(
- Math.min(
- info.bufferSize,
- file.getLength() - file.getCurrentPosition() - 32 - 1 - info.AESBlockSize,
- )
- ),
- "Uint8Arr"
- )
-
- // update HMAC
- hmac0Act.update(cText)
- // decrypt data and write it to output file
- result.appendBytes(decryptor0.process(cText).toString(CryptoJS.enc.Latin1));
- callback_progress(++counter / total);
- }
+ let iv0 = iv_key.slice(0, info.AESBlockSize);
+ let intKey = iv_key.slice(info.AESBlockSize, info.AESBlockSize+32);
- var cText;
-
- // last block reached, remove padding if needed
- // read last block
- if( file.getCurrentPosition() !== file.getLength() - 32 - 1 ) {
- // read typed array
- cText = await file.readBytes(info.AESBlockSize);
-
- if( cText.length < info.AESBlockSize ) {
- console.warn("File is corrupted.");
- return false;
- }
- } else {
- cText = new Uint8Array([]);
- }
+ let result = binaryStream();
- // encode to words for CryptoJS
- cText = utils.encode_to_words(cText, "Uint8Arr");
+ let cText = binaryStream( await file.readBytes(
+ file.getLength() - file.getCurrentPosition() - 32 - 1,
+ ));
- // update HMAC
- hmac0Act.update(cText);
+ let fs16 = utils.arrToInt(await file.readBytes(1));
- let fs16 = await file.readBytesAsInt(1);
+ hmac0Act = await webCryptSubtle._webHashHMAC(cText.finalize(), intKey);
- let pText = decryptor0.process(cText).toString(CryptoJS.enc.Latin1) + decryptor0.finalize().toString(CryptoJS.enc.Latin1);
+ let pText;
- // remove padding
- let toremove = ((16 - fs16) % 16);
- if( toremove !== 0 ) {
- pText = pText.substr(0, pText.length - toremove);
+ try{
+ pText = await webCryptSubtle._webDecryptAes(cText.finalize(), intKey, iv0, fs16);
+ } catch {
+ // AesCrypt on C# use PKCS7 in pad without full pad block
+ // webCrypt can't decrypt it without force cheat with fs16 = 0
+ pText = await webCryptSubtle._webDecryptAes(cText.finalize(), intKey, iv0, 0);
+ let toremove = info.AESBlockSize - fs16;
+ pText = pText.slice(0, pText.length - toremove);
}
result.appendBytes(pText);
- let hmac0 = await file.readBytesAsString(32);
+ let hmac0 = utils.bytes2str( await file.readBytes(32) );
if( hmac0.length !== 32 ) {
console.warn("File is corrupted.");
return false;
}
- if( hmac0 !== CryptoJS.enc.Latin1.stringify(hmac0Act.finalize()) ) {
+ if( hmac0 !== utils.bytes2str(hmac0Act) ) {
console.warn("Bad HMAC (file is corrupted).");
return false;
}
- callback_progress(1);
-
return result.finalize();
}
@@ -221,10 +173,9 @@ AesCrypt = function () {
*
* @param fileObj file element object
* @param passw string password to encrypt
- * @callback_progress callback function (current) => { 0 < current <= 1 }
*/
- async function encrypt(fileObj, passw, callback_progress = (c) => {}) {
+ async function encrypt(fileObj, passw) {
if( passw.length > info.maxPassLen ) {
console.warn("Password is too long.");
return false;
@@ -234,7 +185,7 @@ AesCrypt = function () {
const iv1 = utils.urandom(info.AESBlockSize);
// _stretch password and iv
- const key = _stretch(passw, iv1);
+ const key = await _stretch(passw, iv1);
// generate random main iv
const iv0 = utils.urandom(info.AESBlockSize);
@@ -242,106 +193,24 @@ AesCrypt = function () {
// generate random internal key
const intKey = utils.urandom(32);
- const encryptor0 = _createEncryptor(intKey, iv0);
-
- // instantiate HMAC-SHA256 for the ciphertext
- const hmac0 = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, utils.encode_to_words(intKey));
-
- // instantiate another AES cipher
- const encryptor1 = _createEncryptor(key, iv1);
+ let iv0_and_intkey = binaryStream();
+ iv0_and_intkey.appendBytes(iv0);
+ iv0_and_intkey.appendBytes(intKey);
// encrypt main iv and key
- const c_iv_key = encryptor1.process(utils.encode_to_words(iv0 + intKey)).toString(CryptoJS.enc.Latin1) + encryptor1.finalize().toString(CryptoJS.enc.Latin1);
+ const c_iv_key = await webCryptSubtle._webEncryptAes(iv0_and_intkey.finalize(), key, iv1);
- //# calculate HMAC-SHA256 of the encrypted iv and key
- const hmac1 = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, utils.encode_to_words(key));
- hmac1.update(utils.encode_to_words(c_iv_key));
+ const hmac1 = await webCryptSubtle._webHashHMAC(c_iv_key, key);
- return await _createAesCryptFormat(fileObj, iv1, c_iv_key, hmac0, hmac1, encryptor0, callback_progress);
+ return await _createAesCryptFormat(fileObj, iv1, c_iv_key, intKey, hmac1, iv0);
}
/* PRIVATE START */
- // private class fileReader for read file by blocks
- var fileReader = function(file) {
- let _i = 0;
- let _fileSize = file.size;
- let _reader = new FileReader();
- let _file = file;
-
- function readChunk(length) {
- return new Promise((resolve, reject) => {
- let blob = _file.slice(_i, _i += length);
-
- _reader.onload = () => {
- // return Uint8Array
- resolve(new Uint8Array(_reader.result));
- };
-
- _reader.onerror = reject;
-
- _reader.readAsArrayBuffer(blob);
- });
- }
-
- async function readBytes(length) {
- return await readChunk(length);
- }
-
- async function readByte() {
- let bytes = await readBytes(1);
- return bytes[0];
- }
-
- async function readBytesAsString(length) {
- let bytes = await readBytes(length);
- return utils.bytes_to_latin1(bytes);
- }
-
- async function readBytesAsInt(length) {
- let bytes = await readBytes(length);
- return utils.arrToInt(bytes);
- }
-
- function getCurrentPosition() {
- return _i;
- }
-
- function getLength() {
- return _fileSize;
- }
-
- return {
- readByte: readByte,
- readBytes: readBytes,
- readBytesAsInt: readBytesAsInt,
- readBytesAsString: readBytesAsString,
- getCurrentPosition: getCurrentPosition,
- getLength: getLength,
- };
- };
-
- // private class utils for usefull functions
- var utils = {
-
- random_int: function(min, max) {
- return Math.floor(Math.random() * (max - min) + min);
- },
+ const utils = {
urandom: function(length) {
- let out = "";
- for (let i = 0; i < length; i ++) {
- out += String.fromCharCode(this.random_int(0, 256))
- }
- return out;
- },
-
- fillArray: function(value, len) {
- let arr = [];
- for (let i = 0; i < len; i ++) {
- arr.push(value);
- }
- return arr;
+ return crypto.getRandomValues( new Uint8Array(length) );
},
arrToInt: function (arr) { // buffer is an UInt8Array
@@ -349,89 +218,41 @@ AesCrypt = function () {
},
// bytes is typed array
- bytes_to_latin1: function(bytes) {
+ bytes2str: function(bytes) {
return CryptoJS.enc.Latin1.stringify(CryptoJS.enc.Uint8Arr.parse(bytes))
},
- encode_to_words: function (input, enc = "Latin1") {
- return CryptoJS.enc[enc].parse(input);
+ str2bytes: function(str, enc="Latin1") {
+ return CryptoJS.enc.Uint8Arr.decode(CryptoJS.enc[enc].parse(str));
},
}
- // private class binaryArray for easy work with Uint8Array
- let binaryArray = function() {
- let _data = new Uint8Array([]);
-
- function appendBytes(input) {
- let tmp;
-
- if (typeof (input) == "number") {
- let hex_string = input.toString(16);
- tmp = new Uint8Array(hex_string.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
- } else if (typeof (input) == "string") {
- tmp = new Uint8Array(input.length);
- for (let i = 0; i < input.length; i ++) {
- tmp[i] = input.charCodeAt(i);
- }
- } else {
- tmp = new Uint8Array(input);
- }
-
- let new_uint8_arr = new Uint8Array(_data.length + tmp.length);
-
- new_uint8_arr.set(_data);
- new_uint8_arr.set(tmp, _data.length);
-
- _data = new_uint8_arr;
- };
-
- function finalize() {
- return _data;
- };
-
- return {
- appendBytes: appendBytes,
- finalize: finalize,
- }
- }
-
- // create encryptor object with default options
- function _createEncryptor(key, iv) {
- return CryptoJS.algo.AES._createEncryptor(utils.encode_to_words(key), {
- mode: CryptoJS.mode.CBC,
- iv: CryptoJS.enc.Latin1.parse(iv),
- padding: CryptoJS.pad.NoPadding,
- });
- }
-
- // create decryptor object with default options
- function _createDecryptor(key, iv) {
- return CryptoJS.algo.AES._createDecryptor(utils.encode_to_words(key), {
- mode: CryptoJS.mode.CBC,
- iv: CryptoJS.enc.Latin1.parse(iv),
- padding: CryptoJS.pad.NoPadding,
- });
- }
+ // IMPORT webCryptSubtle THIS
+ // IMPORT fileBytesReader THIS
+ // IMPORT binaryStream THIS
// stretch password and iv1
- function _stretch(passw, iv1) {
+ async function _stretch(passw, iv1) {
// hash the external iv and the password 8192 times
- let digest = utils.encode_to_words(iv1 + ("\x00".repeat(16)));
+ let digest_tmp = binaryStream();
+ digest_tmp.appendBytes(iv1);
+ digest_tmp.appendBytes("\x00".repeat(16));
+
+ let digest = digest_tmp.finalize();
for (let i = 0; i < 8192; i ++) {
- let passHash = CryptoJS.algo.SHA256.create();
- passHash.update(digest);
- passHash.update(utils.encode_to_words(passw, "Utf16LE"));
- digest = passHash.finalize();
+ let passHash = binaryStream(digest);
+ passHash.appendBytes(utils.str2bytes(passw, "Utf16LE"));
+ digest = await webCryptSubtle._webHashSHA256(passHash.finalize(0));
}
- return digest.toString(CryptoJS.enc.Latin1);
+ return digest;
}
// see https://www.aescrypt.com/aes_file_format.html
- async function _createAesCryptFormat(fileObj, iv1, c_iv_key, hmac0, hmac1, encryptor0, callback_progress) {
- let result = binaryArray();
+ async function _createAesCryptFormat(fileObj, iv1, c_iv_key, intKey, hmac1, iv0) {
+ let result = binaryStream();
// header
result.appendBytes("AES");
@@ -459,7 +280,7 @@ AesCrypt = function () {
result.appendBytes([0x0, 0x80]);
// "container" extension
- result.appendBytes(utils.fillArray(0x0, 128));
+ result.appendBytes("\x00".repeat(128));
// end-of-extensions tag
result.appendBytes([0x0, 0x0]);
@@ -472,61 +293,33 @@ AesCrypt = function () {
result.appendBytes(c_iv_key);
// HMAC-SHA256 of the encrypted iv and key
- result.appendBytes(hmac1.finalize().toString(CryptoJS.enc.Latin1));
-
- let fs16 = String.fromCharCode(0);
- let file = new fileReader(fileObj);
-
- const blockLength = Math.ceil(file.getLength() / info.bufferSize);
-
- let total = Math.ceil( (file.getLength() - file.getCurrentPosition()) / info.bufferSize );
- let counter = 0;
-
- while( file.getCurrentPosition() < file.getLength() ) {
+ result.appendBytes(hmac1);
- let fdata = await file.readBytes(info.bufferSize);
-
- let bytesRead = fdata.length;
-
- let cText = encryptor0.process(utils.encode_to_words(fdata, "Uint8Arr")).toString(CryptoJS.enc.Latin1);
-
- // check if EOF was reached
- if (bytesRead < info.bufferSize) {
- // file size mod 16, lsb positions
- fs16 = String.fromCharCode(bytesRead % info.AESBlockSize);
- // pad data (this is NOT PKCS#7!)
- // ...unless no bytes or a multiple of a block size
- // of bytes was read
- let padLen;
- if (bytesRead % info.AESBlockSize === 0) {
- padLen = 0;
- } else {
- padLen = 16 - bytesRead % info.AESBlockSize;
- }
-
- let padByte = fs16.repeat(padLen);
+ let file = new fileBytesReader(fileObj);
+ let bytesRead = file.getLength();
+ let pText = binaryStream(
+ await file.readBytes( file.getLength() )
+ );
- // encrypt data
- cText += encryptor0.process(utils.encode_to_words(padByte)).toString(CryptoJS.enc.Latin1)
- }
+ // file size mod 16, lsb positions
+ let fs16 = String.fromCharCode(bytesRead % info.AESBlockSize);
- cText += encryptor0.finalize().toString(CryptoJS.enc.Latin1);
+ cText = await webCryptSubtle._webEncryptAes(pText.finalize(), intKey ,iv0);
- hmac0.update(utils.encode_to_words(cText));
+ hmac0 = await webCryptSubtle._webHashHMAC(cText, intKey);
- result.appendBytes(cText);
-
- callback_progress(++counter / total);
- }
+ result.appendBytes(cText);
result.appendBytes(fs16);
// HMAC-SHA256 of the encrypted file
- result.appendBytes(hmac0.finalize().toString(CryptoJS.enc.Latin1));
+ result.appendBytes(hmac0);
return await result.finalize();
}
+ webCryptSubtle.info = info;
+
return {
encrypt: encrypt,
decrypt: decrypt,
diff --git a/src/binary_stream.js b/src/binary_stream.js
new file mode 100644
index 0000000..851c0e9
--- /dev/null
+++ b/src/binary_stream.js
@@ -0,0 +1,63 @@
+/** global: binaryStream */
+
+/**
+ * binary array stream object
+ *
+ *
+ * @return The binaryStream object.
+ *
+ * @static
+ *
+ * @example
+ * var binaryStream = binaryStream([1,2,3]);
+ * binaryStream.appendBytes([4,5,6]);
+ * binaryStream.appendBytes("\x07\x08\x09");
+ * console.log(binaryStream.finalize()) // returns Uint8Array [1,2,3,4,5,6,7,8,9]
+ *
+ * @param arr origin array
+ */
+const binaryStream = function(arr = []) {
+ let _data = new Uint8Array(arr);
+
+ function appendBytes(input) {
+ let tmp;
+
+ if (typeof (input) == "number") {
+ let hex_string = input.toString(16);
+ tmp = new Uint8Array(hex_string.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
+ } else if (typeof (input) == "string") {
+ tmp = new Uint8Array(input.length);
+ for (let i = 0; i < input.length; i ++) {
+ tmp[i] = input.charCodeAt(i);
+ }
+ } else {
+ tmp = new Uint8Array(input);
+ }
+
+ let new_uint8_arr = new Uint8Array(_data.length + tmp.length);
+
+ new_uint8_arr.set(_data);
+ new_uint8_arr.set(tmp, _data.length);
+
+ _data = new_uint8_arr;
+ };
+
+ function get(i) {
+ return _data[i];
+ }
+
+ function finalize() {
+ return _data;
+ };
+
+ function getLength() {
+ return _data.length;
+ }
+
+ return {
+ appendBytes: appendBytes,
+ finalize: finalize,
+ get: get,
+ getLength: getLength,
+ }
+}
\ No newline at end of file
diff --git a/src/file_bytes_reader.js b/src/file_bytes_reader.js
new file mode 100644
index 0000000..b275a0a
--- /dev/null
+++ b/src/file_bytes_reader.js
@@ -0,0 +1,64 @@
+/** global: fileBytesReader */
+/** global: FileReader */
+
+/**
+ * read file as string, bytes (uint8Array), int (1 byte)
+ *
+ *
+ * @return The fileBytesReader object.
+ *
+ * @static
+ *
+ * @example
+ * var fileElement = document.getElementById('file').files[0];
+ * var file = fileBytesReader(fileElement);
+ *
+ * @param file a fileElement object
+ */
+
+
+const fileBytesReader = function(file) {
+ let _i = 0;
+ let _fileSize = file.size;
+ let _reader = new FileReader();
+ let _file = file;
+
+ function readChunk(length) {
+ return new Promise((resolve, reject) => {
+ let blob = _file.slice(_i, _i += length);
+
+ _reader.onload = () => {
+ // return Uint8Array
+ resolve(new Uint8Array(_reader.result));
+ };
+
+ _reader.onerror = reject;
+
+ _reader.readAsArrayBuffer(blob);
+ });
+ }
+
+ async function readBytes(length) {
+ return await readChunk(length);
+ }
+
+ async function readByte() {
+ let bytes = await readBytes(1);
+ return bytes[0];
+ }
+
+ function getCurrentPosition() {
+ return _i;
+ }
+
+ function getLength() {
+ return _fileSize;
+ }
+
+ return {
+ readByte: readByte,
+ readBytes: readBytes,
+ getCurrentPosition: getCurrentPosition,
+ getLength: getLength,
+ };
+};
diff --git a/src/web_crypto_subtle.js b/src/web_crypto_subtle.js
new file mode 100644
index 0000000..b5f8d81
--- /dev/null
+++ b/src/web_crypto_subtle.js
@@ -0,0 +1,84 @@
+/** global: webCryptSubtle */
+
+const webCryptSubtle = {
+
+ _createKey: async function(intKeyArr, mode, functions) {
+ return await crypto.subtle.importKey( "raw", intKeyArr.buffer, mode , false, functions);
+ },
+
+ _webHashHMAC: async function(text, intKeyArr) {
+ let key_encoded = await this._createKey(
+ intKeyArr,
+ { // algorithm details
+ name: "HMAC",
+ hash: {name: "SHA-256"}
+ },
+ ["sign", "verify"],
+ );
+
+ return new Uint8Array( await crypto.subtle.sign(
+ "HMAC",
+ key_encoded,
+ text
+ ) );
+ },
+
+ _webEncryptAes :async function(pText, intKeyArr, iv0, stayLast=true) {
+ let key_encoded = await this._createKey(intKeyArr, "AES-CBC", ["encrypt", "decrypt"]);
+
+ let encrypted = new Uint8Array( await crypto.subtle.encrypt(
+ {
+ name: "AES-CBC",
+ iv: iv0,
+ },
+ key_encoded,
+ pText
+ ) );
+
+ if( pText.length % this.info.AESBlockSize === 0 && stayLast == true ) {
+ encrypted = encrypted.slice(0, encrypted.length - this.info.AESBlockSize);
+ }
+
+ return encrypted;
+ },
+
+ _webHashSHA256: async function(text) {
+ return new Uint8Array(await crypto.subtle.digest("SHA-256", text.buffer));
+ },
+
+ _webDecryptAes: async function(cText, intKeyArr, iv0, fs16 = 0) {
+ let key_encoded = await this._createKey(intKeyArr, "AES-CBC", ["encrypt", "decrypt"]);
+ let cTextArr = binaryStream(cText);
+
+ // dirty cheat to add encrypted block pkcs7 padding if mod = 0
+ // because WebCrypto subtle working only with pkcs7 pad
+ if( fs16 == 0 ) {
+ let modBlock = new Uint8Array(this.info.AESBlockSize);
+
+ // xor padding with last block (see mode AES-CBC)
+ for( let i = 0; i < this.info.AESBlockSize;i++ ) {
+ modBlock[i] = 0x00 ^ cText[cText.length - this.info.AESBlockSize + i];
+ }
+
+ modBlockEncrypted = await this._webEncryptAes(modBlock, intKeyArr, iv0, false);
+
+ cTextArr.appendBytes(modBlockEncrypted);
+ }
+
+ let pText = new Uint8Array( await crypto.subtle.decrypt(
+ {
+ name: "AES-CBC",
+ iv: iv0,
+ },
+ key_encoded,
+ cTextArr.finalize()
+ ) );
+
+ // clear empty data pkcs7 padding block
+ if( fs16 == 0 ) {
+ pText = pText.slice(0, pText.length - this.info.AESBlockSize);
+ }
+
+ return pText;
+ },
+}
\ No newline at end of file