mirror of
https://github.com/bitwarden/help
synced 2025-12-06 00:03:30 +00:00
crypto implementations
This commit is contained in:
329
crypto.html
329
crypto.html
@@ -7,14 +7,13 @@
|
||||
|
||||
<title>bitwarden crypto</title>
|
||||
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
rel="stylesheet">
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,300italic,400italic,600italic"
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,300italic,400italic,600italic"
|
||||
rel="stylesheet">
|
||||
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
|
||||
rel="stylesheet">
|
||||
|
||||
<script src="//unpkg.com/vue"></script>
|
||||
<!-- 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>
|
||||
@@ -37,20 +36,21 @@
|
||||
<label for="pbkdf2Iterations">PBKDF2 Client Iterations</label>
|
||||
<input type="number" id="pbkdf2Iterations" class="form-control" v-model="pbkdf2Iterations">
|
||||
</div>
|
||||
<button type="button" id="deriveKeys" class="btn btn-default"
|
||||
v-on:click="deriveKeys">
|
||||
<button type="button" id="deriveKeys" class="btn btn-default" v-on:click="deriveKeys">
|
||||
Derive Keys
|
||||
</button>
|
||||
</form>
|
||||
<hr>
|
||||
<h2>Private Key</h2>
|
||||
<p>{{key.b64}}</p>
|
||||
<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>
|
||||
|
||||
@@ -58,49 +58,149 @@
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="plaintext">Plaintext Value</label>
|
||||
<input type="text" id="plaintext" class="form-control">
|
||||
<input type="text" id="plaintext" class="form-control" v-model="plaintext">
|
||||
</div>
|
||||
<button type="button" id="encrypt" class="btn btn-default">Encrypt</button>
|
||||
<button type="button" id="encrypt" class="btn btn-default" v-on:click="encrypt">
|
||||
Encrypt
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<h2>The "Cipher String"</h2>
|
||||
<p>{{cipher.string}}</p>
|
||||
|
||||
<h2>Decrypt</h2>
|
||||
<p>{{decPlaintext}}</p>
|
||||
</div>
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
<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 () {
|
||||
var CipherString = function (type, iv, ct, mac) {
|
||||
this.type = type;
|
||||
// Objects
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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.mac = mac;
|
||||
this.string = type + '.' + iv.b64 + '|' + ct.b64;
|
||||
this.string = encType + '.' + iv.b64 + '|' + ct.b64;
|
||||
|
||||
this.mac = null;
|
||||
if (mac) {
|
||||
this.mac = mac;
|
||||
this.string += ('|' + mac.b64);
|
||||
}
|
||||
}
|
||||
|
||||
var ByteData = function (buf) {
|
||||
if (!buf) {
|
||||
if (!arguments.length) {
|
||||
this.arr = null;
|
||||
this.b64 = null;
|
||||
return;
|
||||
}
|
||||
this.buf = buf;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Methods
|
||||
|
||||
function pbkdf2(password, salt, iterations, length) {
|
||||
return window.crypto.subtle.importKey('raw', password, { name: 'PBKDF2' },
|
||||
false, ['deriveKey', 'deriveBits']).then(function (importedKey) {
|
||||
return window.crypto.subtle.deriveKey({
|
||||
'name': 'PBKDF2',
|
||||
salt: salt,
|
||||
iterations: iterations,
|
||||
hash: { name: 'SHA-256' }
|
||||
}, importedKey, {
|
||||
name: 'AES-CBC',
|
||||
length: 256
|
||||
}, true, ['encrypt', 'decrypt']);
|
||||
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) {
|
||||
@@ -110,69 +210,158 @@
|
||||
});
|
||||
}
|
||||
|
||||
function fromUtf8(str) {
|
||||
var strUtf8 = unescape(encodeURIComponent(str));
|
||||
var ab = new Uint8Array(strUtf8.length);
|
||||
for (var i = 0; i < strUtf8.length; i++) {
|
||||
ab[i] = strUtf8.charCodeAt(i);
|
||||
}
|
||||
return ab;
|
||||
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 toUtf8(buf) {
|
||||
var bytes = new Uint8Array(buf);
|
||||
var encodedString = String.fromCharCode.apply(null, bytes),
|
||||
decodedString = decodeURIComponent(escape(encodedString));
|
||||
return decodedString;
|
||||
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 fromB64(str) {
|
||||
var binary_string = window.atob(str);
|
||||
var len = binary_string.length;
|
||||
var bytes = new Uint8Array(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
bytes[i] = binary_string.charCodeAt(i);
|
||||
}
|
||||
return bytes.buffer;
|
||||
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 toB64(buf) {
|
||||
var binary = '';
|
||||
var bytes = new Uint8Array(buf);
|
||||
var len = bytes.byteLength;
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
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
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
email: null,
|
||||
masterPassword: null,
|
||||
pbkdf2Iterations: 5000,
|
||||
key: new ByteData(),
|
||||
keyHash: null,
|
||||
keyMaterial: null,
|
||||
keyMaterialEnc: null,
|
||||
keyMaterialMac: null,
|
||||
userKey: new ByteData(),
|
||||
userKeyHash: new ByteData(),
|
||||
key: new SymmetricCryptoKey(),
|
||||
publicKey: null,
|
||||
privateKey: null,
|
||||
plaintext: null,
|
||||
cipherString: null,
|
||||
decPlaintext: null
|
||||
plaintext: '',
|
||||
cipher: new Cipher(),
|
||||
decPlaintext: ''
|
||||
},
|
||||
methods: {
|
||||
deriveKeys: function () {
|
||||
var self = this,
|
||||
password = fromUtf8(this.masterPassword),
|
||||
salt = fromUtf8(this.email);
|
||||
password = fromUtf8(self.masterPassword),
|
||||
salt = fromUtf8(self.email);
|
||||
|
||||
pbkdf2(password, salt, this.pbkdf2Iterations, 256).then(function (key) {
|
||||
self.key = key;
|
||||
pbkdf2(password, salt, self.pbkdf2Iterations, 256).then(function (userKey) {
|
||||
self.userKey = userKey;
|
||||
return pbkdf2(userKey.arr.buffer, password, 1, 256);
|
||||
}).then(function (userKeyHash) {
|
||||
self.userKeyHash = userKeyHash;
|
||||
var key = new Uint8Array(512 / 8);
|
||||
window.crypto.getRandomValues(key);
|
||||
self.key = new SymmetricCryptoKey(key.buffer);
|
||||
}).then(function () {
|
||||
// Something else...
|
||||
});
|
||||
},
|
||||
encrypt: function () {
|
||||
var self = this,
|
||||
data = fromUtf8(self.plaintext);
|
||||
|
||||
aesEncrypt(data, self.key)
|
||||
.then(function (cipher) {
|
||||
self.cipher = cipher;
|
||||
return aesDecrypt(cipher, self.key);
|
||||
}).then(function (plaintext) {
|
||||
self.decPlaintext = toUtf8(plaintext);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user