Pull out pbkdf2Sha512
This commit is contained in:
parent
5b5d64b0a5
commit
c2a0622475
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
122
packages/crypto/src/pbkdf2.spec.ts
Normal file
122
packages/crypto/src/pbkdf2.spec.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
52
packages/crypto/src/pbkdf2.ts
Normal file
52
packages/crypto/src/pbkdf2.ts
Normal 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));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user