Skip to content

Commit

Permalink
refactor: replace bcrypt with argon2, closes #175
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Sep 25, 2020
1 parent 0ae3692 commit ff77a95
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 25 deletions.
2 changes: 1 addition & 1 deletion app/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async function getAddressTransactions(address: string) {
}

async function getTxDetails(txid: string) {
return await axios.get<Transaction | MempoolTransaction>(api + `/v1/tx/${txid}/transactions`);
return await axios.get<Transaction | MempoolTransaction>(api + `/v1/tx/${txid}`);
}

export const Api = {
Expand Down
Binary file added app/argon2.wasm
Binary file not shown.
31 changes: 20 additions & 11 deletions app/crypto/key-generation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import { generateSalt, generateDerivedKey } from './key-generation';

import crypto from 'crypto';
// https://stackoverflow.com/a/52612372/1141891
Object.defineProperty(global, 'crypto', {
value: {
getRandomValues: (arr: Uint8Array) => crypto.randomBytes(arr.length),
},
});

describe(generateDerivedKey.name, () => {
test('a bcrypt hash is returned', async () => {
test('a argon2id hash is returned', async () => {
const salt = '$2a$12$BwnByfKrfRbpxsazN712T.';
const password = 'f255cadb0af84854819c63f26c53e1a9';
const result = await generateDerivedKey({ salt, password });
expect(result).toEqual('$2a$12$BwnByfKrfRbpxsazN712T.ckDPUEMy2RJR6pyE8kOf2l3IMaxZ7R6');
const pass = 'f255cadb0af84854819c63f26c53e1a9';
const result = await generateDerivedKey({ salt, pass });
expect(result).toEqual(
'5d46ddfd7273e1a74ba1db937693bfd59de4881d58b86ed4002ee24abf156a77cf12885ee0e50de19af8c67e0115eb0a82576b11864226a6c157aac8a500e9f8'
);
});
});

describe(generateSalt.name, () => {
test('that bcrypt salt is returned', async () => {
const salt = await generateSalt();
test('that a 32char hex salt is returned', () => {
const salt = generateSalt();
expect(salt).toBeDefined();
expect(salt[0]).toEqual('$');
expect(salt.length).toEqual(29);
expect(salt.length).toEqual(32);
});

test('that salt fn is memoized per client', async () => {
const salt1 = await generateSalt();
const salt2 = await generateSalt();
test('that salt fn is memoized per client', () => {
const salt1 = generateSalt();
const salt2 = generateSalt();
expect(salt1).toEqual(salt2);
});
});
20 changes: 16 additions & 4 deletions app/crypto/key-generation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import bcryptjs from 'bcryptjs';
import { memoizeWith, identity } from 'ramda';
import argon2 from 'argon2-browser';

export async function generateDerivedKey({ password, salt }: { password: string; salt: string }) {
return bcryptjs.hash(password, salt);
export async function generateDerivedKey({ pass, salt }: { pass: string; salt: string }) {
const { hashHex } = await argon2.hash({
pass,
salt,
hashLen: 64,
type: argon2.ArgonType.Argon2id,
});
return hashHex;
}

export const generateSalt = memoizeWith(identity, async () => await bcryptjs.genSalt(12));
export function generateRandomHexString() {
const size = 16;
const randomValues = [...crypto.getRandomValues(new Uint8Array(size))];
return randomValues.map(val => ('00' + val.toString(16)).slice(-2)).join('');
}

export const generateSalt = memoizeWith(identity, () => generateRandomHexString());
6 changes: 3 additions & 3 deletions app/store/keys/keys.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ export function onboardingMnemonicGenerationStep({ stepDelayMs }: { stepDelayMs:
export function setPassword({ password, history }: { password: string; history: History }) {
return async (dispatch: Dispatch, getState: () => RootState) => {
const mnemonic = selectMnemonic(getState());
const salt = await generateSalt();
const derivedEncryptionKey = await generateDerivedKey({ password, salt });
const salt = generateSalt();
const derivedEncryptionKey = await generateDerivedKey({ pass: password, salt });

if (!mnemonic) {
log.error('Cannot derive encryption key unless a mnemonic has been generated');
Expand Down Expand Up @@ -82,7 +82,7 @@ export function decryptWallet({ password, history }: { password: string; history
return;
}

const key = await generateDerivedKey({ password, salt });
const key = await generateDerivedKey({ pass: password, salt });

//
// TODO: remove casting within blockstack.js library
Expand Down
20 changes: 20 additions & 0 deletions configs/webpack.config.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default {
externals: [...Object.keys(externals || {})],

module: {
noParse: /\.wasm$/,
rules: [
{
test: /\.tsx?$/,
Expand All @@ -21,9 +22,28 @@ export default {
},
},
},
// Rule added to support `argon2-browser` library
{
test: /\.wasm$/,
// Tells WebPack that this module should be included as
// base64-encoded binary file and not as code
loaders: ['base64-loader'],
// Disables WebPack's opinion where WebAssembly should be,
// makes it think that it's not WebAssembly
//
// Error: WebAssembly module is included in initial chunk.
type: 'javascript/auto',
},
],
},

node: {
__dirname: false,
fs: 'empty',
Buffer: false,
process: false,
},

output: {
path: path.join(__dirname, '..', 'app'),
// https://github.com/webpack/webpack/issues/1114
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"@blockstack/prettier-config": "0.0.6",
"@blockstack/stacks-blockchain-sidecar-types": "0.0.20",
"@commitlint/config-conventional": "9.0.1",
"@types/argon2-browser": "1.12.0",
"@types/bcryptjs": "2.4.2",
"@types/bn.js": "4.11.6",
"@types/css-font-loading-module": "0.0.4",
Expand Down Expand Up @@ -145,6 +146,7 @@
"babel-loader": "8.1.0",
"babel-plugin-dev-expression": "0.2.2",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"base64-loader": "1.0.0",
"browserslist-config-erb": "0.0.1",
"chalk": "4.1.0",
"circular-dependency-plugin": "5.2.0",
Expand Down Expand Up @@ -197,8 +199,8 @@
"@hot-loader/react-dom": "16.13.0",
"@reduxjs/toolkit": "1.4.0",
"@styled-system/theme-get": "5.1.2",
"argon2-browser": "1.13.0",
"axios": "0.19.2",
"bcryptjs": "2.4.3",
"bignumber.js": "9.0.0",
"bn.js": "5.1.2",
"buffer": "5.6.0",
Expand Down
20 changes: 15 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2321,6 +2321,11 @@
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==

"@types/argon2-browser@1.12.0":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@types/argon2-browser/-/argon2-browser-1.12.0.tgz#b9c3c4a996a80cb3c4f5ad097e8d0debda459072"
integrity sha512-ZPpKOoLuyXf+dKUJKcbUAzAajnJ1+ABXqSyHtsjfDaKhdHh19wXNWC3/hpDdzIO8ykiSONnGCWy38juDZVocvg==

"@types/babel__core@^7.0.0":
version "7.1.9"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
Expand Down Expand Up @@ -3375,6 +3380,11 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"

argon2-browser@1.13.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/argon2-browser/-/argon2-browser-1.13.0.tgz#8b59b159f6d84a6a0d96806793edbc68d1b0456c"
integrity sha512-N96O9SUCzBL4EfPjkfjbeUTGSua6ULMwo8P3pWUhvu2HQcPKQBbEVhT7s3Ffp9G6Z+v21qh6wcwYKPRjVROChA==

argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
Expand Down Expand Up @@ -3769,6 +3779,11 @@ base64-js@^1.0.2:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==

base64-loader@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/base64-loader/-/base64-loader-1.0.0.tgz#e530bad88e906dd2a1fad0af2d9e683fa8bd92a8"
integrity sha1-5TC62I6QbdKh+tCvLZ5oP6i9kqg=

base@^0.11.1:
version "0.11.2"
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
Expand All @@ -3794,11 +3809,6 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"

bcryptjs@2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=

bfj@^6.1.1:
version "6.1.2"
resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f"
Expand Down

0 comments on commit ff77a95

Please sign in to comment.