Skip to content

Commit 272cb5e

Browse files
committed
Use scrypt for key derivation
Previously using a simple SHA-256 without salt function for the key seed. This is inadequate for proper security to protect a user's sensitive information (i.e. private key).
1 parent f0ecaeb commit 272cb5e

File tree

4 files changed

+37
-21
lines changed

4 files changed

+37
-21
lines changed

src/background/crypto.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createHash, randomBytes } from 'crypto';
1+
import { scryptSync, createHash, randomBytes, ScryptOptions } from 'crypto';
22
import sodium from 'libsodium-wrappers';
33
import assert from 'assert';
44

@@ -61,8 +61,8 @@ export class SecretKey {
6161
}
6262
}
6363

64-
public static fromString(password: string): SecretKey {
65-
const h = hash(password);
64+
public static derive(password: string, salt: Uint8Array, opts: ScryptOptions): SecretKey {
65+
const h = scryptSync(password, salt, 32, opts);
6666
return new SecretKey(h);
6767
}
6868
}

src/background/settings.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readFileSync, unlinkSync, renameSync, existsSync, writeFileSync } from 'fs';
22
import { KeyPair, Script, ScriptHash } from 'godcoin';
3+
import { randomBytes, ScryptOptions } from 'crypto';
34
import { SecretKey } from './crypto';
45
import { Logger } from '../log';
56
import { app } from 'electron';
@@ -8,6 +9,7 @@ import path from 'path';
89
const log = new Logger('main:settings');
910

1011
const CRYPTO_VERSION = 1;
12+
const SALT_BYTES = 64;
1113

1214
let globalSettings: Settings | undefined;
1315

@@ -16,6 +18,16 @@ interface SettingsData {
1618
keyPair: KeyPair;
1719
}
1820

21+
function v1ScryptParams(): ScryptOptions {
22+
const N = 2 ** 15;
23+
return {
24+
N,
25+
r: 8,
26+
p: 1,
27+
maxmem: 128 * N * 8 * 2,
28+
};
29+
}
30+
1931
export class Settings implements SettingsData {
2032
public readonly dbSecretKey: SecretKey;
2133
public readonly keyPair: KeyPair;
@@ -66,11 +78,12 @@ export class Settings implements SettingsData {
6678
'utf8',
6779
);
6880

69-
const localKey = SecretKey.fromString(password);
81+
const salt = randomBytes(SALT_BYTES);
82+
const localKey = SecretKey.derive(password, salt, v1ScryptParams());
7083
const encData = localKey.encrypt(unencryptedData);
7184
localKey.zero();
7285

73-
return Buffer.concat([version, encData]);
86+
return Buffer.concat([version, salt, encData]);
7487
}
7588

7689
public static load(password: string): Settings {
@@ -119,12 +132,13 @@ export class Settings implements SettingsData {
119132
private static deserializeEnc(loc: string, password: string): Settings {
120133
const fileData = readFileSync(loc);
121134
const version = fileData.readUInt16BE(0);
122-
const encData = fileData.slice(2);
123135

124136
let data: SettingsData;
125137
switch (version) {
126138
case 1: {
127-
const localKey = SecretKey.fromString(password);
139+
const salt = fileData.slice(2, 2 + SALT_BYTES);
140+
const encData = fileData.slice(2 + SALT_BYTES);
141+
const localKey = SecretKey.derive(password, salt, v1ScryptParams());
128142
try {
129143
const obj = JSON.parse(localKey.decrypt(encData).toString('utf8'));
130144
data = {

src/renderer/ipc.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,26 @@ class IpcManager {
3030
password,
3131
privateKey: typeof keyPair === 'string' ? keyPair : keyPair.privateKey.toWif(),
3232
});
33-
const ipcRes = await this.send({
34-
type: 'wallet:pre_init',
35-
password,
36-
});
37-
if (ipcRes.type !== 'wallet:pre_init') {
38-
throw new Error('Unexpected IPC response: ' + JSON.stringify(ipcRes));
39-
}
33+
const ipcRes = await this.preInit(password);
4034
if (ipcRes.status !== 'success') {
4135
throw new Error('Failed to complete load settings after setup: ' + ipcRes.status);
4236
}
4337
}
4438

39+
public async preInit(password: string): Promise<models.PreInitWalletRes> {
40+
const ipcRes = await this.send(
41+
{
42+
type: 'wallet:pre_init',
43+
password,
44+
},
45+
15000,
46+
);
47+
if (ipcRes.type !== 'wallet:pre_init') {
48+
throw new Error('Unexpected IPC response: ' + JSON.stringify(ipcRes));
49+
}
50+
return ipcRes;
51+
}
52+
4553
public async postInit(): Promise<models.PostInitWalletRes> {
4654
const ipcRes = await this.send({
4755
type: 'wallet:post_init',

src/views/start/Welcome.vue

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,7 @@ export default class Welcome extends Vue {
9494
try {
9595
if (!this.isReady) return;
9696
const password = this.password;
97-
const ipcRes = await ipc.send({
98-
type: 'wallet:pre_init',
99-
password,
100-
});
101-
if (ipcRes.type !== 'wallet:pre_init') {
102-
throw new Error('Unexpected IPC response: ' + JSON.stringify(ipcRes));
103-
}
97+
const ipcRes = await ipc.preInit(password);
10498
const status = ipcRes.status;
10599
switch (status) {
106100
case 'success':

0 commit comments

Comments
 (0)