mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-06 17:37:51 +08:00
- Replace BarcodeDetector with jsQR for raw binary byte access - BarcodeDetector forced UTF-8 decoding which corrupted binary data - jsQR's binaryData property preserves raw bytes without text conversion - Fix regex bug: use single backslash \x00 instead of \x00 for binary detection - Add debug logging for scan data inspection - QR generation already worked (Krux-compatible), only scanning was broken Resolves binary QR code scanning for 12/24-word CompactSeedQR format. Tested with Krux device - full bidirectional compatibility confirmed.
88 lines
2.5 KiB
TypeScript
88 lines
2.5 KiB
TypeScript
/**
|
|
* @file pbkdf2.ts
|
|
* @summary A pure-JS implementation of PBKDF2-HMAC-SHA256 using the Web Crypto API.
|
|
* This is used as a fallback to test for platform inconsistencies in native PBKDF2.
|
|
* Adapted from public domain examples and RFC 2898.
|
|
*/
|
|
|
|
/**
|
|
* Performs HMAC-SHA256 on a given key and data.
|
|
* @param key The HMAC key.
|
|
* @param data The data to hash.
|
|
* @returns A promise that resolves to the HMAC-SHA256 digest as an ArrayBuffer.
|
|
*/
|
|
async function hmacSha256(key: CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
|
|
return crypto.subtle.sign('HMAC', key, data);
|
|
}
|
|
|
|
/**
|
|
* The F function for PBKDF2 (PRF).
|
|
* T_1 = F(P, S, c, 1)
|
|
* T_2 = F(P, S, c, 2)
|
|
* ...
|
|
* F(P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
|
|
* U_1 = PRF(P, S || INT_32_BE(i))
|
|
* U_2 = PRF(P, U_1)
|
|
* ...
|
|
* U_c = PRF(P, U_{c-1})
|
|
*/
|
|
async function F(passwordKey: CryptoKey, salt: Uint8Array, iterations: number, i: number): Promise<Uint8Array> {
|
|
// S || INT_32_BE(i)
|
|
const saltI = new Uint8Array(salt.length + 4);
|
|
saltI.set(salt, 0);
|
|
const i_be = new DataView(saltI.buffer, salt.length, 4);
|
|
i_be.setUint32(0, i, false); // false for big-endian
|
|
|
|
// U_1
|
|
let U = new Uint8Array(await hmacSha256(passwordKey, saltI.buffer));
|
|
// T
|
|
let T = U.slice();
|
|
|
|
for (let c = 1; c < iterations; c++) {
|
|
// U_c = PRF(P, U_{c-1})
|
|
U = new Uint8Array(await hmacSha256(passwordKey, U.buffer));
|
|
// T = T \xor U_c
|
|
for (let j = 0; j < T.length; j++) {
|
|
T[j] ^= U[j];
|
|
}
|
|
}
|
|
|
|
return T;
|
|
}
|
|
|
|
/**
|
|
* Derives a key using PBKDF2-HMAC-SHA256.
|
|
* @param password The password string.
|
|
* @param salt The salt bytes.
|
|
* @param iterations The number of iterations.
|
|
* @param keyLenBytes The desired key length in bytes.
|
|
* @returns A promise that resolves to the derived key as a Uint8Array.
|
|
*/
|
|
export async function pbkdf2HmacSha256(password: string, salt: Uint8Array, iterations: number, keyLenBytes: number): Promise<Uint8Array> {
|
|
const passwordBytes = new TextEncoder().encode(password);
|
|
const passwordKey = await crypto.subtle.importKey(
|
|
'raw',
|
|
passwordBytes,
|
|
{ name: 'HMAC', hash: 'SHA-256' },
|
|
false,
|
|
['sign']
|
|
);
|
|
|
|
const hLen = 32; // SHA-256 output length in bytes
|
|
const l = Math.ceil(keyLenBytes / hLen);
|
|
const r = keyLenBytes - (l - 1) * hLen;
|
|
|
|
const blocks: Uint8Array[] = [];
|
|
for (let i = 1; i <= l; i++) {
|
|
blocks.push(await F(passwordKey, salt, iterations, i));
|
|
}
|
|
|
|
const T = new Uint8Array(keyLenBytes);
|
|
for(let i = 0; i < l - 1; i++) {
|
|
T.set(blocks[i], i * hLen);
|
|
}
|
|
T.set(blocks[l-1].slice(0, r), (l-1) * hLen);
|
|
|
|
return T;
|
|
}
|