bump patch version

pull/160/head
robinmoisson 2023-03-01 18:39:31 +01:00
rodzic 0cdb80a419
commit 69a101e68f
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 9419716500078583
4 zmienionych plików z 261 dodań i 70 usunięć

Wyświetl plik

@ -96,27 +96,6 @@
font-size: 1.5em;
}
.staticrypt-footer {
position: fixed;
height: 20px;
font-size: 16px;
padding: 2px;
bottom: 0;
left: 0;
right: 0;
margin-bottom: 0;
}
.staticrypt-footer p {
margin: 2px;
text-align: center;
float: right;
}
.staticrypt-footer a {
text-decoration: none;
}
label.staticrypt-remember {
display: flex;
align-items: center;
@ -202,10 +181,6 @@
</div>
</div>
<footer class="staticrypt-footer">
<p class="pull-right">Created with <a href="https://robinmoisson.github.io/staticrypt">StatiCrypt</a></p>
</footer>
</div>
@ -262,20 +237,90 @@ exports.decrypt = decrypt;
* @returns string
*/
function hashPassphrase(passphrase, salt) {
var hashedPassphrase = CryptoJS.PBKDF2(passphrase, salt, {
keySize: 256 / 32,
iterations: 1000,
});
// we hash the passphrase in two steps: first 1k iterations, then we add iterations. This is because we used to use 1k,
// so for backwards compatibility with remember-me/autodecrypt links, we need to support going from that to more
// iterations
var hashedPassphrase = hashLegacyRound(passphrase, salt);
return hashedPassphrase.toString();
return hashSecondRound(hashedPassphrase, salt);
}
exports.hashPassphrase = hashPassphrase;
/**
* This hashes the passphrase with 1k iterations. This is a low number, we need this function to support backwards
* compatibility.
*
* @param {string} passphrase
* @param {string} salt
* @returns {string}
*/
function hashLegacyRound(passphrase, salt) {
return CryptoJS.PBKDF2(passphrase, salt, {
keySize: 256 / 32,
iterations: 1000,
}).toString();
}
exports.hashLegacyRound = hashLegacyRound;
/**
* Add a second round of iterations. This is because we used to use 1k, so for backwards compatibility with
* remember-me/autodecrypt links, we need to support going from that to more iterations.
*
* @param hashedPassphrase
* @param salt
* @returns {string}
*/
function hashSecondRound(hashedPassphrase, salt) {
return CryptoJS.PBKDF2(hashedPassphrase, salt, {
keySize: 256 / 32,
iterations: 14000,
hasher: CryptoJS.algo.SHA256,
}).toString();
}
exports.hashSecondRound = hashSecondRound;
function generateRandomSalt() {
return CryptoJS.lib.WordArray.random(128 / 8).toString();
}
exports.generateRandomSalt = generateRandomSalt;
function getRandomAlphanum() {
var possibleCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var byteArray;
var parsedInt;
// Keep generating new random bytes until we get a value that falls
// within a range that can be evenly divided by possibleCharacters.length
do {
byteArray = CryptoJS.lib.WordArray.random(1);
// extract the lowest byte to get an int from 0 to 255 (probably unnecessary, since we're only generating 1 byte)
parsedInt = byteArray.words[0] & 0xff;
} while (parsedInt >= 256 - (256 % possibleCharacters.length));
// Take the modulo of the parsed integer to get a random number between 0 and totalLength - 1
var randomIndex = parsedInt % possibleCharacters.length;
return possibleCharacters[randomIndex];
}
/**
* Generate a random string of a given length.
*
* @param {int} length
* @returns {string}
*/
function generateRandomString(length) {
var randomString = '';
for (var i = 0; i < length; i++) {
randomString += getRandomAlphanum();
}
return randomString;
}
exports.generateRandomString = generateRandomString;
function signMessage(hashedPassphrase, message) {
return CryptoJS.HmacSHA256(
message,
@ -294,22 +339,31 @@ exports.signMessage = signMessage;
* @param cryptoEngine - the engine to use for encryption / decryption
*/
function init(cryptoEngine) {
// TODO: remove on next major version bump. This is a hack to make the salt available in all functions here in a
// backward compatible way (not requiring to change the password_template).
const backwardCompatibleSalt = 'b93bbaf35459951c47721d1f3eaeb5b9';
const exports = {};
/**
* Top-level function for encoding a message.
* Includes passphrase hashing, encryption, and signing.
* Includes password hashing, encryption, and signing.
*
* @param {string} msg
* @param {string} passphrase
* @param {string} password
* @param {string} salt
* @param {boolean} isLegacy - whether to use the legacy hashing algorithm (1k iterations) or not
*
* @returns {string} The encoded text
*/
function encode(msg, passphrase, salt) {
const hashedPassphrase = cryptoEngine.hashPassphrase(passphrase, salt);
function encode(msg, password, salt, isLegacy = false) {
// TODO: remove in the next major version bump. This is to not break backwards compatibility with the old way of hashing
const hashedPassphrase = isLegacy
? cryptoEngine.hashLegacyRound(password, salt)
: cryptoEngine.hashPassphrase(password, salt);
const encrypted = cryptoEngine.encrypt(msg, hashedPassphrase);
// we use the hashed passphrase in the HMAC because this is effectively what will be used a passphrase (so we can store
// it in localStorage safely, we don't use the clear text passphrase)
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = cryptoEngine.signMessage(hashedPassphrase, encrypted);
return hmac + encrypted;
@ -318,21 +372,49 @@ function init(cryptoEngine) {
/**
* Top-level function for decoding a message.
* Includes signature check, an decryption.
* Includes signature check and decryption.
*
* @param {string} signedMsg
* @param {string} hashedPassphrase
* @param {string} backwardCompatibleHashedPassword
*
* @returns {Object} {success: true, decoded: string} | {success: false, message: string}
*/
function decode(signedMsg, hashedPassphrase) {
function decode(signedMsg, hashedPassphrase, backwardCompatibleHashedPassword = '') {
const encryptedHMAC = signedMsg.substring(0, 64);
const encryptedMsg = signedMsg.substring(64);
const decryptedHMAC = cryptoEngine.signMessage(hashedPassphrase, encryptedMsg);
if (decryptedHMAC !== encryptedHMAC) {
// TODO: remove in next major version bump. This is to not break backwards compatibility with the old 1k
// iterations in PBKDF2 - if the key we try isn't working, it might be because it's a remember-me/autodecrypt
// link key, generated with 1k iterations. Try again with the updated iteration count.
if (!backwardCompatibleHashedPassword) {
return decode(
signedMsg,
cryptoEngine.hashSecondRound(hashedPassphrase, backwardCompatibleSalt),
hashedPassphrase
);
}
return { success: false, message: "Signature mismatch" };
}
// TODO: remove in next major version bump. If we're trying to double hash for backward compatibility reasons,
// and the attempt is successful, we check if we should update the stored password in localStorage. This avoids
// having to compute the upgrade each time.
if (backwardCompatibleHashedPassword) {
if (window && window.localStorage) {
const storedPassword = window.localStorage.getItem('staticrypt_passphrase');
// check the stored password is actually the backward compatible one, so we don't save the new one and trigger
// the "remember-me" by mistake, leaking the password
if (storedPassword === backwardCompatibleHashedPassword) {
window.localStorage.setItem('staticrypt_passphrase', hashedPassphrase);
}
}
}
return {
success: true,
decoded: cryptoEngine.decrypt(encryptedMsg, hashedPassphrase),

Wyświetl plik

@ -43,7 +43,7 @@
</h1>
<p>
Based on the <a href="https://github.com/brix/crypto-js">crypto-js library</a>, StatiCrypt uses AES-256
to encrypt your string with your passphrase in your browser (client side).
to encrypt your string with your long password in your browser (client side).
</p>
<p>
Download your encrypted string in a HTML page with a password prompt you can upload anywhere (see <a
@ -62,8 +62,8 @@
</h4>
<div id="concept" class="hidden">
<p>
<b class="text-danger">Disclaimer</b> if you have extra sensitive banking data, you should probably
use something else!
<b class="text-danger">Disclaimer</b> if you are an at-risk activist, or have extra sensitive
banking data, you should probably use something else!
</p>
<p>
StatiCrypt generates a static, password protected page that can be decrypted in-browser:
@ -72,13 +72,14 @@
</p>
<p>
It basically encrypts your page and puts everything with a user-friendly way to use a password
in the new file.
<br>AES-256 is state of the art but <b>brute-force/dictionary attacks would be trivial to
do at a really fast pace: use a long, unusual passphrase!</b>
in the new file. AES-256 is state of the art but <b>brute-force/dictionary attacks would be easy to
do at a really fast pace: use a long, unusual password!</b>
<br/> => To be safe, we recommend 16+ alphanum characters, and using a password manager like the
open-source <a href="http://bitwarden.com">Bitwarden</a>.
</p>
<p>
Feel free to contribute or report any thought to the
<a href="https://github.com/robinmoisson/staticrypt">GitHub project</a>!
<a href="https://github.com/robinmoisson/staticrypt">GitHub project</a>.
</p>
</div>
<br>
@ -88,9 +89,9 @@
<div class="col-xs-12">
<form id="encrypt_form">
<div class="form-group">
<label for="passphrase">Passphrase</label>
<label for="passphrase">Password</label>
<input type="password" class="form-control" id="passphrase"
placeholder="Passphrase (choose a long one!)">
placeholder="Password (choose a long one!)">
</div>
<div class="form-group">
@ -247,20 +248,90 @@ exports.decrypt = decrypt;
* @returns string
*/
function hashPassphrase(passphrase, salt) {
var hashedPassphrase = CryptoJS.PBKDF2(passphrase, salt, {
keySize: 256 / 32,
iterations: 1000,
});
// we hash the passphrase in two steps: first 1k iterations, then we add iterations. This is because we used to use 1k,
// so for backwards compatibility with remember-me/autodecrypt links, we need to support going from that to more
// iterations
var hashedPassphrase = hashLegacyRound(passphrase, salt);
return hashedPassphrase.toString();
return hashSecondRound(hashedPassphrase, salt);
}
exports.hashPassphrase = hashPassphrase;
/**
* This hashes the passphrase with 1k iterations. This is a low number, we need this function to support backwards
* compatibility.
*
* @param {string} passphrase
* @param {string} salt
* @returns {string}
*/
function hashLegacyRound(passphrase, salt) {
return CryptoJS.PBKDF2(passphrase, salt, {
keySize: 256 / 32,
iterations: 1000,
}).toString();
}
exports.hashLegacyRound = hashLegacyRound;
/**
* Add a second round of iterations. This is because we used to use 1k, so for backwards compatibility with
* remember-me/autodecrypt links, we need to support going from that to more iterations.
*
* @param hashedPassphrase
* @param salt
* @returns {string}
*/
function hashSecondRound(hashedPassphrase, salt) {
return CryptoJS.PBKDF2(hashedPassphrase, salt, {
keySize: 256 / 32,
iterations: 14000,
hasher: CryptoJS.algo.SHA256,
}).toString();
}
exports.hashSecondRound = hashSecondRound;
function generateRandomSalt() {
return CryptoJS.lib.WordArray.random(128 / 8).toString();
}
exports.generateRandomSalt = generateRandomSalt;
function getRandomAlphanum() {
var possibleCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var byteArray;
var parsedInt;
// Keep generating new random bytes until we get a value that falls
// within a range that can be evenly divided by possibleCharacters.length
do {
byteArray = CryptoJS.lib.WordArray.random(1);
// extract the lowest byte to get an int from 0 to 255 (probably unnecessary, since we're only generating 1 byte)
parsedInt = byteArray.words[0] & 0xff;
} while (parsedInt >= 256 - (256 % possibleCharacters.length));
// Take the modulo of the parsed integer to get a random number between 0 and totalLength - 1
var randomIndex = parsedInt % possibleCharacters.length;
return possibleCharacters[randomIndex];
}
/**
* Generate a random string of a given length.
*
* @param {int} length
* @returns {string}
*/
function generateRandomString(length) {
var randomString = '';
for (var i = 0; i < length; i++) {
randomString += getRandomAlphanum();
}
return randomString;
}
exports.generateRandomString = generateRandomString;
function signMessage(hashedPassphrase, message) {
return CryptoJS.HmacSHA256(
message,
@ -282,22 +353,31 @@ exports.signMessage = signMessage;
* @param cryptoEngine - the engine to use for encryption / decryption
*/
function init(cryptoEngine) {
// TODO: remove on next major version bump. This is a hack to make the salt available in all functions here in a
// backward compatible way (not requiring to change the password_template).
const backwardCompatibleSalt = '##SALT##';
const exports = {};
/**
* Top-level function for encoding a message.
* Includes passphrase hashing, encryption, and signing.
* Includes password hashing, encryption, and signing.
*
* @param {string} msg
* @param {string} passphrase
* @param {string} password
* @param {string} salt
* @param {boolean} isLegacy - whether to use the legacy hashing algorithm (1k iterations) or not
*
* @returns {string} The encoded text
*/
function encode(msg, passphrase, salt) {
const hashedPassphrase = cryptoEngine.hashPassphrase(passphrase, salt);
function encode(msg, password, salt, isLegacy = false) {
// TODO: remove in the next major version bump. This is to not break backwards compatibility with the old way of hashing
const hashedPassphrase = isLegacy
? cryptoEngine.hashLegacyRound(password, salt)
: cryptoEngine.hashPassphrase(password, salt);
const encrypted = cryptoEngine.encrypt(msg, hashedPassphrase);
// we use the hashed passphrase in the HMAC because this is effectively what will be used a passphrase (so we can store
// it in localStorage safely, we don't use the clear text passphrase)
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = cryptoEngine.signMessage(hashedPassphrase, encrypted);
return hmac + encrypted;
@ -306,21 +386,49 @@ function init(cryptoEngine) {
/**
* Top-level function for decoding a message.
* Includes signature check, an decryption.
* Includes signature check and decryption.
*
* @param {string} signedMsg
* @param {string} hashedPassphrase
* @param {string} backwardCompatibleHashedPassword
*
* @returns {Object} {success: true, decoded: string} | {success: false, message: string}
*/
function decode(signedMsg, hashedPassphrase) {
function decode(signedMsg, hashedPassphrase, backwardCompatibleHashedPassword = '') {
const encryptedHMAC = signedMsg.substring(0, 64);
const encryptedMsg = signedMsg.substring(64);
const decryptedHMAC = cryptoEngine.signMessage(hashedPassphrase, encryptedMsg);
if (decryptedHMAC !== encryptedHMAC) {
// TODO: remove in next major version bump. This is to not break backwards compatibility with the old 1k
// iterations in PBKDF2 - if the key we try isn't working, it might be because it's a remember-me/autodecrypt
// link key, generated with 1k iterations. Try again with the updated iteration count.
if (!backwardCompatibleHashedPassword) {
return decode(
signedMsg,
cryptoEngine.hashSecondRound(hashedPassphrase, backwardCompatibleSalt),
hashedPassphrase
);
}
return { success: false, message: "Signature mismatch" };
}
// TODO: remove in next major version bump. If we're trying to double hash for backward compatibility reasons,
// and the attempt is successful, we check if we should update the stored password in localStorage. This avoids
// having to compute the upgrade each time.
if (backwardCompatibleHashedPassword) {
if (window && window.localStorage) {
const storedPassword = window.localStorage.getItem('staticrypt_passphrase');
// check the stored password is actually the backward compatible one, so we don't save the new one and trigger
// the "remember-me" by mistake, leaking the password
if (storedPassword === backwardCompatibleHashedPassword) {
window.localStorage.setItem('staticrypt_passphrase', hashedPassphrase);
}
}
}
return {
success: true,
decoded: cryptoEngine.decrypt(encryptedMsg, hashedPassphrase),

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "staticrypt",
"version": "2.3.5",
"version": "2.3.6",
"description": "Based on the [crypto-js](https://github.com/brix/crypto-js) library, StatiCrypt uses AES-256 to encrypt your input with your passphrase and put it in a HTML file with a password prompt that can decrypted in-browser (client side).",
"main": "index.js",
"files": [

Wyświetl plik

@ -43,7 +43,7 @@
</h1>
<p>
Based on the <a href="https://github.com/brix/crypto-js">crypto-js library</a>, StatiCrypt uses AES-256
to encrypt your string with your passphrase in your browser (client side).
to encrypt your string with your long password in your browser (client side).
</p>
<p>
Download your encrypted string in a HTML page with a password prompt you can upload anywhere (see <a
@ -62,8 +62,8 @@
</h4>
<div id="concept" class="hidden">
<p>
<b class="text-danger">Disclaimer</b> if you have extra sensitive banking data, you should probably
use something else!
<b class="text-danger">Disclaimer</b> if you are an at-risk activist, or have extra sensitive
banking data, you should probably use something else!
</p>
<p>
StatiCrypt generates a static, password protected page that can be decrypted in-browser:
@ -72,13 +72,14 @@
</p>
<p>
It basically encrypts your page and puts everything with a user-friendly way to use a password
in the new file.
<br>AES-256 is state of the art but <b>brute-force/dictionary attacks would be trivial to
do at a really fast pace: use a long, unusual passphrase!</b>
in the new file. AES-256 is state of the art but <b>brute-force/dictionary attacks would be easy to
do at a really fast pace: use a long, unusual password!</b>
<br/> => To be safe, we recommend 16+ alphanum characters, and using a password manager like the
open-source <a href="http://bitwarden.com">Bitwarden</a>.
</p>
<p>
Feel free to contribute or report any thought to the
<a href="https://github.com/robinmoisson/staticrypt">GitHub project</a>!
<a href="https://github.com/robinmoisson/staticrypt">GitHub project</a>.
</p>
</div>
<br>
@ -88,9 +89,9 @@
<div class="col-xs-12">
<form id="encrypt_form">
<div class="form-group">
<label for="passphrase">Passphrase</label>
<label for="passphrase">Password</label>
<input type="password" class="form-control" id="passphrase"
placeholder="Passphrase (choose a long one!)">
placeholder="Password (choose a long one!)">
</div>
<div class="form-group">