-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathencrypt.js
123 lines (99 loc) · 3.83 KB
/
encrypt.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/* @flow */
// TODO: lifted wholesale from the Browser.
// will remove once it's merged to blockstack.js
import bip39 from 'bip39'
import crypto from 'crypto'
import triplesec from 'triplesec'
function normalizeMnemonic(mnemonic: string) {
return bip39.mnemonicToEntropy(mnemonic).toString('hex')
}
function denormalizeMnemonic(normalizedMnemonic : string) {
return bip39.entropyToMnemonic(normalizedMnemonic)
}
function encryptMnemonic(plaintextBuffer : Buffer, password: string) : Promise<Buffer> {
return Promise.resolve().then(() => {
// must be bip39 mnemonic
if (!bip39.validateMnemonic(plaintextBuffer.toString())) {
throw new Error('Not a valid bip39 nmemonic')
}
// normalize plaintext to fixed length byte string
const plaintextNormalized = Buffer.from(
normalizeMnemonic(plaintextBuffer.toString()), 'hex')
// AES-128-CBC with SHA256 HMAC
const salt = crypto.randomBytes(16)
const keysAndIV = crypto.pbkdf2Sync(password, salt, 100000, 48, 'sha512')
const encKey = keysAndIV.slice(0, 16)
const macKey = keysAndIV.slice(16, 32)
const iv = keysAndIV.slice(32, 48)
const cipher = crypto.createCipheriv('aes-128-cbc', encKey, iv)
let cipherText = cipher.update(plaintextNormalized, undefined, 'hex')
cipherText += cipher.final('hex')
const hmacPayload = Buffer.concat([salt, Buffer.from(cipherText, 'hex')])
const hmac = crypto.createHmac('sha256', macKey)
hmac.write(hmacPayload)
const hmacDigest = hmac.digest()
const payload = Buffer.concat([salt, hmacDigest, Buffer.from(cipherText, 'hex')])
return payload
})
}
function decryptMnemonic(dataBuffer: Buffer, password: string) : Promise<string> {
return Promise.resolve().then(() => {
const salt = dataBuffer.slice(0, 16)
const hmacSig = dataBuffer.slice(16, 48) // 32 bytes
const cipherText = dataBuffer.slice(48)
const hmacPayload = Buffer.concat([salt, cipherText])
const keysAndIV = crypto.pbkdf2Sync(password, salt, 100000, 48, 'sha512')
const encKey = keysAndIV.slice(0, 16)
const macKey = keysAndIV.slice(16, 32)
const iv = keysAndIV.slice(32, 48)
const decipher = crypto.createDecipheriv('aes-128-cbc', encKey, iv)
let plaintext = decipher.update(cipherText.toString('hex'), 'hex').toString('hex')
plaintext += decipher.final().toString('hex')
const hmac = crypto.createHmac('sha256', macKey)
hmac.write(hmacPayload)
const hmacDigest = hmac.digest()
// hash both hmacSig and hmacDigest so string comparison time
// is uncorrelated to the ciphertext
const hmacSigHash = crypto.createHash('sha256')
.update(hmacSig)
.digest()
.toString('hex')
const hmacDigestHash = crypto.createHash('sha256')
.update(hmacDigest)
.digest()
.toString('hex')
if (hmacSigHash !== hmacDigestHash) {
// not authentic
throw new Error('Wrong password (HMAC mismatch)')
}
const mnemonic = denormalizeMnemonic(plaintext)
if (!bip39.validateMnemonic(mnemonic)) {
throw new Error('Wrong password (invalid plaintext)')
}
return mnemonic
})
}
export function encryptBackupPhrase(plaintextBuffer: Buffer, password: string) : Promise<Buffer> {
return encryptMnemonic(plaintextBuffer, password)
}
export function decryptBackupPhrase(dataBuffer: Buffer, password: string) : Promise<string> {
return decryptMnemonic(dataBuffer, password)
.catch((e) => // try the old way
new Promise((resolve, reject) => {
triplesec.decrypt(
{
key: new Buffer(password),
data: dataBuffer
},
(err, plaintextBuffer) => {
if (!err) {
resolve(plaintextBuffer)
} else {
reject(new Error(`current algorithm: "${e.message}", ` +
`legacy algorithm: "${err.message}"`))
}
}
)
})
)
}