Browse Source

Добавлен модуль sjcl для шифрования AES, т.к. WebCrypto API не работает с http, а только с https

Book Pauk 6 năm trước cách đây
mục cha
commit
9cbaf22270

+ 2 - 2
client/components/Reader/ServerStorage/ServerStorage.vue

@@ -32,8 +32,8 @@ class ServerStorage extends Vue {
             //генерируем новый ключ
             //генерируем новый ключ
             this.generateNewServerStorageKey();
             this.generateNewServerStorageKey();
         }
         }
-        this.hashedStorageKey = utils.toBase58(await cryptoUtils.sha256(this.serverStorageKey));
-        
+        this.hashedStorageKey = utils.toBase58(cryptoUtils.sha256(this.serverStorageKey));
+
         await this.loadProfiles();
         await this.loadProfiles();
     }
     }
 
 

+ 16 - 61
client/share/cryptoUtils.js

@@ -1,70 +1,25 @@
-import {Buffer} from 'safe-buffer';
+import sjclWrapper from './sjclWrapper';
 
 
 //не менять
 //не менять
-const iv = Buffer.from('B6E2XejNh2dS');
-let aesKeys = {};
-
-export async function aesKeyFromPassword(password) {
-    return await window.crypto.subtle.importKey(
-        "raw", //only "raw" is allowed
-        Buffer.from(password), //your password
-        {
-            name: "PBKDF2",
-        },
-        false, //whether the key is extractable (i.e. can be used in exportKey)
-        ["deriveKey"] //can be any combination of "deriveKey" and "deriveBits"
-    ).then((key) => {
-        return window.crypto.subtle.deriveKey(
-            {
-                "name": "PBKDF2",
-                salt: Buffer.from('Liberama project is awesome'),//не менять
-                iterations: 1000,
-                hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
-            },
-            key, //your key from generateKey or importKey
-            { //the key type you want to create based on the derived bits
-                name: "AES-GCM", //can be any AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH", or "HMAC")
-                //the generateKey parameters for that type of algorithm
-                length: 256, //can be  128, 192, or 256
-            },
-            false, //whether the derived key is extractable (i.e. can be used in exportKey)
-            ["encrypt", "decrypt"] //limited to the options in that algorithm's importKey
-        );
-    });
-}
-
-export async function aesEncrypt(data, password) {
-    if (!aesKeys[password])
-        aesKeys[password] = await aesKeyFromPassword(password);
-
-    const key = aesKeys[password];
-
-    return await window.crypto.subtle.encrypt(
-        {
-            name: "AES-GCM",
-            iv
-        },
-        key, //from generateKey or importKey above
-        data //ArrayBuffer of data you want to encrypt
+const iv = 'B6E2XejNh2dS';
+const salt = 'Liberama project is awesome';
+
+export function aesEncrypt(data, password) {
+    return sjclWrapper.codec.bytes.fromBits(
+        sjclWrapper.encryptArray(
+            password, sjclWrapper.codec.bytes.toBits(data), {iv, salt}
+        ).ct
     );
     );
 }
 }
 
 
-export async function aesDecrypt(data, password) {
-    if (!aesKeys[password])
-        aesKeys[password] = await aesKeyFromPassword(password);
-
-    const key = aesKeys[password];
-
-    return await window.crypto.subtle.decrypt(
-        {
-            name: "AES-GCM",
-            iv
-        },
-        key, //from generateKey or importKey above
-        data //ArrayBuffer of the data
+export function aesDecrypt(data, password) {
+    return sjclWrapper.codec.bytes.fromBits(
+        sjclWrapper.decryptArray(
+            password, {ct: sjclWrapper.codec.bytes.toBits(data)}, {iv, salt}
+        )
     );
     );
 }
 }
 
 
-export async function sha256(data) {
-    return await crypto.subtle.digest("SHA-256", Buffer.from(data));
+export function sha256(str) {
+    return sjclWrapper.codec.bytes.fromBits(sjclWrapper.hash.sha256.hash(str));
 }
 }

+ 145 - 0
client/share/sjclWrapper.js

