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
11 changes: 2 additions & 9 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
export const AES_ALGORITHM = 'AES-GCM';
export const AES_KEY_BIT_LENGTH = 256;
export const AUX_BYTE_LEN = 16;
export const IV_LEN_BYTES = 16;
export const AES_KEY_BYTE_LENGTH = 32;
export const IV_LEN_BYTES = 12;

export const KEY_WRAPPING_ALGORITHM = 'AES-KW';
export const KEY_FORMAT = 'raw';
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 All @@ -23,8 +18,6 @@ export const KYBER768_PUBLIC_KEY_LENGTH = 1184;
export const KYBER768_SECRET_KEY_LENGTH = 2400;
export const KYBER_SEED_LENGTH = 64;

export const HASH_BIT_LEN = 256;

export const MAX_CACHE_SIZE = 600000000; // 600 MB
export const MAX_EMAIL_PER_BATCH = 100;
export const DB_LABEL = 'email';
Expand Down
6 changes: 3 additions & 3 deletions src/derive-key/deriveKeysFromKey.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { blake3 } from '@noble/hashes/blake3.js';
import { AES_KEY_BIT_LENGTH, CONTEXT_DERIVE } from '../constants';
import { AES_KEY_BYTE_LENGTH, CONTEXT_DERIVE } from '../constants';
import { UTF8ToUint8 } from '../utils';

