Skip to content

Commit 0a6e324

Browse files
authored
Update: Provide PBKDF2-based algorithms publically (#139)
1 parent 20fe41e commit 0a6e324

File tree

2 files changed

+194
-11
lines changed

2 files changed

+194
-11
lines changed

lib/algorithms/pbes2.js

+136-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,120 @@ function fixSalt(hmac, kw, salt) {
2323
return Buffer.concat(output);
2424
}
2525

26+
function pbkdf2Fn(hash) {
27+
function prepareProps(props) {
28+
props = props || {};
29+
var keyLen = props.length || 0;
30+
var salt = util.asBuffer(props.salt || new Buffer(0), "base64u4l"),
31+
itrs = props.iterations || 0;
32+
33+
if (0 >= keyLen) {
34+
throw new Error("invalid key length");
35+
}
36+
if (0 >= itrs) {
37+
throw new Error("invalid iteration count");
38+
}
39+
40+
props.length = keyLen;
41+
props.salt = salt;
42+
props.iterations = itrs;
43+
44+
return props;
45+
}
46+
47+
var fallback = function(key, props) {
48+
try {
49+
props = prepareProps(props);
50+
} catch (err) {
51+
return Promise.reject(err);
52+
}
53+
54+
var keyLen = props.length,
55+
salt = props.salt,
56+
itrs = props.iterations;
57+
58+
var promise = new Promise(function(resolve, reject) {
59+
var md = forge.md[hash.replace("-", "").toLowerCase()].create();
60+
var cb = function(err, dk) {
61+
if (err) {
62+
reject(err);
63+
} else {
64+
dk = new Buffer(dk, "binary");
65+
resolve(dk);
66+
}
67+
};
68+
69+
forge.pkcs5.pbkdf2(key.toString("binary"),
70+
salt.toString("binary"),
71+
itrs,
72+
keyLen,
73+
md,
74+
cb);
75+
});
76+
return promise;
77+
};
78+
var webcrypto = function(key, props) {
79+
try {
80+
props = prepareProps(props);
81+
} catch (err) {
82+
return Promise.reject(err);
83+
}
84+
85+
var keyLen = props.length,
86+
salt = props.salt,
87+
itrs = props.iterations;
88+
89+
var promise = Promise.resolve(key);
90+
promise = promise.then(function(keyval) {
91+
return helpers.subtleCrypto.importKey("raw", keyval, "PBKDF2", false, ["deriveBits"]);
92+
});
93+
promise = promise.then(function(key) {
94+
var mainAlgo = {
95+
name: "PBKDF2",
96+
salt: salt,
97+
iterations: itrs,
98+
hash: hash
99+
};
100+
101+
return helpers.subtleCrypto.deriveBits(mainAlgo, key, keyLen * 8);
102+
});
103+
promise = promise.then(function(result) {
104+
return util.asBuffer(result);
105+
});
106+
return promise;
107+
};
108+
var nodejs = function(key, props) {
109+
if (6 > helpers.nodeCrypto.pbkdf2.length) {
110+
throw new Error("unsupported algorithm: PBES2-" + hmac + "+" + kw);
111+
}
112+
113+
try {
114+
props = prepareProps(props);
115+
} catch (err) {
116+
return Promise.reject(err);
117+
}
118+
119+
var keyLen = props.length,
120+
salt = props.salt,
121+
itrs = props.iterations;
122+
123+
var md = hash.replace("-", "");
124+
var promise = new Promise(function(resolve, reject) {
125+
function cb(err, dk) {
126+
if (err) {
127+
reject(err);
128+
} else {
129+
resolve(dk);
130+
}
131+
}
132+
helpers.nodeCrypto.pbkdf2(key, salt, itrs, keyLen, md, cb);
133+
});
134+
return promise;
135+
};
136+
137+
return helpers.setupFallback(nodejs, webcrypto, fallback);
138+
}
139+
26140
function pbes2EncryptFN(hmac, kw) {
27141
var keyLen = CONSTANTS.KEYLENGTH[kw] / 8;
28142

@@ -33,11 +147,10 @@ function pbes2EncryptFN(hmac, kw) {
33147
itrs = props.p2c || 0;
34148

35149
if (0 >= itrs) {
36-
return Promise.reject(new Error("invalid iteration count"));
150+
throw new Error("invalid iteration count");
37151
}
38-
39152
if (8 > salt.length) {
40-
return Promise.reject(new Error("salt too small"));
153+
throw new Error("salt too small");
41154
}
42155
salt = fixSalt(hmac, kw, salt);
43156

@@ -71,14 +184,16 @@ function pbes2EncryptFN(hmac, kw) {
71184
};
72185

73186
var webcrypto = function(key, pdata, props) {
187+
props = props || {};
188+
74189
var salt = util.asBuffer(props.p2s || new Buffer(0), "base64url"),
75190
itrs = props.p2c || 0;
76191

77192
if (0 >= itrs) {
78-
return Promise.reject(new Error("invalid iteration count"));
193+
throw new Error("invalid iteration count");
79194
}
80195
if (8 > salt.length) {
81-
return Promise.reject(new Error("salt too small"));
196+
throw new Error("salt too small");
82197
}
83198
salt = fixSalt(hmac, kw, salt);
84199

@@ -139,11 +254,10 @@ function pbes2EncryptFN(hmac, kw) {
139254
itrs = props.p2c || 0;
140255

141256
if (0 >= itrs) {
142-
return Promise.reject(new Error("invalid iteration count"));
257+
throw new Error("invalid iteration count");
143258
}
144-
145259
if (8 > salt.length) {
146-
return Promise.reject(new Error("salt too small"));
260+
throw new Error("salt too small");
147261
}
148262
salt = fixSalt(hmac, kw, salt);
149263

@@ -316,9 +430,22 @@ function pbes2DecryptFN(hmac, kw) {
316430
}
317431

318432
// ### Public API
433+
var pbes2 = {};
434+
435+
// * [name].derive
436+
[
437+
"PBKDF2-SHA-256",
438+
"PBKDF2-SHA-384",
439+
"PBKDF2-SHA-512"
440+
].forEach(function(alg) {
441+
var hash = alg.replace("PBKDF2-", "");
442+
pbes2[alg] = {
443+
derive: pbkdf2Fn(hash)
444+
};
445+
});
446+
319447
// [name].encrypt
320448
// [name].decrypt
321-
var pbes2 = {};
322449
[
323450
"PBES2-HS256+A128KW",
324451
"PBES2-HS384+A192KW",

test/algorithms/pbes2-test.js

+58-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,63 @@ var algorithms = require("../../lib/algorithms/"),
1111
util = require("../../lib/util");
1212

1313
describe("algorithms/pbes2", function() {
14-
var vectors = [
14+
var deriveVectors = [
15+
{
16+
alg: "PBKDF2-SHA-256",
17+
desc: "Password-based Key Derivation using HMAC-SHA-256 {password=password, salt=salt, iterations=1}",
18+
password: new Buffer("password"),
19+
salt: new Buffer("salt"),
20+
iterations: 1,
21+
length: 32,
22+
derived: "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"
23+
},
24+
{
25+
alg: "PBKDF2-SHA-256",
26+
desc: "Password-based Key Derivation using HMAC-SHA-256 {password=password, salt=salt, iterations=2}",
27+
password: new Buffer("password"),
28+
salt: new Buffer("salt"),
29+
iterations: 2,
30+
length: 32,
31+
derived: "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"
32+
},
33+
{
34+
alg: "PBKDF2-SHA-256",
35+
desc: "Password-based Key Derivation using HMAC-SHA-256 {password=password, salt=salt, iterations=4096}",
36+
password: new Buffer("password"),
37+
salt: new Buffer("salt"),
38+
iterations: 4096,
39+
length: 32,
40+
derived: "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a"
41+
},
42+
{
43+
alg: "PBKDF2-SHA-256",
44+
desc: "Password-based Key Derivation using HMAC-SHA-256 {password=passwordPASSWORDpassword, salt=saltSALTsaltSALTsaltSALTsaltSALTsalt, iterations=4096}",
45+
password: new Buffer("passwordPASSWORDpassword"),
46+
salt: new Buffer("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
47+
iterations: 4096,
48+
length: 40,
49+
derived: "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9"
50+
}
51+
];
52+
deriveVectors.forEach(function(v) {
53+
var key = v.password,
54+
props = {
55+
salt: v.salt,
56+
iterations: v.iterations,
57+
length: v.length
58+
},
59+
derived = v.derived;
60+
61+
it("performs " + v.alg + " (" + v.desc + ")", function() {
62+
var promise = algorithms.derive(v.alg, key, props);
63+
promise = promise.then(function(result) {
64+
assert.equal(result.toString("hex"), derived);
65+
});
66+
return promise;
67+
});
68+
});
69+
70+
var encVectors = [
1571
{
1672
alg: "PBES2-HS256+A128KW",
1773
desc: "Password-Based Encryption using HMAC-SHA-256 and AES-128-KW",
@@ -43,7 +99,7 @@ describe("algorithms/pbes2", function() {
4399
}
44100
];
45101

46-
vectors.forEach(function(v) {
102+
encVectors.forEach(function(v) {
47103
var key = v.password,
48104
props = {
49105
p2s: v.salt,

0 commit comments

Comments
 (0)