码桶

发现社区成员的开源项目

_crypto.js1.9 KB
// Shared PBKDF2 helpers for password hashing
// Passwords arrive from the client as SHA-256 hex strings.
// We apply PBKDF2 with a random salt on top before storing.

const ITERATIONS = 100000;

export async function pbkdf2Derive(password, salt) {
    const keyMaterial = await crypto.subtle.importKey(
        'raw',
        new TextEncoder().encode(password),
        'PBKDF2',
        false,
        ['deriveBits']
    );
    const bits = await crypto.subtle.deriveBits(
        { name: 'PBKDF2', salt: new TextEncoder().encode(salt), iterations: ITERATIONS, hash: 'SHA-256' },
        keyMaterial,
        256
    );
    return Array.from(new Uint8Array(bits)).map(b => b.toString(16).padStart(2, '0')).join('');
}

// Hash a password (already SHA-256 from client) with a new random salt
export async function hashPassword(clientHash) {
    const salt = crypto.randomUUID();
    const derived = await pbkdf2Derive(clientHash, salt);
    return `${salt}:${derived}`;
}

// Verify a client-sent SHA-256 hash against a stored PBKDF2 hash (salt:hash)
export async function verifyPassword(clientHash, storedHash) {
    if (!storedHash || !storedHash.includes(':')) return false;
    const colonIdx = storedHash.indexOf(':');
    const salt = storedHash.substring(0, colonIdx);
    const hash = storedHash.substring(colonIdx + 1);
    const derived = await pbkdf2Derive(clientHash, salt);
    return derived === hash;
}

// Simple SHA-256 hash (used for hashing env passwords, not for stored credentials)
export async function sha256(str) {
    const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
    return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
}

// Check if stored hash is old legacy format (plain SHA-256, 64 hex chars, no colon)
export function isLegacyHash(storedHash) {
    return storedHash && storedHash.length === 64 && !storedHash.includes(':');
}