ndwallet-core
Version:
Core cryptographic library for NDWallet browser environments
106 lines (105 loc) • 3.95 kB
JavaScript
/**
* Generate a random salt
* @returns Uint8Array containing the salt
*/
export function generateRandomSalt() {
return crypto.getRandomValues(new Uint8Array(32));
}
/**
* Derive a key from password and salt using PBKDF2
* @param password - The password
* @param salt - The salt to use for PBKDF2
* @returns Promise resolving to a CryptoKey
*/
export async function derivePbkdf2EncryptionKey(password, salt) {
// Convert to an ArrayBuffer
const encoder = new TextEncoder();
const passwordBuffer = encoder.encode(password);
const saltBuffer = encoder.encode(salt);
// Derive a key using PBKDF2
const baseKey = await window.crypto.subtle.importKey('raw', passwordBuffer, { name: 'PBKDF2' }, false, ['deriveBits', 'deriveKey']);
// Use PBKDF2 to derive a key
const derivedKey = await window.crypto.subtle.deriveKey({
name: 'PBKDF2',
salt: saltBuffer,
iterations: 100000,
hash: 'SHA-256'
}, baseKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
return derivedKey;
}
/**
* Derive a specific encryption key from the master key using HKDF
* @param masterKey - Master key derived from passkey
* @param context - Context string to derive different keys for different purposes
* @returns Promise resolving to a CryptoKey that can be used for encryption/decryption
*/
export async function deriveHkdfEncryptionKey(masterKey, context) {
try {
// Create a salt from the context
const encoder = new TextEncoder();
const contextBytes = encoder.encode(context);
const salt = await crypto.subtle.digest('SHA-256', contextBytes);
// Derive key using HKDF
return await crypto.subtle.deriveKey({
name: 'HKDF',
hash: 'SHA-256',
salt: salt,
info: new Uint8Array(0)
}, masterKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
}
catch (error) {
console.error('Error deriving encryption key:', error);
if (error instanceof Error) {
throw new Error(`Encryption key derivation failed: ${error.message}`);
}
else {
throw new Error(`Encryption key derivation failed: ${String(error)}`);
}
}
}
// Encrypt data using AES-GCM
export async function encryptData(data, key) {
try {
// Generate a random initialization vector
const iv = window.crypto.getRandomValues(new Uint8Array(12));
// Convert the data to an ArrayBuffer
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
// Encrypt the data
const encryptedBuffer = await window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv
}, key, dataBuffer);
// Convert the encrypted data to an array of numbers
const encryptedArray = Array.from(new Uint8Array(encryptedBuffer));
const ivArray = Array.from(iv);
return {
iv: ivArray,
data: encryptedArray
};
}
catch (error) {
console.error('Encryption error:', error);
throw new Error('Failed to encrypt data');
}
}
// Decrypt data using AES-GCM
export async function decryptData(encryptedData, key) {
try {
// Convert the encrypted data and IV back to Uint8Arrays
const encryptedBuffer = new Uint8Array(encryptedData.data);
const ivBuffer = new Uint8Array(encryptedData.iv);
// Decrypt the data
const decryptedBuffer = await window.crypto.subtle.decrypt({
name: 'AES-GCM',
iv: ivBuffer
}, key, encryptedBuffer);
// Convert the decrypted data back to a string
const decoder = new TextDecoder();
return decoder.decode(decryptedBuffer);
}
catch (error) {
console.error('Decryption error:', error);
throw new Error('Failed to decrypt data');
}
}