Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export const AES_KEY_BYTE_LENGTH = 32;
export const IV_LEN_BYTES = 12;

export const CONTEXT_WRAPPING = 'CRYPTO library 2025-08-22 18:10:00 key derived from ecc and kyber secrets';
export const CONTEXT_ENC_KEYSTORE = 'CRYPTO library 2025-07-30 16:18:03 key for opening encryption keys keystore';
export const CONTEXT_RECOVERY = 'CRYPTO library 2025-07-30 16:20:00 key for account recovery';
export const CONTEXT_INDEX = 'CRYPTO library 2025-07-30 17:20:00 key for protecting current search indices';
Expand Down
2 changes: 1 addition & 1 deletion src/derive-key/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ARGON2ID_SALT_BYTE_LENGTH,
ARGON2ID_OUTPUT_BYTE_LENGTH,
} from '../constants';
import { randomBytes } from '@noble/post-quantum/utils.js';
import { randomBytes } from '@noble/hashes/utils.js';

/**
* Calculates hash using the argon2id password-hashing function
Expand Down
18 changes: 1 addition & 17 deletions src/derive-key/deriveKeysFromKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,12 @@ export function deriveSymmetricKeyFromContext(context: string, baseKey: Uint8Arr
* @returns The derived secret key
*/
export function deriveSymmetricKeyFromTwoKeys(key1: Uint8Array, key2: Uint8Array): Uint8Array {
return deriveSymmetricKeyFromTwoKeysAndContext(key1, key2, CONTEXT_DERIVE);
}

