Fix passwords not being random
This commit is contained in:
parent
46c49655a7
commit
9d9e599c09
7
dist/index.html
vendored
7
dist/index.html
vendored
@ -5007,12 +5007,12 @@ function isTrashGroup(groupId) {
|
|||||||
//#endregion
|
//#endregion
|
||||||
//#region src/lib/crypto/crypto.js
|
//#region src/lib/crypto/crypto.js
|
||||||
/**
|
/**
|
||||||
* Crypto module — Web Crypto API wrapper.
|
* Crypto module - Web Crypto API wrapper.
|
||||||
*
|
*
|
||||||
* Uses PBKDF2 for key derivation and AES-GCM for symmetric encryption.
|
* Uses PBKDF2 for key derivation and AES-GCM for symmetric encryption.
|
||||||
* All operations are async and use the browser's native Web Crypto API.
|
* All operations are async and use the browser's native Web Crypto API.
|
||||||
*
|
*
|
||||||
* The derived encryption key is kept in memory only — never written to disk.
|
* The derived encryption key is kept in memory only - never written to disk.
|
||||||
*/
|
*/
|
||||||
var PBKDF2_ITERATIONS = 6e5;
|
var PBKDF2_ITERATIONS = 6e5;
|
||||||
var SALT_LENGTH = 16;
|
var SALT_LENGTH = 16;
|
||||||
@ -5146,10 +5146,11 @@ function generatePassword({ length = 16, uppercase = true, lowercase = true, dig
|
|||||||
const excludeSet = new Set(exclude.split(""));
|
const excludeSet = new Set(exclude.split(""));
|
||||||
charset = charset.split("").filter((c) => !excludeSet.has(c)).join("");
|
charset = charset.split("").filter((c) => !excludeSet.has(c)).join("");
|
||||||
}
|
}
|
||||||
if (!charset) throw new Error("Password charset is empty — enable at least one character type");
|
if (!charset) throw new Error("Password charset is empty - enable at least one character type");
|
||||||
const charsetLength = charset.length;
|
const charsetLength = charset.length;
|
||||||
const maxValid = 256 - 256 % charsetLength;
|
const maxValid = 256 - 256 % charsetLength;
|
||||||
const randomBytes = new Uint8Array(length * 2);
|
const randomBytes = new Uint8Array(length * 2);
|
||||||
|
crypto.getRandomValues(randomBytes);
|
||||||
let password = "";
|
let password = "";
|
||||||
let byteIdx = 0;
|
let byteIdx = 0;
|
||||||
while (password.length < length) {
|
while (password.length < length) {
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Crypto module — Web Crypto API wrapper.
|
* Crypto module - Web Crypto API wrapper.
|
||||||
*
|
*
|
||||||
* Uses PBKDF2 for key derivation and AES-GCM for symmetric encryption.
|
* Uses PBKDF2 for key derivation and AES-GCM for symmetric encryption.
|
||||||
* All operations are async and use the browser's native Web Crypto API.
|
* All operations are async and use the browser's native Web Crypto API.
|
||||||
*
|
*
|
||||||
* The derived encryption key is kept in memory only — never written to disk.
|
* The derived encryption key is kept in memory only - never written to disk.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { generateId } from '../models/schema.js'
|
import { generateId } from '../models/schema.js'
|
||||||
@ -48,7 +48,7 @@ export async function deriveKey(masterPassword, salt) {
|
|||||||
},
|
},
|
||||||
keyMaterial,
|
keyMaterial,
|
||||||
{ name: 'AES-GCM', length: 256 },
|
{ name: 'AES-GCM', length: 256 },
|
||||||
false, // not extractable — stays in memory
|
false, // not extractable - stays in memory
|
||||||
['encrypt', 'decrypt']
|
['encrypt', 'decrypt']
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -186,12 +186,13 @@ export function generatePassword({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!charset) {
|
if (!charset) {
|
||||||
throw new Error('Password charset is empty — enable at least one character type')
|
throw new Error('Password charset is empty - enable at least one character type')
|
||||||
}
|
}
|
||||||
|
|
||||||
const charsetLength = charset.length
|
const charsetLength = charset.length
|
||||||
const maxValid = 256 - (256 % charsetLength)
|
const maxValid = 256 - (256 % charsetLength)
|
||||||
const randomBytes = new Uint8Array(length * 2)
|
const randomBytes = new Uint8Array(length * 2)
|
||||||
|
crypto.getRandomValues(randomBytes)
|
||||||
let password = ''
|
let password = ''
|
||||||
let byteIdx = 0
|
let byteIdx = 0
|
||||||
|
|
||||||
|
|||||||
43
tests/lib/crypto/crypto.test.js
Normal file
43
tests/lib/crypto/crypto.test.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { generatePassword } from '../../../src/lib/crypto/crypto.js'
|
||||||
|
|
||||||
|
describe('generatePassword', () => {
|
||||||
|
it('should produce different passwords on consecutive calls', () => {
|
||||||
|
const passwords = new Set()
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
passwords.add(generatePassword({ length: 16 }))
|
||||||
|
}
|
||||||
|
// With 50 calls and 94^16 possible values, all should be unique
|
||||||
|
expect(passwords.size).toBe(50)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respect the length option', () => {
|
||||||
|
for (let len of [4, 8, 16, 32, 64]) {
|
||||||
|
const pw = generatePassword({ length: len })
|
||||||
|
expect(pw.length).toBe(len)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should only use allowed character types', () => {
|
||||||
|
const pw = generatePassword({ uppercase: true, lowercase: false, digits: false, symbols: false })
|
||||||
|
expect(pw).toMatch(/^[A-Z]+$/)
|
||||||
|
|
||||||
|
const pw2 = generatePassword({ uppercase: false, lowercase: true, digits: false, symbols: false })
|
||||||
|
expect(pw2).toMatch(/^[a-z]+$/)
|
||||||
|
|
||||||
|
const pw3 = generatePassword({ uppercase: false, lowercase: false, digits: true, symbols: false })
|
||||||
|
expect(pw3).toMatch(/^[0-9]+$/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should exclude specified characters', () => {
|
||||||
|
const pw = generatePassword({ length: 32, exclude: '0OIl1' })
|
||||||
|
for (const ch of '0OIl1') {
|
||||||
|
expect(pw).not.toContain(ch)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw when charset is empty', () => {
|
||||||
|
expect(() => generatePassword({ uppercase: false, lowercase: false, digits: false, symbols: false }))
|
||||||
|
.toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user