mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 09:57:50 +08:00
feat: fix CompactSeedQR binary QR code scanning with jsQR library
- 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.
This commit is contained in:
87
src/lib/pbkdf2.ts
Normal file
87
src/lib/pbkdf2.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
Reference in New Issue
Block a user