Pull out pbkdf2Sha512

This commit is contained in:
Simon Warta 2022-02-27 13:49:12 +01:00
parent 5b5d64b0a5
commit c2a0622475
3 changed files with 176 additions and 52 deletions

View File

@ -1,5 +1,6 @@
import { toUtf8 } from "@cosmjs/encoding";
import { pbkdf2Sha512 } from "./pbkdf2";
import { sha256 } from "./sha";
const wordlist = [
@ -2177,18 +2178,6 @@ export class EnglishMnemonic {
}
}
async function getSubtle(): Promise<any | undefined> {
const g: any = globalThis;
let subtle = g.crypto && g.crypto.subtle;
if (!subtle) {
const crypto: any = await import("crypto");
if (crypto.webcrypto && crypto.webcrypto.subtle) {
subtle = crypto.webcrypto.subtle;
}
}
return subtle;
}
export class Bip39 {
/**
* Encodes raw entropy of length 16, 20, 24, 28 or 32 bytes as an English mnemonic between 12 and 24 words.
@ -2217,45 +2206,6 @@ export class Bip39 {
const mnemonicBytes = toUtf8(normalize(mnemonic.toString()));
const salt = "mnemonic" + (password ? normalize(password) : "");
const saltBytes = toUtf8(salt);
return this.pbkdf2Sha512(mnemonicBytes, saltBytes, 2048, 64);
}
// convert pbkdf2's callback interface to Promise interface
private static async pbkdf2Sha512(
secret: Uint8Array,
salt: Uint8Array,
iterations: number,
keylen: number,
): Promise<Uint8Array> {
const subtle = await getSubtle();
if (subtle) {
return subtle
.importKey("raw", secret, { name: "PBKDF2" }, false, ["deriveBits"])
.then((key: Uint8Array) =>
subtle
.deriveBits(
{
name: "PBKDF2",
salt: salt,
iterations: iterations,
hash: { name: "SHA-512" },
},
key,
keylen * 8,
)
.then((buffer: ArrayBuffer) => new Uint8Array(buffer)),
);
} else {
const module = await import("crypto");
return new Promise((resolve, reject) => {
module.pbkdf2(secret, salt, iterations, keylen, "sha512", (error, result) => {
if (error) {
reject(error);
} else {
resolve(Uint8Array.from(result));
}
});
});
}
return pbkdf2Sha512(mnemonicBytes, saltBytes, 2048, 64);
}
}

View File

@ -0,0 +1,122 @@
import { fromHex, toAscii, toUtf8 } from "@cosmjs/encoding";
import { pbkdf2Sha512 } from "./pbkdf2";
interface TestVector {
secret: Uint8Array;
salt: Uint8Array;
iterations: number;
keylen: number;
expected: Uint8Array;
}
describe("pbkdf2", () => {
// https://github.com/randombit/botan/blob/master/src/tests/data/pbkdf/pbkdf2.vec#L70-L74
const botanTest: TestVector = {
secret: toAscii("xyz"),
salt: fromHex("0001020304050607"),
iterations: 10000,
keylen: 48,
expected: fromHex(
"DAF8A734327745EB63D19054DBD4018A682CEF11086A1BFB63FDBC16158C2F8B0742802F36AEF1B1DF92ACCBEA5D31A5",
),
};
// https://github.com/brycx/Test-Vector-Generation/blob/f88d152db/PBKDF2/pbkdf2-hmac-sha2-test-vectors.md
const brycxTests: TestVector[] = [
// Test Case 1
{
secret: toUtf8("password"),
salt: toUtf8("salt"),
iterations: 1,
keylen: 20,
expected: fromHex("867f70cf1ade02cff3752599a3a53dc4af34c7a6"),
},
// Test Case 2
{
secret: toUtf8("password"),
salt: toUtf8("salt"),
iterations: 2,
keylen: 20,
expected: fromHex("e1d9c16aa681708a45f5c7c4e215ceb66e011a2e"),
},
// Test Case 3
{
secret: toUtf8("password"),
salt: toUtf8("salt"),
iterations: 4096,
keylen: 20,
expected: fromHex("d197b1b33db0143e018b12f3d1d1479e6cdebdcc"),
},
// Test Case 4
{
secret: toUtf8("password"),
salt: toUtf8("salt"),
iterations: 16777216,
keylen: 20,
expected: fromHex("6180a3ceabab45cc3964112c811e0131bca93a35"),
},
// Test Case 5
{
secret: toUtf8("passwordPASSWORDpassword"),
salt: toUtf8("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
iterations: 4096,
keylen: 25,
expected: fromHex("8c0511f4c6e597c6ac6315d8f0362e225f3c501495ba23b868"),
},
// Test Case 6
{
secret: toUtf8("pass\0word"),
salt: toUtf8("sa\0lt"),
iterations: 4096,
keylen: 16,
expected: fromHex("9d9e9c4cd21fe4be24d5b8244c759665"),
},
// Test Case 7
{
secret: toUtf8("passwd"),
salt: toUtf8("salt"),
iterations: 1,
keylen: 128,
expected: fromHex(
"c74319d99499fc3e9013acff597c23c5baf0a0bec5634c46b8352b793e324723d55caa76b2b25c43402dcfdc06cdcf66f95b7d0429420b39520006749c51a04ef3eb99e576617395a178ba33214793e48045132928a9e9bf2661769fdc668f31798597aaf6da70dd996a81019726084d70f152baed8aafe2227c07636c6ddece",
),
},
// Test Case 8
{
secret: toUtf8("Password"),
salt: toUtf8("NaCl"),
iterations: 80000,
keylen: 128,
expected: fromHex(
"e6337d6fbeb645c794d4a9b5b75b7b30dac9ac50376a91df1f4460f6060d5addb2c1fd1f84409abacc67de7eb4056e6bb06c2d82c3ef4ccd1bded0f675ed97c65c33d39f81248454327aa6d03fd049fc5cbb2b5e6dac08e8ace996cdc960b1bd4530b7e754773d75f67a733fdb99baf6470e42ffcb753c15c352d4800fb6f9d6",
),
},
// Test Case 9
{
secret: toUtf8("Password"),
salt: toUtf8("sa\0lt"),
iterations: 4096,
keylen: 256,
expected: fromHex(
"10176fb32cb98cd7bb31e2bb5c8f6e425c103333a2e496058e3fd2bd88f657485c89ef92daa0668316bc23ebd1ef88f6dd14157b2320b5d54b5f26377c5dc279b1dcdec044bd6f91b166917c80e1e99ef861b1d2c7bce1b961178125fb86867f6db489a2eae0022e7bc9cf421f044319fac765d70cb89b45c214590e2ffb2c2b565ab3b9d07571fde0027b1dc57f8fd25afa842c1056dd459af4074d7510a0c020b914a5e202445d4d3f151070589dd6a2554fc506018c4f001df6239643dc86771286ae4910769d8385531bba57544d63c3640b90c98f1445ebdd129475e02086b600f0beb5b05cc6ca9b3633b452b7dad634e9336f56ec4c3ac0b4fe54ced8",
),
},
];
describe("pbkdf2Sha512", () => {
it("works", async () => {
{
const { secret, salt, iterations, keylen, expected } = botanTest;
const hash = await pbkdf2Sha512(secret, salt, iterations, keylen);
expect(hash).toEqual(expected);
}
for (const [index, test] of brycxTests.entries()) {
const { secret, salt, iterations, keylen, expected } = test;
const hash = await pbkdf2Sha512(secret, salt, iterations, keylen);
expect(hash).withContext(`Index ${index}`).toEqual(expected);
}
});
});
});

View File

@ -0,0 +1,52 @@
async function getSubtle(): Promise<any | undefined> {
const g: any = globalThis;
let subtle = g.crypto && g.crypto.subtle;
if (!subtle) {
const crypto: any = await import("crypto");
if (crypto.webcrypto && crypto.webcrypto.subtle) {
subtle = crypto.webcrypto.subtle;
}
}
return subtle;
}
/**
* A pbkdf2 implementation for BIP39. This is not exported at package level and thus a private API.
*/
export async function pbkdf2Sha512(
secret: Uint8Array,
salt: Uint8Array,
iterations: number,
keylen: number,
): Promise<Uint8Array> {
const subtle = await getSubtle();
if (subtle) {
return subtle
.importKey("raw", secret, { name: "PBKDF2" }, false, ["deriveBits"])
.then((key: Uint8Array) =>
subtle
.deriveBits(
{
name: "PBKDF2",
salt: salt,
iterations: iterations,
hash: { name: "SHA-512" },
},
key,
keylen * 8,
)
.then((buffer: ArrayBuffer) => new Uint8Array(buffer)),
);
} else {
const module = await import("crypto");
return new Promise((resolve, reject) => {
module.pbkdf2(secret, salt, iterations, keylen, "sha512", (error, result) => {
if (error) {
reject(error);
} else {
resolve(Uint8Array.from(result));
}
});
});
}
}