// Produces a pm-encrypted-export-v1 container with WebCrypto — the exact // primitive the browser uses — so the Delphi/Android implementation can be // checked against a known answer instead of "it seems to work on my file". // // node make-testvector.js > testvector.json // // The password and the payload are fixed below; keep them in sync with // testvector.md. Salt and IV are fixed too, so the output is reproducible and // a diff means a real behavioural change. const { webcrypto } = require('node:crypto'); const { subtle } = webcrypto; const PASSWORD = 'sync-test-password-éàü'; // deliberately NON-ASCII: that is // where char[] vs UTF-8 diverges const PAYLOAD = { version: 1, exported_at: '2026-08-14T12:00:00.000Z', username: 'testvector', folders: [{ name: 'Work', color: '#3b82f6', icon: '' }], entries: [ { uuid: '11111111-2222-3333-4444-555555555555', site: 'example.com', title: 'Example', username: 'alice', password: 'correct horse battery staple', folder: 'Work', tags: ['demo'], kind: 'login', deleted: 0, }, { uuid: '66666666-7777-8888-9999-000000000000', site: 'trashed.example', title: 'Trashed', username: 'bob', password: 'in the bin', folder: 'All', tags: [], kind: 'login', deleted: 1, deleted_at: '2026-08-01 09:30:00', }, ], }; // Fixed, so the file is byte-stable across runs. NEVER do this in the app: // a reused salt/IV pair with the same key destroys GCM's security. const salt = Buffer.alloc(32); for (let i = 0; i < 32; i++) salt[i] = i; const iv = Buffer.alloc(12); for (let i = 0; i < 12; i++) iv[i] = 200 + i; (async () => { const base = await subtle.importKey('raw', new TextEncoder().encode(PASSWORD), 'PBKDF2', false, ['deriveKey']); const key = await subtle.deriveKey( { name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' }, base, { name: 'AES-GCM', length: 256 }, false, ['encrypt']); const ct = await subtle.encrypt({ name: 'AES-GCM', iv }, key, new TextEncoder().encode(JSON.stringify(PAYLOAD))); console.log(JSON.stringify({ format: 'pm-encrypted-export-v1', kdf: 'pbkdf2-sha256', kdf_iterations: 600000, kdf_salt: salt.toString('base64'), iv: iv.toString('base64'), ciphertext: Buffer.from(ct).toString('base64'), created_at: '2026-08-14T12:00:00.000Z', }, null, 2)); })();