/**
Expand Down Expand Up @@ -39,8 +39,8 @@ export function deriveSymmetricKeyFromTwoKeysAndContext(
context: string,
): Uint8Array {
try {
if (key2.length != AES_KEY_BIT_LENGTH / 8 || key1.length != AES_KEY_BIT_LENGTH / 8) {
throw new Error(`Input key length must be exactly ${AES_KEY_BIT_LENGTH / 8} bytes`);
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) });
Expand Down
26 changes: 8 additions & 18 deletions src/email-crypto/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { encryptSymmetrically, decryptSymmetrically, genSymmetricKey } from '../
import { encapsulateHybrid, decapsulateHybrid } from '../hybrid-crypto';
import { wrapKey, unwrapKey } from '../key-wrapper';
import { getKeyFromPassword, getKeyFromPasswordAndSalt } from '../derive-key';
import { UTF8ToUint8, base64ToUint8Array, uint8ArrayToBase64, uint8ToUTF8, uuidToBytes } from '../utils';
import { UTF8ToUint8, base64ToUint8Array, uint8ArrayToBase64, uint8ToUTF8 } from '../utils';
import { getAux } from './utils';

/**
Expand All @@ -29,12 +29,12 @@ export async function encryptEmailBody(
let params = email.params;

if (isSubjectEncrypted) {
const result = await encryptEmailContentAndSubjectSymmetrically(email.body, email.params.subject, aux, email.id);
const result = await encryptEmailContentAndSubjectSymmetrically(email.body, email.params.subject, aux);
enc = result.enc;
encryptionKey = result.encryptionKey;
params = { ...email.params, subject: result.encSubject };
} else {
const result = await encryptEmailContentSymmetrically(email.body, aux, email.id);
const result = await encryptEmailContentSymmetrically(email.body, aux);
enc = result.enc;
encryptionKey = result.encryptionKey;
}
Expand Down Expand Up @@ -86,20 +86,18 @@ export async function decryptEmailBody(
*
* @param email - The email to encrypt.
* @param aux - The auxiliary data (e.g., email ID or timestamp) for AEAD.
* @param emailID - The unique identifier of the email.
* @returns The resulting ciphertext and the used symmetric key
*/
export async function encryptEmailContentSymmetrically(
email: EmailBody,
aux: Uint8Array,
emailID: string,
): Promise<{ enc: EmailBodyEncrypted; encryptionKey: Uint8Array }> {
try {
if (!email.text) {
throw new Error('Invalid input');
}
const encryptionKey = genSymmetricKey();
const enc = await encryptEmailContentSymmetricallyWithKey(email, encryptionKey, aux, emailID);
const enc = await encryptEmailContentSymmetricallyWithKey(email, encryptionKey, aux);
return { enc, encryptionKey };
} catch (error) {
throw new Error('Failed to symmetrically encrypt email', { cause: error });
Expand All @@ -112,21 +110,19 @@ export async function encryptEmailContentSymmetrically(
* @param email - The email to encrypt.
* @param subject - The email subject to encrypt.
* @param aux - The auxiliary data (e.g., email ID or timestamp) for AEAD.
* @param emailID - The unique identifier of the email.
* @returns The resulting ciphertext and the used symmetric key
*/
export async function encryptEmailContentAndSubjectSymmetrically(
email: EmailBody,
subject: string,
aux: Uint8Array,
emailID: string,
): Promise<{ enc: EmailBodyEncrypted; encSubject: string; encryptionKey: Uint8Array }> {
try {
if (!subject || !email.text) {
throw new Error('Invalid input');
}
const encryptionKey = genSymmetricKey();
const enc = await encryptEmailContentSymmetricallyWithKey(email, encryptionKey, aux, emailID);
const enc = await encryptEmailContentSymmetricallyWithKey(email, encryptionKey, aux);
const subjectBuff = UTF8ToUint8(subject);
const subjectEnc = await encryptSymmetrically(encryptionKey, subjectBuff, aux);
const encSubject = uint8ArrayToBase64(subjectEnc);
Expand Down Expand Up @@ -168,24 +164,21 @@ export async function decryptEmailAndSubjectSymmetrically(
* @param emailBody - The email body to encrypt.
* @param encryptionKey - The symmetric key for encryption.
* @param aux - The auxiliary data (e.g., email ID or timestamp) for AEAD.
* @param emailID - The unique identifier of the email.
* @returns The resulting encrypted emailBody
*/
export async function encryptEmailContentSymmetricallyWithKey(
emailBody: EmailBody,
encryptionKey: Uint8Array,
aux: Uint8Array,
emailID: string,
): Promise<EmailBodyEncrypted> {
try {
const freeField = uuidToBytes(emailID);
const text = UTF8ToUint8(emailBody.text);
const encryptedText = await encryptSymmetrically(encryptionKey, text, aux, freeField);
const encryptedText = await encryptSymmetrically(encryptionKey, text, aux);
const encText = uint8ArrayToBase64(encryptedText);
const result: EmailBodyEncrypted = { encText };

if (emailBody.attachments) {
const encryptedAttachements = await encryptEmailAttachements(emailBody.attachments, encryptionKey, aux, emailID);
const encryptedAttachements = await encryptEmailAttachements(emailBody.attachments, encryptionKey, aux);
result.encAttachments = encryptedAttachements?.map(uint8ArrayToBase64);
}
return result;
Expand All @@ -200,21 +193,18 @@ export async function encryptEmailContentSymmetricallyWithKey(
* @param attachments - The attachments.
* @param encryptionKey - The symmetric key.
* @param aux - The auxiliary data (e.g., email ID or timestamp) for AEAD.
* @param emailID - The unique identifier of the email.
* @returns The decrypted email attackements
*/
async function encryptEmailAttachements(
attachments: string[],
encryptionKey: Uint8Array,
aux: Uint8Array,
emailID: string,
): Promise<Uint8Array[]> {
try {
const freeField = uuidToBytes(emailID);
const encryptedAttachments = await Promise.all(
attachments.map((attachment) => {
const binaryAttachment = UTF8ToUint8(attachment);
return encryptSymmetrically(encryptionKey, binaryAttachment, aux, freeField);
return encryptSymmetrically(encryptionKey, binaryAttachment, aux);
}),
);
return encryptedAttachments;
Expand Down
4 changes: 2 additions & 2 deletions src/email-search/indexedDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const encryptAndStoreEmail = async (
): Promise<void> => {
try {
const aux = getAux(newEmailToStore.params, false);
const enc = await encryptEmailContentSymmetricallyWithKey(newEmailToStore.body, indexKey, aux, newEmailToStore.id);
const enc = await encryptEmailContentSymmetricallyWithKey(newEmailToStore.body, indexKey, aux);
const encryptedEmail: StoredEmail = { enc, params: newEmailToStore.params, id: newEmailToStore.id };
await esDB.put(DB_LABEL, encryptedEmail);
} catch (error) {
Expand All @@ -115,7 +115,7 @@ export const encryptAndStoreManyEmail = async (
const encryptedEmails = await Promise.all(
newEmailsToStore.map(async (email: Email) => {
const aux = getAux(email.params, false);
const enc = await encryptEmailContentSymmetricallyWithKey(email.body, indexKey, aux, email.id);
const enc = await encryptEmailContentSymmetricallyWithKey(email.body, indexKey, aux);

return { enc, params: email.params, id: email.id };
}),
Expand Down
4 changes: 2 additions & 2 deletions src/hash/mac.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { bytesToHex } from '@noble/hashes/utils.js';
import { AES_KEY_BIT_LENGTH } from '../constants';
import { AES_KEY_BYTE_LENGTH } from '../constants';
import { getBytesFromData, hashDataArrayWithKey } from './blake3';

/**
Expand All @@ -11,7 +11,7 @@ import { getBytesFromData, hashDataArrayWithKey } from './blake3';
*/
export function computeMac(keyMaterial: Uint8Array, data: Uint8Array[]): string {
try {
const key = getBytesFromData(AES_KEY_BIT_LENGTH / 8, keyMaterial);
const key = getBytesFromData(AES_KEY_BYTE_LENGTH, keyMaterial);
const hash = hashDataArrayWithKey(key, data);
return bytesToHex(hash);
} catch (error) {
Expand Down
9 changes: 1 addition & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,7 @@ export {
export { unwrapKey, wrapKey } from './key-wrapper';
export { createEncryptionAndRecoveryKeystores, openEncryptionKeystore, openRecoveryKeystore } from './keystore-crypto';
export { generateKyberKeys, encapsulateKyber, decapsulateKyber } from './post-quantum-crypto';
export {
encryptSymmetrically,
decryptSymmetrically,
importSymmetricCryptoKey,
exportSymmetricCryptoKey,
genSymmetricCryptoKey,
genSymmetricKey,
} from './symmetric-crypto';
export { encryptSymmetrically, decryptSymmetrically, genSymmetricKey } from './symmetric-crypto';
export {
uint8ArrayToHex,
UTF8ToUint8,
Expand Down
4 changes: 2 additions & 2 deletions src/keystore-crypto/core.ts
Original file line number Diff line number Diff line change
@@ -1,7 +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_BIT_LENGTH, CONTEXT_RECOVERY } from '../constants';
import { CONTEXT_ENC_KEYSTORE, AES_KEY_BYTE_LENGTH, CONTEXT_RECOVERY } from '../constants';
import { getBytesFromData } from '../hash';
import { EncryptedKeystore, HybridKeyPair, KeystoreType } from '../types';

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

Expand Down
31 changes: 19 additions & 12 deletions src/symmetric-crypto/aes.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import { createNISTbasedIV, encryptMessage, decryptMessage } from './core';
import { concatBytes } from '@noble/hashes/utils.js';
import { IV_LEN_BYTES } from '../constants';
import { concatBytes, randomBytes } from '@noble/hashes/utils.js';
import { IV_LEN_BYTES, AES_KEY_BYTE_LENGTH } from '../constants';
import { gcm as aeadCipher } from '@noble/ciphers/webcrypto.js';

/**
* Symmetrically encrypts the message
*
* @param encryptionKey - The symmetric key used for message encryption
* @param message - The message to encrypt
* @param freeField - The context of the message (required for IV generation)
* @param aux - The auxilary string
* @param aux - The auxilary string (e.g., context string or timestamp) for AEAD.
* @returns The resulting ciphertext.
*/
export async function encryptSymmetrically(
encryptionKey: Uint8Array,
message: Uint8Array,
aux: Uint8Array,
freeField?: Uint8Array,
aux?: Uint8Array,
): Promise<Uint8Array> {
try {
const iv = createNISTbasedIV(freeField);
const ciphertext = await encryptMessage(message, encryptionKey, iv, aux);
const iv = randomBytes(IV_LEN_BYTES);
const ciphertext = await aeadCipher(encryptionKey, iv, aux).encrypt(message);
return concatBytes(ciphertext, iv);
} catch (error) {
throw new Error('Failed to encrypt symmetrically', { cause: error });
Expand All @@ -31,20 +29,29 @@ export async function encryptSymmetrically(
*
* @param encryptionKey - The symmetric key used for message encryption
* @param encryptedMessage - The ciphertext
* @param aux - The auxilary string
* @param aux - The auxilary string (e.g., context string or timestamp) for AEAD.
* @returns The resulting ciphertext.
*/
export async function decryptSymmetrically(
encryptionKey: Uint8Array,
encryptedMessage: Uint8Array,
aux: Uint8Array,
aux?: Uint8Array,
): Promise<Uint8Array> {
try {
const ciphertext = encryptedMessage.slice(0, encryptedMessage.length - IV_LEN_BYTES);
const iv = encryptedMessage.slice(encryptedMessage.length - IV_LEN_BYTES);
const result = await decryptMessage(ciphertext, iv, encryptionKey, aux);
const result = await aeadCipher(encryptionKey, iv, aux).decrypt(ciphertext);
return result;
} catch (error) {
throw new Error('Failed to decrypt symmetrically', { cause: error });
}
}

/**
* Generates symmetric key as Uint8Array
*
* @returns The generated Uint8Array.
*/
export function genSymmetricKey(): Uint8Array {
return randomBytes(AES_KEY_BYTE_LENGTH);
}
68 changes: 0 additions & 68 deletions src/symmetric-crypto/core.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/symmetric-crypto/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './keys';
export * from './aes';
Loading
Loading