1
0
mirror of https://github.com/bitwarden/help synced 2025-12-06 00:03:30 +00:00
Files
help/crypto.html
Kyle Spearrin d086cc2da3 use watches
2017-10-26 16:33:32 -04:00

437 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>bitwarden crypto</title>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,300italic,400italic,600italic"
rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container" id="app">
<h1>Key Derivation</h1>
<form>
<div class="row">
<div class="col-sm-4">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" class="form-control" v-model="email">
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="masterPassword">Master Password</label>
<input type="password" id="masterPassword" class="form-control" v-model="masterPassword">
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<label for="pbkdf2Iterations">Client PBKDF2 Iterations</label>
<input type="number" id="pbkdf2Iterations" class="form-control" v-model="pbkdf2Iterations">
</div>
</div>
</div>
</form>
<h2>User Key</h2>
<p>{{userKey.b64}}</p>
<h2>Master Password Hash</h2>
<p>{{userKeyHash.b64}}</p>
<h2>Generated Symmetric Key Material</h2>
<p>{{key.key.b64}}</p>
<h3>Encryption Key</h3>
<p>{{key.encKey.b64}}</p>
<h3>MAC Key</h3>
<p>{{key.macKey.b64}}</p>
<h2>Generated RSA Keypair</h2>
<h3>Public Key</h3>
<p></p>
<h3>Private Key</h3>
<p></p>
<button type="button" id="deriveKeys" class="btn btn-primary" v-on:click="generateKeys">
Regenerate Keys
</button>
<hr />
<h1>Encryption</h1>
<form>
<div class="form-group">
<label for="plaintext">Plaintext Value</label>
<input type="text" id="plaintext" class="form-control" v-model="plaintext">
</div>
</form>
<hr />
<h2>The "Cipher String"</h2>
<p>{{cipher.string}}</p>
<h2>Decrypt</h2>
<p>{{decPlaintext}}</p>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue"></script>
<script>
(function () {
// Constants/Enums
var encType = {
AesCbc256_B64: 0,
AesCbc128_HmacSha256_B64: 1,
AesCbc256_HmacSha256_B64: 2,
Rsa2048_OaepSha256_B64: 3,
Rsa2048_OaepSha1_B64: 4,
Rsa2048_OaepSha256_HmacSha256_B64: 5,
Rsa2048_OaepSha1_HmacSha256_B64: 6
};
// Classes
var Cipher = function (encType, iv, ct, mac) {
if (!arguments.length) {
this.encType = null;
this.iv = null;
this.ct = null;
this.mac = null;
this.string = null;
return;
}
this.encType = encType;
this.iv = iv;
this.ct = ct;
this.string = encType + '.' + iv.b64 + '|' + ct.b64;
this.mac = null;
if (mac) {
this.mac = mac;
this.string += ('|' + mac.b64);
}
}
var ByteData = function (buf) {
if (!arguments.length) {
this.arr = null;
this.b64 = null;
return;
}
this.arr = new Uint8Array(buf);
this.b64 = toB64(buf);
}
var SymmetricCryptoKey = function (buf) {
if (!arguments.length) {
this.key = new ByteData();
this.encKey = new ByteData();
this.macKey = new ByteData();
return;
}
this.key = new ByteData(buf);
// First half
var encKey = this.key.arr.slice(0, this.key.arr.length / 2).buffer;
this.encKey = new ByteData(encKey);
// Second half
var macKey = this.key.arr.slice(this.key.arr.length / 2).buffer;
this.macKey = new ByteData(macKey);
}
// Helpers
function fromUtf8(str) {
var strUtf8 = unescape(encodeURIComponent(str)),
bytes = new Uint8Array(strUtf8.length);
for (var i = 0; i < strUtf8.length; i++) {
bytes[i] = strUtf8.charCodeAt(i);
}
return bytes.buffer;
}
function toUtf8(buf) {
var bytes = new Uint8Array(buf),
encodedString = String.fromCharCode.apply(null, bytes),
decodedString = decodeURIComponent(escape(encodedString));
return decodedString;
}
function fromB64(str) {
var binary_string = window.atob(str),
len = binary_string.length,
bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
function toB64(buf) {
var binary = '',
bytes = new Uint8Array(buf);
for (var i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
function hasValue(str) {
return str && str !== '';
}
// Crypto
function pbkdf2(password, salt, iterations, length) {
var importAlg = {
name: 'PBKDF2'
};
var deriveAlg = {
name: 'PBKDF2',
salt: salt,
iterations: iterations,
hash: { name: 'SHA-256' }
};
var aesOptions = {
name: 'AES-CBC',
length: 256
};
return window.crypto.subtle.importKey('raw', password, importAlg, false, ['deriveKey'])
.then(function (importedKey) {
return window.crypto.subtle.deriveKey(deriveAlg, importedKey, aesOptions, true, ['encrypt']);
}).then(function (derivedKey) {
return window.crypto.subtle.exportKey('raw', derivedKey);
}).then(function (exportedKey) {
return new ByteData(exportedKey);
}).catch(function (err) {
console.error(err);
});
}
function aesEncrypt(data, key) {
var keyOptions = {
name: 'AES-CBC'
};
var encOptions = {
name: 'AES-CBC',
iv: new Uint8Array(16)
};
window.crypto.getRandomValues(encOptions.iv);
var ivData, ctData, macData;
return window.crypto.subtle.importKey('raw', key.encKey.arr.buffer, keyOptions, false, ['encrypt'])
.then(function (importedKey) {
return window.crypto.subtle.encrypt(encOptions, importedKey, data);
}).then(function (encryptedBuffer) {
ivData = new ByteData(encOptions.iv.buffer);
ctData = new ByteData(encryptedBuffer);
var dataForMac = buildDataForMac(ivData.arr, ctData.arr);
return computeMac(dataForMac.buffer, key.macKey.arr.buffer);
}).then(function (macBuffer) {
macData = new ByteData(macBuffer);
return new Cipher(encType.AesCbc256_HmacSha256_B64, ivData, ctData, macData);
}).catch(function (err) {
console.error(err);
});
}
function aesDecrypt(cipher, key) {
var keyOptions = {
name: 'AES-CBC'
};
var decOptions = {
name: 'AES-CBC',
iv: cipher.iv.arr.buffer
};
var dataForMac = buildDataForMac(cipher.iv.arr, cipher.ct.arr);
return computeMac(dataForMac.buffer, key.macKey.arr.buffer)
.then(function (macBuffer) {
return macsEqual(cipher.mac.arr.buffer, macBuffer, key.macKey.arr.buffer);
}).then(function (macsMatch) {
if (macsMatch === false) {
throw 'MAC check failed.';
}
return window.crypto.subtle.importKey('raw', key.encKey.arr.buffer, keyOptions, false, ['decrypt']);
}).then(function (importedKey) {
return window.crypto.subtle.decrypt(decOptions, importedKey, cipher.ct.arr.buffer);
}).catch(function (err) {
console.error(err);
});
}
function computeMac(data, key) {
var alg = {
name: 'HMAC',
hash: { name: 'SHA-256' }
};
return window.crypto.subtle.importKey('raw', key, alg, false, ['sign'])
.then(function (importedKey) {
return window.crypto.subtle.sign(alg, importedKey, data);
});
}
function macsEqual(mac1Data, mac2Data, key) {
var alg = {
name: 'HMAC',
hash: { name: 'SHA-256' }
};
var mac1, importedMacKey;
return window.crypto.subtle.importKey('raw', key, alg, false, ['sign'])
.then(function (importedKey) {
importedMacKey = importedKey;
return window.crypto.subtle.sign(alg, importedMacKey, mac1Data);
}).then(function (mac) {
mac1 = mac;
return window.crypto.subtle.sign(alg, importedMacKey, mac2Data);
}).then(function (mac2) {
if (mac1.byteLength !== mac2.byteLength) {
return false;
}
var arr1 = new Uint8Array(mac1);
var arr2 = new Uint8Array(mac2);
for (var i = 0; i < arr2.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
});
}
function buildDataForMac(ivArr, ctArr) {
var dataForMac = new Uint8Array(ivArr.length + ctArr.length);
dataForMac.set(ivArr, 0);
dataForMac.set(ctArr, ivArr.length);
return dataForMac;
}
// App
var vm = new Vue({
el: '#app',
data: {
email: null,
masterPassword: null,
pbkdf2Iterations: 5000,
userKey: new ByteData(),
userKeyHash: new ByteData(),
key: new SymmetricCryptoKey(),
publicKey: null,
privateKey: null,
plaintext: '',
cipher: new Cipher(),
decPlaintext: ''
},
computed: {
masterPasswordBuffer: function () {
return this.masterPassword ? fromUtf8(this.masterPassword) : null;
},
emailBuffer: function () {
return this.email ? fromUtf8(this.email) : null;
},
plaintextBuffer: function () {
return this.plaintext ? fromUtf8(this.plaintext) : null;
}
},
watch: {
userKey: function (newUserKey) {
var self = this;
if (!newUserKey || !newUserKey.arr || !self.masterPasswordBuffer) {
return new ByteData();
}
pbkdf2(newUserKey.arr.buffer, self.masterPasswordBuffer, 1, 256).then(function (userKeyHash) {
self.userKeyHash = userKeyHash;
});
}
},
methods: {
generateKeys: function () {
var key = new Uint8Array(512 / 8);
window.crypto.getRandomValues(key);
this.key = new SymmetricCryptoKey(key);
}
}
});
vm.$watch(function () {
return {
masterPassword: vm.masterPasswordBuffer,
email: vm.emailBuffer,
iterations: vm.pbkdf2Iterations
};
}, function (newVal, oldVal) {
if (!newVal.masterPassword || !newVal.email || !newVal.iterations || newVal.iterations < 1) {
vm.userKey = new ByteData();
return;
}
pbkdf2(newVal.masterPassword, newVal.email, newVal.iterations, 256)
.then(function (userKey) {
vm.userKey = userKey;
});
});
vm.$watch(function () {
return {
key: vm.key,
plaintext: vm.plaintextBuffer
};
}, function (newVal, oldVal) {
if (!newVal.key || !newVal.plaintext) {
vm.cipher = new Cipher();
vm.decPlaintext = '';
return;
}
aesEncrypt(newVal.plaintext, newVal.key)
.then(function (cipher) {
vm.cipher = cipher;
return aesDecrypt(cipher, newVal.key);
}).then(function (plaintext) {
vm.decPlaintext = toUtf8(plaintext);
});
});
// Generate initial set of keys
vm.generateKeys();
})();
</script>
</body>
</html>