Add new encryption scheme in chrome addon/firefox webextension
This commit is contained in:
parent
c3fab882f2
commit
36db5056a3
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
function getPref(key)
|
function getPref(key)
|
||||||
{
|
{
|
||||||
|
// Inspired from https://github.com/akiomik/chrome-storage-promise/
|
||||||
var promise = new Promise((resolve, reject) => {
|
var promise = new Promise((resolve, reject) => {
|
||||||
chrome.storage.local.get(key, (items) => {
|
chrome.storage.local.get(key, (items) => {
|
||||||
let err = chrome.runtime.lastError;
|
let err = chrome.runtime.lastError;
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var DEBUG = true;
|
var DEBUG = true;
|
||||||
var protocol_version = 3;
|
var protocol_version = 4;
|
||||||
|
|
||||||
SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
|
SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
|
||||||
|
|
||||||
|
@ -47,14 +47,24 @@ function debug(s)
|
||||||
console.log(s);
|
console.log(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generate_request(domain, login, mkey)
|
async function generate_request(domain, login, mkey, iv, old)
|
||||||
|
{
|
||||||
|
if (old)
|
||||||
{
|
{
|
||||||
var v = "@@" + domain + ";" + login;
|
var v = "@@" + domain + ";" + login;
|
||||||
debug("will encrypt " + v);
|
debug("will encrypt " + v);
|
||||||
//debug("with " + a2hex(mkey));
|
enc = encrypt_ecb(mkey, v);
|
||||||
enc = encrypt(mkey, v);
|
}
|
||||||
//debug("res " + a2hex(enc));
|
else
|
||||||
|
{
|
||||||
|
var v = domain + ";" + login;
|
||||||
|
debug("will encrypt " + v);
|
||||||
|
while ((v.length % 16))
|
||||||
|
v += "\0";
|
||||||
|
hash = await digest(v);
|
||||||
|
v += hash.slice(8, 24);
|
||||||
|
enc = encrypt_cbc(mkey, iv, v);
|
||||||
|
}
|
||||||
return enc;
|
return enc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,23 +78,43 @@ async function ask_server(form, field, logins, domain, wdomain, mkey, submit)
|
||||||
|
|
||||||
pbkdf2_level = await getPref("pbkdf2_level");
|
pbkdf2_level = await getPref("pbkdf2_level");
|
||||||
|
|
||||||
mkey = pbkdf2(mkey, salt, pbkdf2_level);
|
global_iv = await simple_pbkdf2(salt, mkey, pbkdf2_level);
|
||||||
|
global_iv = global_iv.slice(0, 16);
|
||||||
|
mkey = crypto_pbkdf2(mkey, salt, pbkdf2_level);
|
||||||
|
|
||||||
|
debug("global_iv " + a2hex(global_iv));
|
||||||
|
|
||||||
keys = "";
|
keys = "";
|
||||||
for(a=0, b=logins.length; a<logins.length; a++)
|
for(key_index=0, a=0; a<logins.length; a++, key_index++)
|
||||||
{
|
{
|
||||||
enc = await generate_request(domain, logins[a], mkey);
|
enc = await generate_request(domain, logins[a], mkey, global_iv, 0);
|
||||||
keys += (keys.length != 0) ? "&" : "";
|
keys += (keys.length != 0) ? "&" : "";
|
||||||
keys += "k" + a + "=" + a2hex(enc);
|
keys += "k" + key_index + "=" + a2hex(enc);
|
||||||
|
|
||||||
if (wdomain != "")
|
if (wdomain != "")
|
||||||
{
|
{
|
||||||
enc = await generate_request(wdomain, logins[a], mkey);
|
enc = await generate_request(wdomain, logins[a], mkey, global_iv, 0);
|
||||||
keys += (keys.length != 0) ? "&" : "";
|
keys += (keys.length != 0) ? "&" : "";
|
||||||
keys += "k" + (b++) + "=" + a2hex(enc);
|
keys += "k" + (++key_index) + "=" + a2hex(enc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await getPref("crypto_v1_compatible"))
|
||||||
|
{
|
||||||
|
for(a=0; a<logins.length; a++, key_index++)
|
||||||
|
{
|
||||||
|
enc = await generate_request(domain, logins[a], mkey, global_iv, 1);
|
||||||
|
keys += (keys.length != 0) ? "&" : "";
|
||||||
|
keys += "k" + key_index + "=" + a2hex(enc);
|
||||||
|
|
||||||
|
if (wdomain != "")
|
||||||
|
{
|
||||||
|
enc = await generate_request(wdomain, logins[a], mkey, global_iv, 1);
|
||||||
|
keys += (keys.length != 0) ? "&" : "";
|
||||||
|
keys += "k" + (++key_index) + "=" + a2hex(enc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
debug("Keys " + keys);
|
debug("Keys " + keys);
|
||||||
|
|
||||||
var gPassRequest = new XMLHttpRequest();
|
var gPassRequest = new XMLHttpRequest();
|
||||||
|
@ -96,6 +126,7 @@ async function ask_server(form, field, logins, domain, wdomain, mkey, submit)
|
||||||
var ciphered_password = "";
|
var ciphered_password = "";
|
||||||
var server_pbkdf2_level = 0;
|
var server_pbkdf2_level = 0;
|
||||||
var server_version = 0;
|
var server_version = 0;
|
||||||
|
var matched_key = 0;
|
||||||
|
|
||||||
var r = this.responseText.split("\n");
|
var r = this.responseText.split("\n");
|
||||||
debug("resp " + r);
|
debug("resp " + r);
|
||||||
|
@ -143,13 +174,18 @@ async function ask_server(form, field, logins, domain, wdomain, mkey, submit)
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
// Version 3 : nothing special to do
|
// Version 3 : nothing special to do
|
||||||
|
case 4:
|
||||||
|
// Version 3 : nothing special to do
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "matched_key":
|
||||||
|
matched_key = params[1];
|
||||||
case "pass":
|
case "pass":
|
||||||
ciphered_password = params[1];
|
ciphered_password = params[1];
|
||||||
break;
|
break;
|
||||||
|
case "pkdbf2_level":
|
||||||
case "pbkdf2_level":
|
case "pbkdf2_level":
|
||||||
server_pbkdf2_level = parseInt(params[1].match(/\d+/)[0], 10);
|
server_pbkdf2_level = parseInt(params[1].match(/\d+/)[0], 10);
|
||||||
if (server_pbkdf2_level != NaN &&
|
if (server_pbkdf2_level != NaN &&
|
||||||
|
@ -180,10 +216,20 @@ async function ask_server(form, field, logins, domain, wdomain, mkey, submit)
|
||||||
if (ciphered_password != "")
|
if (ciphered_password != "")
|
||||||
{
|
{
|
||||||
debug("Ciphered password : " + ciphered_password);
|
debug("Ciphered password : " + ciphered_password);
|
||||||
clear_password = await decrypt(mkey, hex2a(ciphered_password));
|
if (matched_key < logins.length)
|
||||||
|
{
|
||||||
|
clear_password = await decrypt_cbc(mkey, global_iv, hex2a(ciphered_password));
|
||||||
|
clear_password = clear_password.replace(/\0*$/, "");
|
||||||
|
clear_password = clear_password.substr(3, clear_password.length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Crypto v1
|
||||||
|
{
|
||||||
|
clear_password = await decrypt_ecb(mkey, hex2a(ciphered_password));
|
||||||
// Remove trailing \0 and salt
|
// Remove trailing \0 and salt
|
||||||
clear_password = clear_password.replace(/\0*$/, "");
|
clear_password = clear_password.replace(/\0*$/, "");
|
||||||
clear_password = clear_password.substr(0, clear_password.length-3);
|
clear_password = clear_password.substr(0, clear_password.length-3);
|
||||||
|
}
|
||||||
debug("Clear password " + clear_password);
|
debug("Clear password " + clear_password);
|
||||||
field.value = clear_password;
|
field.value = clear_password;
|
||||||
// Remove gPass event listener and submit again with clear password
|
// Remove gPass event listener and submit again with clear password
|
||||||
|
@ -321,6 +367,9 @@ function on_sumbit(e)
|
||||||
if (password.indexOf("@@") != 0 && password.indexOf("@_") != 0)
|
if (password.indexOf("@@") != 0 && password.indexOf("@_") != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Remove current value to limit master key stealing
|
||||||
|
field.value = "";
|
||||||
|
|
||||||
mkey = password.substring(2);
|
mkey = password.substring(2);
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -335,15 +384,14 @@ function on_sumbit(e)
|
||||||
case SERVER.FAILED:
|
case SERVER.FAILED:
|
||||||
if (logins !== all_logins)
|
if (logins !== all_logins)
|
||||||
{
|
{
|
||||||
/*ret = */ask_server(form, field, all_logins, domain, wdomain, mkey, (password.indexOf("@@") == 0));
|
ask_server(form, field, all_logins, domain, wdomain, mkey, (password.indexOf("@@") == 0));
|
||||||
/*if (ret == SERVER.OK)
|
|
||||||
break;};*/
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SERVER.RESTART_REQUEST:
|
case SERVER.RESTART_REQUEST:
|
||||||
i = -1; // Restart loop
|
i = -1; // Restart loop
|
||||||
break;
|
break;
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,15 +421,17 @@ document_loaded(document);
|
||||||
|
|
||||||
async function self_test()
|
async function self_test()
|
||||||
{
|
{
|
||||||
mkey = pbkdf2("password", "salt", 4096);
|
mkey = crypto_pbkdf2("password", "salt", 4096);
|
||||||
res = await encrypt(mkey, "DDDDDDDDDDDDDDDD");
|
res = await encrypt_ecb(mkey, "DDDDDDDDDDDDDDDD");
|
||||||
reference = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
||||||
|
reference = new Uint8Array([0xc4, 0x76, 0x01, 0x07, 0xa1, 0xc0, 0x2f, 0x22, 0xee, 0xbe, 0x60,
|
||||||
|
0xff, 0x65, 0x33, 0x5b, 0x9e]);
|
||||||
if (res != ab2str(reference))
|
if (res != ab2str(reference))
|
||||||
{
|
{
|
||||||
console.log("Self test ERROR !");
|
console.log("Self test ERROR !");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
console.log("All is OK ! ");
|
console.log("Self test OK !");
|
||||||
}
|
}
|
||||||
|
|
||||||
// self_test();
|
self_test();
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var default_preferences = {"pbkdf2_level": 1000,
|
var default_preferences = {"pbkdf2_level": 1000,
|
||||||
"account_url": "https://gpass-demo.soutade.fr/demo"};
|
"account_url": "https://gpass-demo.soutade.fr/demo",
|
||||||
|
"crypto_v1_compatible": true};
|
||||||
|
|
||||||
var browser = browser || chrome;
|
var browser = browser || chrome;
|
||||||
var crypto = crypto || window.crypto;
|
var crypto = crypto || window.crypto;
|
||||||
|
@ -52,18 +53,12 @@ function str2ab(str) {
|
||||||
return bufView;
|
return bufView;
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnResult(result) { return result;}
|
function crypto_pbkdf2(mkey, salt, level)
|
||||||
function onError(error) { console.error(error); }
|
|
||||||
|
|
||||||
function pbkdf2(mkey, salt, level)
|
|
||||||
{
|
{
|
||||||
debug("Process pbkdf2 with " + mkey);
|
|
||||||
|
|
||||||
AESCBC = {
|
AESCBC = {
|
||||||
name: "AES-CBC",
|
name: "AES-CBC",
|
||||||
length: 256,
|
length: 256,
|
||||||
}
|
}
|
||||||
level=1000;
|
|
||||||
|
|
||||||
var key = str2ab(mkey);
|
var key = str2ab(mkey);
|
||||||
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
|
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
|
||||||
|
@ -87,21 +82,52 @@ function pbkdf2(mkey, salt, level)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _encrypt(mkey, data)
|
function simple_pbkdf2(mkey, salt, level)
|
||||||
|
{
|
||||||
|
AESCBC = {
|
||||||
|
name: "AES-CBC",
|
||||||
|
length: 256,
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = str2ab(mkey);
|
||||||
|
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
|
||||||
|
.then(function(key){
|
||||||
|
//sha-256
|
||||||
|
return crypto.subtle.deriveKey({
|
||||||
|
name: "PBKDF2",
|
||||||
|
salt: str2ab(salt),
|
||||||
|
iterations: level,
|
||||||
|
hash: "SHA-256",
|
||||||
|
}, key, AESCBC, true, ["unwrapKey", "wrapKey"])
|
||||||
|
.then(function(key) {
|
||||||
|
return crypto.subtle.exportKey("raw", key)
|
||||||
|
.then(function (key) {
|
||||||
|
return ab2str(key);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err){
|
||||||
|
console.log("Error derive key " + err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log("Error import key" + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _encrypt(mkey, iv, data)
|
||||||
{
|
{
|
||||||
while ((data.length % 16))
|
while ((data.length % 16))
|
||||||
data += "\0";
|
data += "\0";
|
||||||
|
|
||||||
debug("Encrypt " + data);
|
debug("Encrypt " + data);
|
||||||
|
debug("Encrypt " + iv.length);
|
||||||
|
|
||||||
data = str2ab(data);
|
data = str2ab(data);
|
||||||
|
|
||||||
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
||||||
|
|
||||||
promise = mkey.then(function(mkey){
|
promise = mkey.then(function(mkey){
|
||||||
return crypto.subtle.encrypt({
|
return crypto.subtle.encrypt({
|
||||||
name: "AES-CBC",
|
name: "AES-CBC",
|
||||||
iv: nulliv
|
iv: iv
|
||||||
}, mkey, data)})
|
}, mkey, data)})
|
||||||
.then(function(encrypted) {
|
.then(function(encrypted) {
|
||||||
return ab2str(encrypted);
|
return ab2str(encrypted);
|
||||||
|
@ -113,13 +139,14 @@ function _encrypt(mkey, data)
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _decrypt(mkey, data)
|
async function _decrypt(mkey, iv, data)
|
||||||
{
|
{
|
||||||
while ((data.length % 16))
|
while ((data.length % 16))
|
||||||
data += "\0";
|
data += "\0";
|
||||||
|
|
||||||
|
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
pkcs7_padding = new Uint8Array([16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]);
|
pkcs7_padding = new Uint8Array([16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]);
|
||||||
pkcs7_padding = await _encrypt(mkey, ab2str(pkcs7_padding));
|
pkcs7_padding = await _encrypt(mkey, nulliv, ab2str(pkcs7_padding));
|
||||||
|
|
||||||
debug("Decrypt " + data);
|
debug("Decrypt " + data);
|
||||||
|
|
||||||
|
@ -130,7 +157,7 @@ async function _decrypt(mkey, data)
|
||||||
promise = mkey.then(function(mkey){
|
promise = mkey.then(function(mkey){
|
||||||
return crypto.subtle.decrypt({
|
return crypto.subtle.decrypt({
|
||||||
name: "AES-CBC",
|
name: "AES-CBC",
|
||||||
iv: nulliv
|
iv: iv
|
||||||
}, mkey, data)})
|
}, mkey, data)})
|
||||||
.then(function(decrypted) {
|
.then(function(decrypted) {
|
||||||
return ab2str(decrypted);
|
return ab2str(decrypted);
|
||||||
|
@ -142,36 +169,63 @@ async function _decrypt(mkey, data)
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function encrypt(mkey, data)
|
async function encrypt_ecb(mkey, data)
|
||||||
{
|
{
|
||||||
var result = "";
|
var result = "";
|
||||||
|
|
||||||
|
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
|
|
||||||
while (data.length > 16)
|
while (data.length > 16)
|
||||||
{
|
{
|
||||||
res = await _encrypt(mkey, data.slice(0, 16));
|
res = await _encrypt(mkey, nulliv, data.slice(0, 16));
|
||||||
// Remove PKCS7 padding
|
// Remove PKCS7 padding
|
||||||
result += res.slice(0, 16);
|
result += res.slice(0, 16);
|
||||||
data = data.slice(16);
|
data = data.slice(16);
|
||||||
}
|
}
|
||||||
res = await _encrypt(mkey, data);
|
res = await _encrypt(mkey, nulliv, data);
|
||||||
result += res.slice(0, 16);
|
result += res.slice(0, 16);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decrypt(mkey, data)
|
async function decrypt_ecb(mkey, data)
|
||||||
{
|
{
|
||||||
var result = "";
|
var result = "";
|
||||||
|
|
||||||
|
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
|
|
||||||
while (data.length > 16)
|
while (data.length > 16)
|
||||||
{
|
{
|
||||||
res = await _decrypt(mkey, data.slice(0, 16));
|
res = await _decrypt(mkey, nulliv, data.slice(0, 16));
|
||||||
// Remove PKCS7 padding
|
// Remove PKCS7 padding
|
||||||
result += res.slice(0, 16);
|
result += res.slice(0, 16);
|
||||||
data = data.slice(16);
|
data = data.slice(16);
|
||||||
}
|
}
|
||||||
res = await _decrypt(mkey, data);
|
res = await _decrypt(mkey, nulliv, data);
|
||||||
result += res.slice(0, 16);
|
result += res.slice(0, 16);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function encrypt_cbc(mkey, iv, data)
|
||||||
|
{
|
||||||
|
var result = await _encrypt(mkey, str2ab(iv), data);
|
||||||
|
|
||||||
|
// Remove PKCS7 padding
|
||||||
|
return result.slice(0, result.length-16);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function decrypt_cbc(mkey, iv, data)
|
||||||
|
{
|
||||||
|
var result = await _decrypt(mkey, str2ab(iv), data);
|
||||||
|
|
||||||
|
// Remove PKCS7 padding
|
||||||
|
return result.slice(0, result.length-16);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function digest(data)
|
||||||
|
{
|
||||||
|
return crypto.subtle.digest("SHA-256", str2ab(data)).then(function (hash) {
|
||||||
|
return ab2str(hash);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
<br/>
|
<br/>
|
||||||
<b>PBKDF2 level</b> Number of iterations used to derivate master key <input id="pbkdf2" type="number"/><br />
|
<b>PBKDF2 level</b> Number of iterations used to derivate master key <input id="pbkdf2" type="number"/><br />
|
||||||
<br/>
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<b>Crypto v1 compatible </b> Compatible with old crypto schema (AES ECB). Use it for encrypted passwords with server <= 0.7 <input id="crypto_v1_compatible" type="checkbox"/><br />
|
||||||
|
<br/>
|
||||||
<input type="button" id="save" value="Save"/>
|
<input type="button" id="save" value="Save"/>
|
||||||
|
|
||||||
<script type="text/javascript" src="options.js">
|
<script type="text/javascript" src="options.js">
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
var default_preferences = {"pbkdf2_level": 1000,
|
var default_preferences = {"pbkdf2_level": 1000,
|
||||||
"account_url": "https://gpass-demo.soutade.fr/demo"};
|
"account_url": "https://gpass-demo.soutade.fr/demo",
|
||||||
|
"crypto_v1_compatible": true};
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
var account_url = document.getElementById('account_url').value;
|
var account_url = document.getElementById('account_url').value;
|
||||||
var pbkdf2 = document.getElementById('pbkdf2').value;
|
var pbkdf2 = document.getElementById('pbkdf2').value;
|
||||||
|
var crypto_v1_compatible = document.getElementById('crypto_v1_compatible').checked;
|
||||||
|
|
||||||
chrome.storage.local.set({
|
chrome.storage.local.set({
|
||||||
'account_url': account_url,
|
'account_url': account_url,
|
||||||
'pbkdf2': pbkdf2,
|
'pbkdf2': pbkdf2,
|
||||||
|
'crypto_v1_compatible': crypto_v1_compatible,
|
||||||
}, function() {
|
}, function() {
|
||||||
alert('Saved');
|
alert('Saved');
|
||||||
});
|
});
|
||||||
|
@ -24,8 +27,14 @@ chrome.storage.local.get(null, function(prefs) {
|
||||||
else
|
else
|
||||||
pbkdf2 = prefs['pbkdf2_level'];
|
pbkdf2 = prefs['pbkdf2_level'];
|
||||||
|
|
||||||
|
if (!prefs.hasOwnProperty("crypto_v1_compatible"))
|
||||||
|
crypto_v1_compatible = default_preferences['crypto_v1_compatible'];
|
||||||
|
else
|
||||||
|
crypto_v1_compatible = prefs['crypto_v1_compatible'];
|
||||||
|
|
||||||
document.getElementById('account_url').value = account_url;
|
document.getElementById('account_url').value = account_url;
|
||||||
document.getElementById('pbkdf2').value = pbkdf2;
|
document.getElementById('pbkdf2').value = pbkdf2;
|
||||||
|
document.getElementById('crypto_v1_compatible').checked = crypto_v1_compatible;
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('save').addEventListener("click", save);
|
document.getElementById('save').addEventListener("click", save);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user