diff --git a/README.md b/README.md index f7476e2..cf57493 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ expect(resultAlice).toStrictEqual(resultBob); // Symmetric encryption const data = utils.UTF8ToUint8('Sensitive information to encrypt'); // convert to Uint8Array const additionalData = 'Additional non-secret data'; -const key = await symmetric.genSymmetricCryptoKey(); // CryptoKey +const key = await symmetric.genSymmetricKey(); const ciphertext: Uint8Array = await symmetric.encryptSymmetrically(key, data, additionalData); const plainText = await symmetric.decryptSymmetrically(encryptionKey, ciphertext, additionalData); expect(data).toStrictEqual(plainText); diff --git a/package.json b/package.json index e76fb18..e761a1d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "dependencies": { "@noble/ciphers": "^2.1.1", + "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "@noble/post-quantum": "^0.5.2", "@scure/bip39": "^2.0.1", diff --git a/src/asymmetric-crypto/ellipticCurve.ts b/src/asymmetric-crypto/ellipticCurve.ts index 86fa4a0..6442ee5 100644 --- a/src/asymmetric-crypto/ellipticCurve.ts +++ b/src/asymmetric-crypto/ellipticCurve.ts @@ -1,24 +1,25 @@ -import { ECC_ALGORITHM, AES_KEY_BIT_LENGTH } from '../constants'; +import { x25519 } from '@noble/curves/webcrypto.js'; /** * Derives secret key from the other user's public key and own private key * - * @param otherUserPublicKey - The public key of the other user - * @param ownPrivateKey - The private key + * @param aliceSecX - The secret key of the user deriving the shared secret key + * @param bobPubX - The public key of the other user * @returns The derived secret key bits */ -export async function deriveSecretKey(otherUserPublicKey: CryptoKey, ownPrivateKey: CryptoKey): Promise { +export async function deriveSecretKey(aliceSecX: Uint8Array, bobPubX: Uint8Array): Promise { try { - const result = await crypto.subtle.deriveBits( - { - name: ECC_ALGORITHM, - public: otherUserPublicKey, - }, - ownPrivateKey, - AES_KEY_BIT_LENGTH, - ); - return new Uint8Array(result); + return await x25519.getSharedSecret(aliceSecX, bobPubX); } catch (error) { throw new Error('Failed to derive elliptic curve secret key', { cause: error }); } } + +/** + * Generates elliptic curve key pair + * + * @returns The generated key pair + */ +export async function generateEccKeys(): Promise<{ secretKey: Uint8Array; publicKey: Uint8Array }> { + return x25519.keygen(); +} diff --git a/src/asymmetric-crypto/index.ts b/src/asymmetric-crypto/index.ts index 750914c..722d9f0 100644 --- a/src/asymmetric-crypto/index.ts +++ b/src/asymmetric-crypto/index.ts @@ -1,2 +1 @@ export * from './ellipticCurve'; -export * from './keys'; diff --git a/src/asymmetric-crypto/keys.ts b/src/asymmetric-crypto/keys.ts deleted file mode 100644 index f794426..0000000 --- a/src/asymmetric-crypto/keys.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ECC_ALGORITHM } from '../constants'; - -/** - * Generates elliptic curve key pair - * - * @returns The generated key pair - */ -export async function generateEccKeys(): Promise { - try { - return (await crypto.subtle.generateKey( - { - name: ECC_ALGORITHM, - }, - true, - ['deriveBits'], - )) as CryptoKeyPair; - } catch (error) { - throw new Error('Failed to generate elliptic curve key pair', { cause: error }); - } -} - -/** - * Converts public CryptoKey into Uint8Array using SubjectPublicKeyInfo format (RFC 5280) - * - * @returns The Uint8Array representation of the public key - */ -export async function exportPublicKey(key: CryptoKey): Promise { - try { - const result = await crypto.subtle.exportKey('spki', key); - return new Uint8Array(result); - } catch (error) { - throw new Error('Failed to export public key', { cause: error }); - } -} - -/** - * Converts public key in SubjectPublicKeyInfo format (RFC 5280) to CryptoKey - * - * @returns The CryptoKey representation of the public key - */ -export async function importPublicKey(spkiKeyData: Uint8Array): Promise { - try { - return await crypto.subtle.importKey( - 'spki', - spkiKeyData as BufferSource, - { - name: ECC_ALGORITHM, - }, - true, - [], - ); - } catch (error) { - throw new Error('Failed to import public key', { cause: error }); - } -} - -/** - * Converts private key in CryptoKey to PKCS #8 format (RFC 5208) - * - * @returns The Uint8Array representation of the private key - */ -export async function exportPrivateKey(key: CryptoKey): Promise { - try { - const result = await crypto.subtle.exportKey('pkcs8', key); - return new Uint8Array(result); - } catch (error) { - throw new Error('Failed to export private key', { cause: error }); - } -} - -/** - * Converts private key in PKCS #8 format (RFC 5208) to CryptoKey - * - * @returns The CryptoKey representation of the private key - */ -export async function importPrivateKey(pkcs8KeyData: Uint8Array): Promise { - try { - return await crypto.subtle.importKey( - 'pkcs8', - pkcs8KeyData as BufferSource, - { - name: ECC_ALGORITHM, - }, - true, - ['deriveBits'], - ); - } catch (error) { - throw new Error('Failed to import private key', { cause: error }); - } -} diff --git a/src/constants.ts b/src/constants.ts index 569e4a7..ea34768 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,8 +7,6 @@ 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 ECC_ALGORITHM = 'X25519'; - 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'; diff --git a/src/index.ts b/src/index.ts index 1da2e3d..29cde29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,4 @@ -export { - deriveSecretKey, - generateEccKeys, - exportPublicKey, - importPublicKey, - exportPrivateKey, - importPrivateKey, -} from './asymmetric-crypto'; +export { deriveSecretKey, generateEccKeys } from './asymmetric-crypto'; export { deriveSymmetricKeyFromTwoKeys, deriveSymmetricCryptoKeyFromTwoKeys, diff --git a/src/types.ts b/src/types.ts index efa4b2b..86fe3d6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,16 +14,6 @@ export type UserWithPublicKey = User & { publicHybridKey: Uint8Array; }; -export type PublicKeys = { - eccPublicKey: CryptoKey; - kyberPublicKey: Uint8Array; -}; - -export type PublicKeysBase64 = { - eccPublicKeyBase64: string; - kyberPublicKeyBase64: string; -}; - export type HybridKeyPair = { publicKey: Uint8Array; secretKey: Uint8Array; diff --git a/tests/asymmetric-crypto/ecc.test.ts b/tests/asymmetric-crypto/ecc.test.ts index 05880ff..bf382f1 100644 --- a/tests/asymmetric-crypto/ecc.test.ts +++ b/tests/asymmetric-crypto/ecc.test.ts @@ -2,12 +2,18 @@ import { describe, expect, it } from 'vitest'; import { generateEccKeys, deriveSecretKey } from '../../src/asymmetric-crypto'; describe('Test ecc functions', () => { + it('should generate elliptic curves key pair', async () => { + const keyPair = await generateEccKeys(); + expect(keyPair.publicKey).toBeInstanceOf(Uint8Array); + expect(keyPair.secretKey).toBeInstanceOf(Uint8Array); + }); + it('should derive the same keys for Bob and Alice', async () => { const keysAlice = await generateEccKeys(); const keysBob = await generateEccKeys(); - const resultAlice = await deriveSecretKey(keysBob.publicKey, keysAlice.privateKey); - const resultBob = await deriveSecretKey(keysAlice.publicKey, keysBob.privateKey); + const resultAlice = await deriveSecretKey(keysBob.secretKey, keysAlice.publicKey); + const resultBob = await deriveSecretKey(keysAlice.secretKey, keysBob.publicKey); expect(resultAlice).toStrictEqual(resultBob); }); @@ -17,8 +23,8 @@ describe('Test ecc functions', () => { const keysBob = await generateEccKeys(); const keysEve = await generateEccKeys(); - const resultAliceEve = await deriveSecretKey(keysEve.publicKey, keysAlice.privateKey); - const resultAliceBob = await deriveSecretKey(keysBob.publicKey, keysAlice.privateKey); + const resultAliceEve = await deriveSecretKey(keysEve.secretKey, keysAlice.publicKey); + const resultAliceBob = await deriveSecretKey(keysBob.secretKey, keysAlice.publicKey); expect(resultAliceBob).not.toStrictEqual(resultAliceEve); }); @@ -26,7 +32,7 @@ describe('Test ecc functions', () => { it('should throw an error if cannot derive', async () => { const keysAlice = await generateEccKeys(); - await expect(deriveSecretKey(keysAlice.privateKey, keysAlice.privateKey)).rejects.toThrowError( + await expect(deriveSecretKey(keysAlice.secretKey, new Uint8Array())).rejects.toThrowError( /Failed to derive elliptic curve secret key/, ); }); diff --git a/tests/asymmetric-crypto/keys.test.ts b/tests/asymmetric-crypto/keys.test.ts deleted file mode 100644 index fe931a3..0000000 --- a/tests/asymmetric-crypto/keys.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { - generateEccKeys, - importPublicKey, - exportPublicKey, - exportPrivateKey, - deriveSecretKey, - importPrivateKey, -} from '../../src/asymmetric-crypto'; -import { ECC_ALGORITHM } from '../../src/constants'; -import { genSymmetricKey } from '../../src/symmetric-crypto'; - -describe('Test ecc functions', () => { - it('should generate elliptic curves key pair', async () => { - const keyPair = await generateEccKeys(); - - expect(keyPair).toHaveProperty('publicKey'); - expect(keyPair).toHaveProperty('privateKey'); - expect(keyPair.publicKey).toBeInstanceOf(CryptoKey); - expect(keyPair.privateKey).toBeInstanceOf(CryptoKey); - expect(keyPair.publicKey.type).toBe('public'); - expect(keyPair.privateKey.type).toBe('private'); - expect(keyPair.privateKey.extractable).toBeTruthy(); - expect(keyPair.privateKey.usages).toContain('deriveBits'); - - const alg = keyPair.publicKey.algorithm as EcKeyAlgorithm; - expect(alg.name).toBe(ECC_ALGORITHM); - }); - - it('should throw an error if generateKey fails', async () => { - const originalGenerateKey = crypto.subtle.generateKey; - - crypto.subtle.generateKey = vi.fn(() => { - throw new Error('simulated failure'); - }); - await expect(generateEccKeys()).rejects.rejects.toMatchObject({ - message: 'Failed to generate elliptic curve key pair', - cause: expect.any(Error), - }); - - crypto.subtle.generateKey = originalGenerateKey; - }); - - it('should export and import public and secret key', async () => { - const keyPair = await generateEccKeys(); - - const pk = keyPair.publicKey; - const publicKeyArray = await exportPublicKey(pk); - const publicKey = await importPublicKey(publicKeyArray); - - await expect(publicKey).toStrictEqual(pk); - - const sk = keyPair.privateKey; - const secretKeyArray = await exportPrivateKey(sk); - const secretKey = await importPrivateKey(secretKeyArray); - - await expect(secretKey).toStrictEqual(sk); - }); - - it('should sucessfully serive secret key', async () => { - const keyPair = await generateEccKeys(); - const keyPairSecond = await generateEccKeys(); - - const pk1 = keyPair.publicKey; - const pk2 = keyPairSecond.publicKey; - const sk1 = keyPair.privateKey; - const sk2 = keyPairSecond.privateKey; - - const resultOriginal = await deriveSecretKey(pk1, sk2); - const result = await deriveSecretKey(pk2, sk1); - - expect(resultOriginal).toStrictEqual(result); - }); - - it('should throw an error if given array is not a key', async () => { - const badKey = genSymmetricKey(); - await expect(importPublicKey(badKey)).rejects.toThrowError(/Failed to import public key/); - await expect(importPrivateKey(badKey)).rejects.toThrowError(/Failed to import private key/); - }); - - it('should throw an error if given CryptKey is not exportable', async () => { - const keyPair = await generateEccKeys(); - const badPublicKey = keyPair.privateKey; - await expect(exportPublicKey(badPublicKey)).rejects.toThrowError(/Failed to export public key/); - const badPrivateKey = keyPair.publicKey; - await expect(exportPrivateKey(badPrivateKey)).rejects.toThrowError(/Failed to export private key/); - }); - - it('should throw an error if key import fails', async () => { - const keyPair = await generateEccKeys(); - const pk = keyPair.publicKey; - const sk = keyPair.privateKey; - const publicKeyArray = await exportPublicKey(pk); - const secretKeyArray = await exportPrivateKey(sk); - - const originalImportKey = crypto.subtle.importKey; - - crypto.subtle.importKey = vi.fn(() => { - throw new Error('error', { cause: 'simulated failure' }); - }); - await expect(importPublicKey(publicKeyArray)).rejects.toThrowError(/Failed to import public key/); - - await expect(importPrivateKey(secretKeyArray)).rejects.toThrowError(/Failed to import private key/); - - crypto.subtle.importKey = originalImportKey; - }); -}); diff --git a/yarn.lock b/yarn.lock index afa08c3..dc2096b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -496,7 +496,7 @@ resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-2.1.1.tgz#c8c74fcda8c3d1f88797d0ecda24f9fc8b92b052" integrity sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw== -"@noble/curves@~2.0.0": +"@noble/curves@^2.0.1", "@noble/curves@~2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-2.0.1.tgz#64ba8bd5e8564a02942655602515646df1cdb3ad" integrity sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==