/**
* Derives a symmetric key from two keys and context
*
* @param key1 - The 32-bytes key
* @param key2 - The 32-bytes key
* @param context - The context string
* @returns The derived symmetric key
*/
export function deriveSymmetricKeyFromTwoKeysAndContext(
key1: Uint8Array,
key2: Uint8Array,
context: string,
): Uint8Array {
try {
if (key2.length != AES_KEY_BYTE_LENGTH || key1.length != AES_KEY_BYTE_LENGTH) {
throw new Error(`Input key length must be exactly ${AES_KEY_BYTE_LENGTH} bytes`);
}
const key = blake3(key1, { key: key2 });
return blake3(key, { context: UTF8ToUint8(context) });
return deriveSymmetricKeyFromContext(CONTEXT_DERIVE, key);
} catch (error) {
throw new Error('Failed to derive symmetric key from two keys and context', { cause: error });
}
Expand Down
46 changes: 0 additions & 46 deletions src/derive-key/deriveKeysFromPassword.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { hexToUint8Array, uint8ArrayToHex } from '../utils';
import { argon2, sampleSalt } from './core';

/**
Expand Down Expand Up @@ -31,48 +30,3 @@ export async function getKeyFromPasswordAndSalt(password: string, salt: Uint8Arr
throw new Error('Failed to derive key from password and salt', { cause: error });
}
}

/**
* Derives a HEX symmetric key from a user's password with a randomly sampled salt
*
* @param password - The user's password
* @returns The derived HEX secret key and randomly sampled HEX salt
*/
export async function getKeyFromPasswordHex(password: string): Promise<{ keyHex: string; saltHex: string }> {
try {
const { key, salt } = await getKeyFromPassword(password);
return { keyHex: uint8ArrayToHex(key), saltHex: uint8ArrayToHex(salt) };
} catch (error) {
throw new Error('Failed to derive key from password', { cause: error });
}
}

/**
* Derives a HEX symmetric key from a user's password and salt
*
* @param password - The user's password
* @param saltHex - The given HEX salt
* @returns The derived HEX secret key
*/
export async function getKeyFromPasswordAndSaltHex(password: string, saltHex: string): Promise<string> {
try {
const salt = hexToUint8Array(saltHex);
const key = await getKeyFromPasswordAndSalt(password, salt);
return uint8ArrayToHex(key);
} catch (error) {
throw new Error('Failed to derive key from password and salt', { cause: error });
}
}

/**
* Verifies the derived key
*
* @param password - The user's password
* @param saltHex - The given HEX salt
* @param keyHex - The derived HEX key
* @returns The result of the key verification
*/
export async function verifyKeyFromPasswordHex(password: string, saltHex: string, keyHex: string): Promise<boolean> {
const result = await getKeyFromPasswordAndSaltHex(password, saltHex);
return keyHex === result;
}
71 changes: 26 additions & 45 deletions src/hash/blake3.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { blake3 } from '@noble/hashes/blake3.js';
import { bytesToHex } from '@noble/hashes/utils.js';

/**
* Hashes the given array of data
*
* @param data - The data to hash
* @returns The resulting hash array
* @param data - The array of data
* @returns The resulting hash
*/
export function hashDataArray(data: Uint8Array[]): Uint8Array {
try {
Expand All @@ -20,24 +19,12 @@ export function hashDataArray(data: Uint8Array[]): Uint8Array {
}

/**
* Hashes the given array of data
* Hashes the given array of data with the given key
*
* @param data - The data to hash
* @returns The resulting hash hex string
* @param hashKey - The key for keyed hashing
* @param data - The array of data
* @returns The resulting keyed hash
*/
export function hashDataArrayHex(data: Uint8Array[]): string {
return bytesToHex(hashDataArray(data));
}

export function hashDataArrayWithKeyHex(hashKey: Uint8Array, data: Uint8Array[]): string {
try {
const hash = hashDataArrayWithKey(hashKey, data);
return bytesToHex(hash);
} catch (error) {
throw new Error('Failed to compute hash hex', { cause: error });
}
}

export function hashDataArrayWithKey(hashKey: Uint8Array, data: Uint8Array[]): Uint8Array {
try {
const hasher = blake3.create({ key: hashKey });
Expand All @@ -51,13 +38,13 @@ export function hashDataArrayWithKey(hashKey: Uint8Array, data: Uint8Array[]): U
}

/**
* Hashes the given array of data using blake3 algorithm
* Hashes the given array of data to the desired byte-length
*
* @param data - The array of data
* @param bytes - The desired output byte-length
* @param data - The data to hash
* @returns The resulting hash value
* @returns The resulting hash of the desired byte-length
*/
export function getBytesFromDataArray(bytes: number, data: Uint8Array[]): Uint8Array {
export function getBytesFromDataArray(data: Uint8Array[], bytes: number): Uint8Array {
try {
const hasher = blake3.create({ dkLen: bytes });
for (const chunk of data) {
Expand All @@ -70,39 +57,33 @@ export function getBytesFromDataArray(bytes: number, data: Uint8Array[]): Uint8A
}

/**
* Hashes the given array of data using blake3 algorithm
* Hashes the given data
*
* @param bytes - The desired output byte-length
* @param data - The data to hash
* @returns The resulting hash value
* @param data - The data
* @returns The resulting hash
*/
export function getBytesFromDataArrayHex(bytes: number, data: Uint8Array[]): string {
try {
const hash = getBytesFromDataArray(bytes, data);
return bytesToHex(hash);
} catch (error) {
throw new Error('Failed to get bytes from data', { cause: error });
}
export function hashData(data: Uint8Array): Uint8Array {
return blake3(data);
}

/**
* Hashes the given string using blake3 algorithm
* Hashes the given data with the given key
*
* @param bytes - The desired output byte-length
* @param data - The data to hash
* @returns The resulting hash value
* @param hashKey - The key for keyed hashing
* @param data - The data
* @returns The resulting keyed hash
*/
export function getBytesFromData(bytes: number, data: Uint8Array): Uint8Array {
return blake3(data, { dkLen: bytes });
export function hashDataWithKey(hashKey: Uint8Array, data: Uint8Array): Uint8Array {
return blake3(data, { key: hashKey });
}

/**
* Hashes the given data using blake3 algorithm
* Hashes the given data to the desired byte-length
*
* @param data - The data
* @param bytes - The desired output byte-length
* @param data - The data to hash
* @returns The resulting hash value
* @returns The resulting hash of the desired byte-length
*/
export function getBytesFromDataHex(bytes: number, data: Uint8Array): string {
return bytesToHex(getBytesFromData(bytes, data));
export function getBytesFromData(data: Uint8Array, bytes: number): Uint8Array {
return blake3(data, { dkLen: bytes });
}
1 change: 0 additions & 1 deletion src/hash/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './blake3';
export * from './mac';
20 changes: 0 additions & 20 deletions src/hash/mac.ts

This file was deleted.

12 changes: 3 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
export { deriveSecretKey, generateEccKeys } from './asymmetric-crypto';
export {
deriveSymmetricKeyFromTwoKeys,
deriveSymmetricKeyFromTwoKeysAndContext,
deriveSymmetricKeyFromContext,
getKeyFromPassword,
getKeyFromPasswordAndSalt,
getKeyFromPasswordHex,
getKeyFromPasswordAndSaltHex,
verifyKeyFromPasswordHex,
} from './derive-key';
export {
encryptEmailHybrid,
Expand Down Expand Up @@ -45,12 +41,10 @@ export {
export {
hashDataArray,
hashDataArrayWithKey,
hashDataArrayHex,
hashDataArrayWithKeyHex,
getBytesFromDataArray,
hashData,
hashDataWithKey,
getBytesFromData,
getBytesFromDataHex,
getBytesFromDataArrayHex,
computeMac,
} from './hash';
export { unwrapKey, wrapKey } from './key-wrapper';
export { createEncryptionAndRecoveryKeystores, openEncryptionKeystore, openRecoveryKeystore } from './keystore-crypto';
Expand Down
20 changes: 0 additions & 20 deletions src/key-wrapper/aesWrapper.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,5 @@
import { CONTEXT_WRAPPING } from '../constants';
import { deriveSymmetricKeyFromTwoKeysAndContext } from '../derive-key';
import { aeskw } from '@noble/ciphers/aes.js';

/**
* Derives wrapping key from two secrets
*
* @param eccSecret - The secret exchanged via elliptic curves
* @param kyberSecret - The secret exchanged via Kyber KEM
* @returns The resulting wrapping key
*/
export async function deriveWrappingKey(eccSecret: Uint8Array, kyberSecret: Uint8Array): Promise<Uint8Array> {
try {
if (eccSecret.length !== kyberSecret.length) {
throw new Error('secrets must have equal length');
}
return deriveSymmetricKeyFromTwoKeysAndContext(eccSecret, kyberSecret, CONTEXT_WRAPPING);
} catch (error) {
throw new Error('Failed to derive wrapping key', { cause: error });
}
}

/**
* Unwraps the given wrapped key
*
Expand Down
6 changes: 2 additions & 4 deletions src/keystore-crypto/core.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { encryptSymmetrically, decryptSymmetrically } from '../symmetric-crypto';
import { base64ToUint8Array, uint8ArrayToBase64, UTF8ToUint8, mnemonicToBytes } from '../utils';
import { deriveSymmetricKeyFromContext } from '../derive-key';
import { CONTEXT_ENC_KEYSTORE, AES_KEY_BYTE_LENGTH, CONTEXT_RECOVERY } from '../constants';
import { getBytesFromData } from '../hash';
import { CONTEXT_ENC_KEYSTORE, CONTEXT_RECOVERY } from '../constants';
import { EncryptedKeystore, HybridKeyPair, KeystoreType } from '../types';

/**
Expand Down Expand Up @@ -70,8 +69,7 @@ export async function decryptKeystoreContent(
*/
export async function deriveRecoveryKey(recoveryCodes: string): Promise<Uint8Array> {
const recoverCodesArray = mnemonicToBytes(recoveryCodes);
const recoveryCodesBuffer = getBytesFromData(AES_KEY_BYTE_LENGTH, recoverCodesArray);
return deriveSymmetricKeyFromContext(CONTEXT_RECOVERY, recoveryCodesBuffer);
return deriveSymmetricKeyFromContext(CONTEXT_RECOVERY, recoverCodesArray);
}

/**
Expand Down
40 changes: 1 addition & 39 deletions tests/derive-keys/deriveKeysFromPwd.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { describe, expect, it } from 'vitest';
import {
getKeyFromPasswordAndSalt,
verifyKeyFromPasswordHex,
getKeyFromPasswordAndSaltHex,
getKeyFromPasswordHex,
getKeyFromPassword,
} from '../../src/derive-key';
import { getKeyFromPasswordAndSalt, getKeyFromPassword } from '../../src/derive-key';

import { argon2, sampleSalt } from '../../src/derive-key/core';
import { uint8ArrayToHex } from '../../src/utils';
Expand Down Expand Up @@ -38,14 +32,6 @@ describe('Test Argon2', () => {
expect(test_salt_1).not.toBe(test_salt_2);
});

it('should sucessfully verify generated from the password and salt key', async () => {
const test_password = 'text demo';
const test_salt = uint8ArrayToHex(sampleSalt());
const test_key = await getKeyFromPasswordAndSaltHex(test_password, test_salt);
const result = await verifyKeyFromPasswordHex(test_password, test_salt, test_key);
expect(result).toBe(true);
});

it('should give the same result for the same password and salt', async () => {
const test_password = 'text demo';
const test_salt = sampleSalt();
Expand All @@ -54,24 +40,7 @@ describe('Test Argon2', () => {
expect(result1).toStrictEqual(result2);
});

it('should give different result for the same password but different salt', async () => {
const test_password = 'text demo';
const test_salt_1 = uint8ArrayToHex(sampleSalt());
const test_salt_2 = uint8ArrayToHex(sampleSalt());
const result1 = await getKeyFromPasswordAndSaltHex(test_password, test_salt_1);
const result2 = await getKeyFromPasswordAndSaltHex(test_password, test_salt_2);
expect(result1).not.toBe(result2);
});

it('should sucessfully verify generated from the password key', async () => {
const test_password = 'text demo';
const { keyHex: hash, saltHex: salt } = await getKeyFromPasswordHex(test_password);
const result = await verifyKeyFromPasswordHex(test_password, salt, hash);
expect(result).toBe(true);
});

it('should throw an error if no password is given', async () => {
await expect(getKeyFromPasswordHex('')).rejects.toThrowError(/Failed to derive key from password/);
await expect(getKeyFromPassword('')).rejects.toThrowError(/Failed to derive key from password/);
});

Expand All @@ -84,12 +53,5 @@ describe('Test Argon2', () => {
await expect(getKeyFromPasswordAndSalt('', test_salt)).rejects.toThrowError(
/Failed to derive key from password and salt/,
);

await expect(getKeyFromPasswordAndSaltHex(test_password, '')).rejects.toThrowError(
/Failed to derive key from password and salt/,
);
await expect(getKeyFromPasswordAndSaltHex('', uint8ArrayToHex(test_salt))).rejects.toThrowError(
/Failed to derive key from password and salt/,
);
});
});
Loading
Loading