@@ -0,0 +1,145 @@
+import sjcl from 'sjcl';
+
+//везде недоработки...
+
+sjcl.codec.bytes = {
+  fromBits: function(arr) {
+    var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp;
+    for (i=0; i<bl/8; i++) {
+      if ((i&3) === 0) {
+        tmp = arr[i/4];
+      }
+      out.push(tmp >>> 24);
+      tmp <<= 8;
+    }
+    return out;
+  },
+  toBits: function(bytes) {
+    var out = [], i, tmp=0;
+    for (i=0; i<bytes.length; i++) {
+      tmp = tmp << 8 | bytes[i];
+      if ((i&3) === 3) {
+        out.push(tmp);
+        tmp = 0;
+      }
+    }
+    if (i&3) {
+      out.push(sjcl.bitArray.partial(8*(i&3), tmp));
+    }
+    return out;
+  }
+};
+
+sjcl.json._add = function(target, src, requireSame) {
+    if (target === undefined) { target = {}; }
+    if (src === undefined) { return target; }
+    var i;
+    for (i in src) {
+      if (src.hasOwnProperty(i)) {
+        if (requireSame && target[i] !== undefined && target[i] !== src[i]) {
+          throw new sjcl.exception.invalid("required parameter overridden");
+        }
+        target[i] = src[i];
+      }
+    }
+    return target;
+}
+
+sjcl.encryptArray = function(password, plaintext, params, rp) {
+    params = params || {};
+    rp = rp || {};
+
+    var j = sjcl.json, p = j._add({ iv: sjcl.random.randomWords(4,0) },
+                                  j.defaults), tmp, prp, adata;
+    j._add(p, params);
+    adata = p.adata;
+    if (typeof p.salt === "string") {
+      p.salt = sjcl.codec.base64.toBits(p.salt);
+    }
+    if (typeof p.iv === "string") {
+      p.iv = sjcl.codec.base64.toBits(p.iv);
+    }
+
+    if (!sjcl.mode[p.mode] ||
+        !sjcl.cipher[p.cipher] ||
+        (typeof password === "string" && p.iter <= 100) ||
+        (p.ts !== 64 && p.ts !== 96 && p.ts !== 128) ||
+        (p.ks !== 128 && p.ks !== 192 && p.ks !== 256) ||
+        (p.iv.length < 2 || p.iv.length > 4)) {
+      throw new sjcl.exception.invalid("json encrypt: invalid parameters");
+    }
+
+    if (typeof password === "string") {
+      tmp = sjcl.misc.cachedPbkdf2(password, p);
+      password = tmp.key.slice(0,p.ks/32);
+      p.salt = tmp.salt;
+    } else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.publicKey) {
+      tmp = password.kem();
+      p.kemtag = tmp.tag;
+      password = tmp.key.slice(0,p.ks/32);
+    }
+    if (typeof plaintext === "string") {
+      plaintext = sjcl.codec.utf8String.toBits(plaintext);
+    }
+    if (typeof adata === "string") {
+      p.adata = adata = sjcl.codec.utf8String.toBits(adata);
+    }
+    prp = new sjcl.cipher[p.cipher](password);
+
+    j._add(rp, p);
+    rp.key = password;
+
+    /* do the encryption */
+    if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && plaintext instanceof ArrayBuffer) {
+      p.ct = sjcl.arrayBuffer.ccm.encrypt(prp, plaintext, p.iv, adata, p.ts);
+    } else {
+      p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts);
+    }
+
+    return p;
+}
+
+sjcl.decryptArray = function(password, ciphertext, params) {
+    params = params || {};
+
+    var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),ciphertext), params, true), ct, tmp, prp, adata=p.adata;
+    if (typeof p.salt === "string") {
+      p.salt = sjcl.codec.base64.toBits(p.salt);
+    }
+    if (typeof p.iv === "string") {
+      p.iv = sjcl.codec.base64.toBits(p.iv);
+    }
+
+    if (!sjcl.mode[p.mode] ||
+        !sjcl.cipher[p.cipher] ||
+        (typeof password === "string" && p.iter <= 100) ||
+        (p.ts !== 64 && p.ts !== 96 && p.ts !== 128) ||
+        (p.ks !== 128 && p.ks !== 192 && p.ks !== 256) ||
+        (!p.iv) ||
+        (p.iv.length < 2 || p.iv.length > 4)) {
+      throw new sjcl.exception.invalid("json decrypt: invalid parameters");
+    }
+
+    if (typeof password === "string") {
+      tmp = sjcl.misc.cachedPbkdf2(password, p);
+      password = tmp.key.slice(0,p.ks/32);
+      p.salt  = tmp.salt;
+    } else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.secretKey) {
+      password = password.unkem(sjcl.codec.base64.toBits(p.kemtag)).slice(0,p.ks/32);
+    }
+    if (typeof adata === "string") {
+      adata = sjcl.codec.utf8String.toBits(adata);
+    }
+    prp = new sjcl.cipher[p.cipher](password);
+
+    /* do the decryption */
+    if (p.mode === "ccm" && sjcl.arrayBuffer && sjcl.arrayBuffer.ccm && p.ct instanceof ArrayBuffer) {
+      ct = sjcl.arrayBuffer.ccm.decrypt(prp, p.ct, p.iv, p.tag, adata, p.ts);
+    } else {
+      ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts);
+    }
+
+    return ct;
+}
+
+export default sjcl;

+ 5 - 0
package-lock.json

@@ -10084,6 +10084,11 @@
         }
         }
       }
       }
     },
     },
+    "sjcl": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.8.tgz",
+      "integrity": "sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ=="
+    },
     "slash": {
     "slash": {
       "version": "1.0.0",
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
       "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",

+ 1 - 0
package.json

@@ -74,6 +74,7 @@
     "pako": "^1.0.10",
     "pako": "^1.0.10",
     "path-browserify": "^1.0.0",
     "path-browserify": "^1.0.0",
     "safe-buffer": "^5.1.2",
     "safe-buffer": "^5.1.2",
+    "sjcl": "^1.0.8",
     "sql-template-strings": "^2.2.2",
     "sql-template-strings": "^2.2.2",
     "sqlite": "^3.0.0",
     "sqlite": "^3.0.0",
     "tar-fs": "^2.0.0",
     "tar-fs": "^2.0.0",