mirror of
https://github.com/bitwarden/help
synced 2025-12-12 22:33:16 +00:00
crypto updates
This commit is contained in:
189
crypto.html
189
crypto.html
@@ -53,24 +53,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h2>User Key</h2>
|
<h2>Master Key</h2>
|
||||||
<pre>{{userKey.b64}}</pre>
|
<pre>{{masterKey.b64}}</pre>
|
||||||
|
|
||||||
<h2>Master Password Hash</h2>
|
<h2>Master Password Hash</h2>
|
||||||
<pre>{{userKeyHash.b64}}</pre>
|
<pre>{{masterKeyHash.b64}}</pre>
|
||||||
|
|
||||||
<h2>Generated Symmetric Key Material</h2>
|
<h2>Generated Symmetric Key</h2>
|
||||||
<pre>{{key.key.b64}}</pre>
|
<pre>{{symKey.key.b64}}</pre>
|
||||||
<h3>Encryption Key</h3>
|
<h3>Encryption Key</h3>
|
||||||
<pre>{{key.encKey.b64}}</pre>
|
<pre>{{symKey.encKey.b64}}</pre>
|
||||||
<h3>MAC Key</h3>
|
<h3>MAC Key</h3>
|
||||||
<pre>{{key.macKey.b64}}</pre>
|
<pre>{{symKey.macKey.b64}}</pre>
|
||||||
|
<h3>Protected Symmetric Key</h3>
|
||||||
|
<pre>{{protectedSymKey.string}}</pre>
|
||||||
|
|
||||||
<h2>Generated RSA Keypair</h2>
|
<h2>Generated RSA Keypair</h2>
|
||||||
<h3>Public Key</h3>
|
<h3>Public Key</h3>
|
||||||
<pre>{{publicKey.b64}}</pre>
|
<pre>{{publicKey.b64}}</pre>
|
||||||
<h3>Private Key</h3>
|
<h3>Private Key</h3>
|
||||||
<pre>{{privateKey.b64}}</pre>
|
<pre>{{privateKey.b64}}</pre>
|
||||||
|
<h3>Protected Private Key</h3>
|
||||||
|
<pre>{{protectedPrivateKey.string}}</pre>
|
||||||
|
|
||||||
<button type="button" id="deriveKeys" class="btn btn-primary" v-on:click="generateKeys">
|
<button type="button" id="deriveKeys" class="btn btn-primary" v-on:click="generateKeys">
|
||||||
<i class="fa fa-refresh"></i> Regenerate Keys
|
<i class="fa fa-refresh"></i> Regenerate Keys
|
||||||
@@ -82,16 +86,16 @@
|
|||||||
|
|
||||||
<form>
|
<form>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="plaintext">Plaintext Value</label>
|
<label for="secret">Secret Value</label>
|
||||||
<textarea id="plaintext" class="form-control" v-model="plaintext"></textarea>
|
<textarea id="secret" class="form-control" v-model="secret"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h2>The "Cipher String"</h2>
|
<h2>The "Cipher String"</h2>
|
||||||
<pre>{{cipher.string}}</pre>
|
<pre>{{protectedSecret.string}}</pre>
|
||||||
|
|
||||||
<h2>Decrypt</h2>
|
<h2>Decrypt</h2>
|
||||||
<textarea class="form-control" v-model="decPlaintext" readonly></textarea>
|
<textarea class="form-control" v-model="unprotectedSecret" readonly></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||||
@@ -102,7 +106,7 @@
|
|||||||
(function () {
|
(function () {
|
||||||
// Constants/Enums
|
// Constants/Enums
|
||||||
|
|
||||||
var encType = {
|
var encTypes = {
|
||||||
AesCbc256_B64: 0,
|
AesCbc256_B64: 0,
|
||||||
AesCbc128_HmacSha256_B64: 1,
|
AesCbc128_HmacSha256_B64: 1,
|
||||||
AesCbc256_HmacSha256_B64: 2,
|
AesCbc256_HmacSha256_B64: 2,
|
||||||
@@ -238,7 +242,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function aesEncrypt(data, key) {
|
function aesEncrypt(data, encKey, macKey) {
|
||||||
var keyOptions = {
|
var keyOptions = {
|
||||||
name: 'AES-CBC'
|
name: 'AES-CBC'
|
||||||
};
|
};
|
||||||
@@ -248,25 +252,32 @@
|
|||||||
iv: new Uint8Array(16)
|
iv: new Uint8Array(16)
|
||||||
};
|
};
|
||||||
window.crypto.getRandomValues(encOptions.iv);
|
window.crypto.getRandomValues(encOptions.iv);
|
||||||
|
var ivData = new ByteData(encOptions.iv.buffer);
|
||||||
|
|
||||||
var ivData, ctData, macData;
|
var ctData, macData;
|
||||||
return window.crypto.subtle.importKey('raw', key.encKey.arr.buffer, keyOptions, false, ['encrypt'])
|
return window.crypto.subtle.importKey('raw', encKey.arr.buffer, keyOptions, false, ['encrypt'])
|
||||||
.then(function (importedKey) {
|
.then(function (importedKey) {
|
||||||
return window.crypto.subtle.encrypt(encOptions, importedKey, data);
|
return window.crypto.subtle.encrypt(encOptions, importedKey, data);
|
||||||
}).then(function (encryptedBuffer) {
|
}).then(function (encryptedBuffer) {
|
||||||
ivData = new ByteData(encOptions.iv.buffer);
|
|
||||||
ctData = new ByteData(encryptedBuffer);
|
ctData = new ByteData(encryptedBuffer);
|
||||||
|
if (!macKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
var dataForMac = buildDataForMac(ivData.arr, ctData.arr);
|
var dataForMac = buildDataForMac(ivData.arr, ctData.arr);
|
||||||
return computeMac(dataForMac.buffer, key.macKey.arr.buffer);
|
return computeMac(dataForMac.buffer, macKey.arr.buffer);
|
||||||
}).then(function (macBuffer) {
|
}).then(function (macBuffer) {
|
||||||
macData = new ByteData(macBuffer);
|
var type = encTypes.AesCbc256_B64;
|
||||||
return new Cipher(encType.AesCbc256_HmacSha256_B64, ivData, ctData, macData);
|
if (macBuffer) {
|
||||||
|
type = encTypes.AesCbc256_HmacSha256_B64;
|
||||||
|
macData = new ByteData(macBuffer);
|
||||||
|
}
|
||||||
|
return new Cipher(type, ivData, ctData, macData);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function aesDecrypt(cipher, key) {
|
function aesDecrypt(cipher, encKey, macKey) {
|
||||||
var keyOptions = {
|
var keyOptions = {
|
||||||
name: 'AES-CBC'
|
name: 'AES-CBC'
|
||||||
};
|
};
|
||||||
@@ -276,15 +287,33 @@
|
|||||||
iv: cipher.iv.arr.buffer
|
iv: cipher.iv.arr.buffer
|
||||||
};
|
};
|
||||||
|
|
||||||
var dataForMac = buildDataForMac(cipher.iv.arr, cipher.ct.arr);
|
return new Promise(function (resolve) {
|
||||||
return computeMac(dataForMac.buffer, key.macKey.arr.buffer)
|
if (cipher.encType == encTypes.AesCbc256_B64) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!macKey) {
|
||||||
|
throw 'MAC key not provided.';
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
})
|
||||||
|
.then(function (checkMac) {
|
||||||
|
if (!checkMac) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var dataForMac = buildDataForMac(cipher.iv.arr, cipher.ct.arr);
|
||||||
|
return computeMac(dataForMac.buffer, macKey.arr.buffer)
|
||||||
|
})
|
||||||
.then(function (macBuffer) {
|
.then(function (macBuffer) {
|
||||||
return macsEqual(cipher.mac.arr.buffer, macBuffer, key.macKey.arr.buffer);
|
if (!macBuffer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return macsEqual(cipher.mac.arr.buffer, macBuffer, macKey.arr.buffer);
|
||||||
}).then(function (macsMatch) {
|
}).then(function (macsMatch) {
|
||||||
if (macsMatch === false) {
|
if (macsMatch === false) {
|
||||||
throw 'MAC check failed.';
|
throw 'MAC check failed.';
|
||||||
}
|
}
|
||||||
return window.crypto.subtle.importKey('raw', key.encKey.arr.buffer, keyOptions, false, ['decrypt']);
|
return window.crypto.subtle.importKey('raw', encKey.arr.buffer, keyOptions, false, ['decrypt']);
|
||||||
}).then(function (importedKey) {
|
}).then(function (importedKey) {
|
||||||
return window.crypto.subtle.decrypt(decOptions, importedKey, cipher.ct.arr.buffer);
|
return window.crypto.subtle.decrypt(decOptions, importedKey, cipher.ct.arr.buffer);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
@@ -377,14 +406,22 @@
|
|||||||
email: '',
|
email: '',
|
||||||
masterPassword: '',
|
masterPassword: '',
|
||||||
pbkdf2Iterations: 5000,
|
pbkdf2Iterations: 5000,
|
||||||
userKey: new ByteData(),
|
|
||||||
userKeyHash: new ByteData(),
|
masterKey: new ByteData(),
|
||||||
key: new SymmetricCryptoKey(),
|
masterKeyHash: new ByteData(),
|
||||||
|
|
||||||
|
symKey: new SymmetricCryptoKey(),
|
||||||
|
protectedSymKey: new Cipher(),
|
||||||
|
unprotectedSymKey: new ByteData(),
|
||||||
|
|
||||||
publicKey: new ByteData(),
|
publicKey: new ByteData(),
|
||||||
privateKey: new ByteData(),
|
privateKey: new ByteData(),
|
||||||
plaintext: '',
|
protectedPrivateKey: new Cipher(),
|
||||||
cipher: new Cipher(),
|
unprotectedPrivateKey: new ByteData(),
|
||||||
decPlaintext: ''
|
|
||||||
|
secret: '',
|
||||||
|
protectedSecret: new Cipher(),
|
||||||
|
unprotectedSecret: ''
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
masterPasswordBuffer: function () {
|
masterPasswordBuffer: function () {
|
||||||
@@ -393,21 +430,21 @@
|
|||||||
emailBuffer: function () {
|
emailBuffer: function () {
|
||||||
return this.email ? fromUtf8(this.email) : null;
|
return this.email ? fromUtf8(this.email) : null;
|
||||||
},
|
},
|
||||||
plaintextBuffer: function () {
|
secretBuffer: function () {
|
||||||
return this.plaintext ? fromUtf8(this.plaintext) : null;
|
return this.secret ? fromUtf8(this.secret) : null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
userKey: function (newUserKey) {
|
masterKey: function (newMasterKey) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (!newUserKey || !newUserKey.arr || !self.masterPasswordBuffer) {
|
if (!newMasterKey || !newMasterKey.arr || !self.masterPasswordBuffer) {
|
||||||
return new ByteData();
|
return new ByteData();
|
||||||
}
|
}
|
||||||
|
|
||||||
pbkdf2(newUserKey.arr.buffer, self.masterPasswordBuffer, 1, 256)
|
pbkdf2(newMasterKey.arr.buffer, self.masterPasswordBuffer, 1, 256)
|
||||||
.then(function (userKeyHash) {
|
.then(function (masterKeyHash) {
|
||||||
self.userKeyHash = userKeyHash;
|
self.masterKeyHash = masterKeyHash;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -415,9 +452,9 @@
|
|||||||
generateKeys: function () {
|
generateKeys: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var key = new Uint8Array(512 / 8);
|
var symKey = new Uint8Array(512 / 8);
|
||||||
window.crypto.getRandomValues(key);
|
window.crypto.getRandomValues(symKey);
|
||||||
self.key = new SymmetricCryptoKey(key);
|
self.symKey = new SymmetricCryptoKey(symKey);
|
||||||
|
|
||||||
generateRsaKeypair().then(function (keypair) {
|
generateRsaKeypair().then(function (keypair) {
|
||||||
self.publicKey = keypair.publicKey;
|
self.publicKey = keypair.publicKey;
|
||||||
@@ -435,44 +472,84 @@
|
|||||||
};
|
};
|
||||||
}, function (newVal, oldVal) {
|
}, function (newVal, oldVal) {
|
||||||
if (!newVal.masterPassword || !newVal.email || !newVal.iterations || newVal.iterations < 1) {
|
if (!newVal.masterPassword || !newVal.email || !newVal.iterations || newVal.iterations < 1) {
|
||||||
vm.userKey = new ByteData();
|
vm.masterKey = new ByteData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pbkdf2(newVal.masterPassword, newVal.email, newVal.iterations, 256)
|
pbkdf2(newVal.masterPassword, newVal.email, newVal.iterations, 256)
|
||||||
.then(function (userKey) {
|
.then(function (masterKey) {
|
||||||
vm.userKey = userKey;
|
vm.masterKey = masterKey;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
vm.$watch(function () {
|
vm.$watch(function () {
|
||||||
return {
|
return {
|
||||||
key: vm.key,
|
symKey: vm.symKey,
|
||||||
plaintext: vm.plaintextBuffer
|
secret: vm.secretBuffer
|
||||||
};
|
};
|
||||||
}, function (newVal, oldVal) {
|
}, function (newVal, oldVal) {
|
||||||
if (!newVal.key || !newVal.plaintext) {
|
if (!newVal.symKey || !newVal.secret) {
|
||||||
vm.cipher = new Cipher();
|
vm.protectedSecret = new Cipher();
|
||||||
vm.decPlaintext = '';
|
vm.unprotectedSecret = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
aesEncrypt(newVal.plaintext, newVal.key)
|
aesEncrypt(newVal.secret, newVal.symKey.encKey, newVal.symKey.macKey)
|
||||||
.then(function (cipher) {
|
.then(function (cipher) {
|
||||||
vm.cipher = cipher;
|
vm.protectedSecret = cipher;
|
||||||
return aesDecrypt(cipher, newVal.key);
|
return aesDecrypt(vm.protectedSecret, newVal.symKey.encKey, newVal.symKey.macKey);
|
||||||
}).then(function (plaintext) {
|
}).then(function (secret) {
|
||||||
vm.decPlaintext = toUtf8(plaintext);
|
vm.unprotectedSecret = toUtf8(secret);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate initial set of keys
|
vm.$watch(function () {
|
||||||
vm.generateKeys();
|
return {
|
||||||
|
masterKey: vm.masterKey,
|
||||||
|
symKey: vm.symKey
|
||||||
|
};
|
||||||
|
}, function (newVal, oldVal) {
|
||||||
|
if (!newVal.masterKey || !newVal.masterKey.arr || !newVal.symKey || !newVal.symKey.key) {
|
||||||
|
vm.protectedSymKey = new Cipher();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
aesEncrypt(newVal.symKey.key.arr, newVal.masterKey, null)
|
||||||
|
.then(function (cipher) {
|
||||||
|
vm.protectedSymKey = cipher;
|
||||||
|
return aesDecrypt(vm.protectedSymKey, newVal.masterKey, null);
|
||||||
|
}).then(function (unprotectedSymKey) {
|
||||||
|
vm.unprotectedSymKey = new ByteData(unprotectedSymKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.$watch(function () {
|
||||||
|
return {
|
||||||
|
symKey: vm.symKey,
|
||||||
|
privateKey: vm.privateKey
|
||||||
|
};
|
||||||
|
}, function (newVal, oldVal) {
|
||||||
|
if (!newVal.symKey || !newVal.symKey.key || !newVal.privateKey || !newVal.privateKey.arr) {
|
||||||
|
vm.protectedPrivateKey = new Cipher();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
aesEncrypt(newVal.privateKey.arr, newVal.symKey.encKey, newVal.symKey.macKey)
|
||||||
|
.then(function (cipher) {
|
||||||
|
vm.protectedPrivateKey = cipher;
|
||||||
|
return aesDecrypt(vm.protectedPrivateKey, newVal.symKey.encKey, newVal.symKey.macKey);
|
||||||
|
}).then(function (unprotectedPrivateKey) {
|
||||||
|
vm.unprotectedPrivateKey = new ByteData(unprotectedPrivateKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Set default values
|
// Set default values
|
||||||
vm.email = 'user@example.com';
|
vm.email = 'user@example.com';
|
||||||
vm.masterPassword = 'password123';
|
vm.masterPassword = 'password123';
|
||||||
vm.plaintext = 'This is a secret.';
|
vm.secret = 'This is a secret.';
|
||||||
|
|
||||||
|
// Generate initial set of keys
|
||||||
|
vm.generateKeys();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user