From 517317f36e5b92cd115b34e73ac1b17c7613690a Mon Sep 17 00:00:00 2001 From: Tobi Ojuolape <45619240+Toheeb-Ojuolape@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:30:31 +0100 Subject: [PATCH 1/5] refactor: replaced built-in crypto library with @web5/crypto --- package-lock.json | 102 +++++++++++++++++++++++++++++++--------- package.json | 1 + src/utils/encryption.ts | 96 +++++++++++++++++++++++++------------ src/utils/hd-key.ts | 3 +- 4 files changed, 147 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index c2ec79454..281788ac2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@noble/curves": "1.4.2", "@noble/ed25519": "2.0.0", "@noble/secp256k1": "2.0.0", + "@web5/crypto": "^1.0.5", "@web5/dids": "^1.1.3", "abstract-level": "1.0.3", "ajv": "8.12.0", @@ -1249,14 +1250,14 @@ } }, "node_modules/@web5/crypto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-1.0.3.tgz", - "integrity": "sha512-gZJKo0scX+L53E2K/5cgEiFYxejzHP2RSg64ncF6TitOnCNxUyWjofovgufb+u3ZpGC4iuliD7V0o1C+V73Law==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-1.0.5.tgz", + "integrity": "sha512-btVYrv6G29VQgdLVtqH4IbaH6DvvEA2UWgF3y0AMv/wAmT4EKSdwVUHhEWt16gtpN05UT5qEYmnKLZUOxk1zzw==", "dependencies": { "@noble/ciphers": "0.5.3", "@noble/curves": "1.3.0", "@noble/hashes": "1.4.0", - "@web5/common": "1.0.1" + "@web5/common": "1.0.2" }, "engines": { "node": ">=18.0.0" @@ -1296,14 +1297,14 @@ } }, "node_modules/@web5/crypto/node_modules/@web5/common": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@web5/common/-/common-1.0.1.tgz", - "integrity": "sha512-dxczXqzWt6HCwuNyOVBeakg6GgOpP74tVEVxBeKkb+D3XcSP96mYaDtky5ZnjY4iBYb16SaCgwje+sgevOL51A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@web5/common/-/common-1.0.2.tgz", + "integrity": "sha512-SerGdrxZF47yidvhrRa8sGLEOunIlDHppxrtWYCuKMVgtQKgheEmaS4+xchGAc/mZggJX4LlwJbRuniIiSaXrw==", "dependencies": { "@isaacs/ttlcache": "1.4.1", "level": "8.0.1", "multiformats": "13.1.0", - "readable-stream": "4.4.2" + "readable-stream": "4.5.2" }, "engines": { "node": ">=18.0.0" @@ -1348,21 +1349,6 @@ "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.1.0.tgz", "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==" }, - "node_modules/@web5/crypto/node_modules/readable-stream": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", - "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@web5/dids": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@web5/dids/-/dids-1.1.3.tgz", @@ -1382,6 +1368,56 @@ "node": ">=18.0.0" } }, + "node_modules/@web5/dids/node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@web5/dids/node_modules/@web5/crypto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@web5/crypto/-/crypto-1.0.3.tgz", + "integrity": "sha512-gZJKo0scX+L53E2K/5cgEiFYxejzHP2RSg64ncF6TitOnCNxUyWjofovgufb+u3ZpGC4iuliD7V0o1C+V73Law==", + "dependencies": { + "@noble/ciphers": "0.5.3", + "@noble/curves": "1.3.0", + "@noble/hashes": "1.4.0", + "@web5/common": "1.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web5/dids/node_modules/@web5/crypto/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@web5/dids/node_modules/@web5/crypto/node_modules/@web5/common": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@web5/common/-/common-1.0.1.tgz", + "integrity": "sha512-dxczXqzWt6HCwuNyOVBeakg6GgOpP74tVEVxBeKkb+D3XcSP96mYaDtky5ZnjY4iBYb16SaCgwje+sgevOL51A==", + "dependencies": { + "@isaacs/ttlcache": "1.4.1", + "level": "8.0.1", + "multiformats": "13.1.0", + "readable-stream": "4.4.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@web5/dids/node_modules/abstract-level": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.4.tgz", @@ -1416,6 +1452,26 @@ "url": "https://opencollective.com/level" } }, + "node_modules/@web5/dids/node_modules/multiformats": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.1.0.tgz", + "integrity": "sha512-HzdtdBwxsIkzpeXzhQ5mAhhuxcHbjEHH+JQoxt7hG/2HGFjjwyolLo7hbaexcnhoEuV4e0TNJ8kkpMjiEYY4VQ==" + }, + "node_modules/@web5/dids/node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", diff --git a/package.json b/package.json index 43ba8d8af..b1735dd03 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@noble/curves": "1.4.2", "@noble/ed25519": "2.0.0", "@noble/secp256k1": "2.0.0", + "@web5/crypto": "^1.0.5", "@web5/dids": "^1.1.3", "abstract-level": "1.0.3", "ajv": "8.12.0", diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts index ca1d4771d..a334ab308 100644 --- a/src/utils/encryption.ts +++ b/src/utils/encryption.ts @@ -1,5 +1,6 @@ -import * as crypto from 'crypto'; import * as eciesjs from 'eciesjs'; +import { AesCtr } from '@web5/crypto'; +import type { Jwk } from '@web5/crypto'; import { Readable } from 'readable-stream'; // compress publicKey for message encryption @@ -12,57 +13,87 @@ export class Encryption { /** * Encrypts the given plaintext stream using AES-256-CTR algorithm. */ - public static async aes256CtrEncrypt(key: Uint8Array, initializationVector: Uint8Array, plaintextStream: Readable): Promise { - const cipher = crypto.createCipheriv('aes-256-ctr', key, initializationVector); + private static async convertToJwk(key: Uint8Array): Promise { + return { + kty : 'oct', + k : Buffer.from(key).toString('base64url'), + alg : 'A256CTR', + ext : 'true', + }; + } + + public static async aes256CtrEncrypt( + key: Uint8Array, + initializationVector: Uint8Array, + plaintextStream: Readable + ): Promise { + const jwkKey = await this.convertToJwk(key); + + // Create a cipher stream const cipherStream = new Readable({ - read(): void { } + read(): void {}, }); - plaintextStream.on('data', (chunk) => { - const encryptedChunk = cipher.update(chunk); + plaintextStream.on('data', async (chunk) => { + // Encrypt the chunk using AesCtr + const encryptedChunk = await AesCtr.encrypt({ + data : chunk, + key : jwkKey, + counter : initializationVector, + length : 256, + }); + cipherStream.push(encryptedChunk); }); plaintextStream.on('end', () => { - const finalChunk = cipher.final(); - cipherStream.push(finalChunk); - cipherStream.push(null); + cipherStream.push(null); // Signal the end of the stream }); plaintextStream.on('error', (err) => { - cipherStream.emit('error', err); + cipherStream.emit('error', err); // Emit error if any occurs in the plaintext stream }); - return cipherStream; + return cipherStream; // Return the cipher stream } /** * Decrypts the given cipher stream using AES-256-CTR algorithm. */ - public static async aes256CtrDecrypt(key: Uint8Array, initializationVector: Uint8Array, cipherStream: Readable): Promise { - const decipher = crypto.createDecipheriv('aes-256-ctr', key, initializationVector); - + public static async aes256CtrDecrypt( + key: Uint8Array, + initializationVector: Uint8Array, + cipherStream: Readable + ): Promise { + const jwkKey = await this.convertToJwk(key); // Convert key to JWK format + + // Create a plaintext stream const plaintextStream = new Readable({ - read(): void { } + read(): void {}, }); - cipherStream.on('data', (chunk) => { - const decryptedChunk = decipher.update(chunk); - plaintextStream.push(decryptedChunk); + cipherStream.on('data', async (chunk) => { + // Decrypt the chunk using AesCtr + const decryptedChunk = await AesCtr.decrypt({ + data : chunk, + key : jwkKey, + counter : initializationVector, + length : 256, // Length of the key in bits + }); + + plaintextStream.push(decryptedChunk); // Push the decrypted chunk to the plaintext stream }); cipherStream.on('end', () => { - const finalChunk = decipher.final(); - plaintextStream.push(finalChunk); - plaintextStream.push(null); + plaintextStream.push(null); // Signal the end of the stream }); cipherStream.on('error', (err) => { - plaintextStream.emit('error', err); + plaintextStream.emit('error', err); // Emit error if any occurs in the cipher stream }); - return plaintextStream; + return plaintextStream; // Return the plaintext stream } /** @@ -70,7 +101,10 @@ export class Encryption { * with SECP256K1 for the asymmetric calculations, HKDF as the key-derivation function, * and AES-GCM for the symmetric encryption and MAC algorithms. */ - public static async eciesSecp256k1Encrypt(publicKeyBytes: Uint8Array, plaintext: Uint8Array): Promise { + public static async eciesSecp256k1Encrypt( + publicKeyBytes: Uint8Array, + plaintext: Uint8Array + ): Promise { // underlying library requires Buffer as input const publicKey = Buffer.from(publicKeyBytes); const plaintextBuffer = Buffer.from(plaintext); @@ -96,7 +130,7 @@ export class Encryption { ciphertext, ephemeralPublicKey, initializationVector, - messageAuthenticationCode + messageAuthenticationCode, }; } @@ -105,14 +139,16 @@ export class Encryption { * with SECP256K1 for the asymmetric calculations, HKDF as the key-derivation function, * and AES-GCM for the symmetric encryption and MAC algorithms. */ - public static async eciesSecp256k1Decrypt(input: EciesEncryptionInput): Promise { + public static async eciesSecp256k1Decrypt( + input: EciesEncryptionInput + ): Promise { // underlying library requires Buffer as input const privateKeyBuffer = Buffer.from(input.privateKey); const eciesEncryptionOutput = Buffer.concat([ input.ephemeralPublicKey, input.initializationVector, input.messageAuthenticationCode, - input.ciphertext + input.ciphertext, ]); const plaintext = eciesjs.decrypt(privateKeyBuffer, eciesEncryptionOutput); @@ -123,7 +159,7 @@ export class Encryption { /** * Expose eciesjs library configuration */ - static get isEphemeralKeyCompressed():boolean { + static get isEphemeralKeyCompressed(): boolean { return eciesjs.ECIES_CONFIG.isEphemeralKeyCompressed; } } @@ -141,5 +177,5 @@ export type EciesEncryptionInput = EciesEncryptionOutput & { export enum EncryptionAlgorithm { Aes256Ctr = 'A256CTR', - EciesSecp256k1 = 'ECIES-ES256K' -} \ No newline at end of file + EciesSecp256k1 = 'ECIES-ES256K', +} diff --git a/src/utils/hd-key.ts b/src/utils/hd-key.ts index 6a64bd7b9..7ccbd9544 100644 --- a/src/utils/hd-key.ts +++ b/src/utils/hd-key.ts @@ -1,9 +1,8 @@ -import type { PrivateJwk, PublicJwk } from '../types/jose-types.js'; - import { Encoder } from './encoder.js'; import { getWebcryptoSubtle } from '@noble/ciphers/webcrypto'; import { Secp256k1 } from './secp256k1.js'; import { DwnError, DwnErrorCode } from '../core/dwn-error.js'; +import type { PrivateJwk, PublicJwk } from '../types/jose-types.js'; export enum KeyDerivationScheme { /** From 5b705b5893d744ce06e6cd037cd7cc0a39744186 Mon Sep 17 00:00:00 2001 From: Tobi Ojuolape <45619240+Toheeb-Ojuolape@users.noreply.github.com> Date: Fri, 11 Oct 2024 23:12:14 +0100 Subject: [PATCH 2/5] fix: refactored code to pass encryption tests --- src/utils/encryption.ts | 64 +++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts index a334ab308..02b7303c2 100644 --- a/src/utils/encryption.ts +++ b/src/utils/encryption.ts @@ -3,7 +3,7 @@ import { AesCtr } from '@web5/crypto'; import type { Jwk } from '@web5/crypto'; import { Readable } from 'readable-stream'; -// compress publicKey for message encryption +// Compress publicKey for message encryption eciesjs.ECIES_CONFIG.isEphemeralKeyCompressed = true; /** @@ -35,24 +35,31 @@ export class Encryption { read(): void {}, }); + let buffer = Buffer.alloc(0); + plaintextStream.on('data', async (chunk) => { - // Encrypt the chunk using AesCtr - const encryptedChunk = await AesCtr.encrypt({ - data : chunk, - key : jwkKey, - counter : initializationVector, - length : 256, - }); - - cipherStream.push(encryptedChunk); + buffer = Buffer.concat([buffer, chunk]); }); - plaintextStream.on('end', () => { - cipherStream.push(null); // Signal the end of the stream + plaintextStream.on('end', async () => { + try { + // Encrypt the entire buffer when the stream ends + const encryptedData = await AesCtr.encrypt({ + data : buffer, + key : jwkKey, + counter : initializationVector, + length : 128, // FIX: Counter length must be between 1 and 128 + }); + + cipherStream.push(encryptedData); + cipherStream.push(null); // Signal the end of the stream + } catch (error) { + cipherStream.emit('error', error); // Emit error if encryption fails + } }); plaintextStream.on('error', (err) => { - cipherStream.emit('error', err); // Emit error if any occurs in the plaintext stream + cipherStream.emit('error', err); // Propagate errors }); return cipherStream; // Return the cipher stream @@ -73,24 +80,31 @@ export class Encryption { read(): void {}, }); + let buffer = Buffer.alloc(0); + cipherStream.on('data', async (chunk) => { - // Decrypt the chunk using AesCtr - const decryptedChunk = await AesCtr.decrypt({ - data : chunk, - key : jwkKey, - counter : initializationVector, - length : 256, // Length of the key in bits - }); - - plaintextStream.push(decryptedChunk); // Push the decrypted chunk to the plaintext stream + buffer = Buffer.concat([buffer, chunk]); }); - cipherStream.on('end', () => { - plaintextStream.push(null); // Signal the end of the stream + cipherStream.on('end', async () => { + try { + // Decrypt the entire buffer when the stream ends + const decryptedData = await AesCtr.decrypt({ + data : buffer, + key : jwkKey, + counter : initializationVector, + length : 128, // FIX: Counter length must be between 1 and 128 + }); + + plaintextStream.push(decryptedData); + plaintextStream.push(null); // Signal the end of the stream + } catch (error) { + plaintextStream.emit('error', error); // Emit error if decryption fails + } }); cipherStream.on('error', (err) => { - plaintextStream.emit('error', err); // Emit error if any occurs in the cipher stream + plaintextStream.emit('error', err); // Propagate errors }); return plaintextStream; // Return the plaintext stream From 3d68bc2e560928850a24c378dc55c032e5c1d49f Mon Sep 17 00:00:00 2001 From: Tobi Ojuolape <45619240+Toheeb-Ojuolape@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:21:38 +0100 Subject: [PATCH 3/5] fix: refactor code to pass browser test --- src/utils/encryption.ts | 71 +++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts index 02b7303c2..c46b8cc8a 100644 --- a/src/utils/encryption.ts +++ b/src/utils/encryption.ts @@ -6,18 +6,42 @@ import { Readable } from 'readable-stream'; // Compress publicKey for message encryption eciesjs.ECIES_CONFIG.isEphemeralKeyCompressed = true; +export interface EciesEncryptionOutput { + ciphertext: Uint8Array; + ephemeralPublicKey: Uint8Array; + initializationVector: Uint8Array; + messageAuthenticationCode: Uint8Array; +} + +export interface EciesEncryptionInput { + privateKey: Uint8Array; + ephemeralPublicKey: Uint8Array; + initializationVector: Uint8Array; + messageAuthenticationCode: Uint8Array; + ciphertext: Uint8Array; +} + /** * Utility class for performing common, non-DWN specific encryption operations. */ export class Encryption { /** - * Encrypts the given plaintext stream using AES-256-CTR algorithm. + * Converts a key to base64url encoding + * @param key - Uint8Array to convert */ + public static isEphemeralKeyCompressed: boolean = true; // Set default value + + private static toBase64Url(buffer: Buffer): string { + return buffer.toString('base64') // Convert to base64 + .replace(/\+/g, '-') // Replace + with - + .replace(/\//g, '_') // Replace / with _ + .replace(/=+$/, ''); // Remove any trailing '=' + } private static async convertToJwk(key: Uint8Array): Promise { return { kty : 'oct', - k : Buffer.from(key).toString('base64url'), + k : this.toBase64Url(Buffer.from(key)), // Use the new base64url method alg : 'A256CTR', ext : 'true', }; @@ -37,7 +61,7 @@ export class Encryption { let buffer = Buffer.alloc(0); - plaintextStream.on('data', async (chunk) => { + plaintextStream.on('data', (chunk) => { buffer = Buffer.concat([buffer, chunk]); }); @@ -65,9 +89,6 @@ export class Encryption { return cipherStream; // Return the cipher stream } - /** - * Decrypts the given cipher stream using AES-256-CTR algorithm. - */ public static async aes256CtrDecrypt( key: Uint8Array, initializationVector: Uint8Array, @@ -82,7 +103,7 @@ export class Encryption { let buffer = Buffer.alloc(0); - cipherStream.on('data', async (chunk) => { + cipherStream.on('data', (chunk) => { buffer = Buffer.concat([buffer, chunk]); }); @@ -110,22 +131,15 @@ export class Encryption { return plaintextStream; // Return the plaintext stream } - /** - * Encrypts the given plaintext using ECIES (Elliptic Curve Integrated Encryption Scheme) - * with SECP256K1 for the asymmetric calculations, HKDF as the key-derivation function, - * and AES-GCM for the symmetric encryption and MAC algorithms. - */ public static async eciesSecp256k1Encrypt( publicKeyBytes: Uint8Array, plaintext: Uint8Array ): Promise { - // underlying library requires Buffer as input const publicKey = Buffer.from(publicKeyBytes); const plaintextBuffer = Buffer.from(plaintext); const cryptogram = eciesjs.encrypt(publicKey, plaintextBuffer); - // split cryptogram returned into constituent parts let start = 0; let end = Encryption.isEphemeralKeyCompressed ? 33 : 65; const ephemeralPublicKey = cryptogram.subarray(start, end); @@ -148,15 +162,9 @@ export class Encryption { }; } - /** - * Decrypt the given plaintext using ECIES (Elliptic Curve Integrated Encryption Scheme) - * with SECP256K1 for the asymmetric calculations, HKDF as the key-derivation function, - * and AES-GCM for the symmetric encryption and MAC algorithms. - */ public static async eciesSecp256k1Decrypt( input: EciesEncryptionInput ): Promise { - // underlying library requires Buffer as input const privateKeyBuffer = Buffer.from(input.privateKey); const eciesEncryptionOutput = Buffer.concat([ input.ephemeralPublicKey, @@ -165,31 +173,12 @@ export class Encryption { input.ciphertext, ]); - const plaintext = eciesjs.decrypt(privateKeyBuffer, eciesEncryptionOutput); - - return plaintext; - } - - /** - * Expose eciesjs library configuration - */ - static get isEphemeralKeyCompressed(): boolean { - return eciesjs.ECIES_CONFIG.isEphemeralKeyCompressed; + return eciesjs.decrypt(privateKeyBuffer, eciesEncryptionOutput); } } -export type EciesEncryptionOutput = { - initializationVector: Uint8Array; - ephemeralPublicKey: Uint8Array; - ciphertext: Uint8Array; - messageAuthenticationCode: Uint8Array; -}; - -export type EciesEncryptionInput = EciesEncryptionOutput & { - privateKey: Uint8Array; -}; export enum EncryptionAlgorithm { Aes256Ctr = 'A256CTR', EciesSecp256k1 = 'ECIES-ES256K', -} +} \ No newline at end of file From 7220425ffa392960c5755a445b3a37b1d855b995 Mon Sep 17 00:00:00 2001 From: Tobi Ojuolape <45619240+Toheeb-Ojuolape@users.noreply.github.com> Date: Tue, 22 Oct 2024 00:56:09 +0100 Subject: [PATCH 4/5] fix: return comments to encryption file --- src/utils/encryption.ts | 28 ++++++++++++++++++++++------ src/utils/hd-key.ts | 2 +- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts index c46b8cc8a..5eb493649 100644 --- a/src/utils/encryption.ts +++ b/src/utils/encryption.ts @@ -6,29 +6,30 @@ import { Readable } from 'readable-stream'; // Compress publicKey for message encryption eciesjs.ECIES_CONFIG.isEphemeralKeyCompressed = true; -export interface EciesEncryptionOutput { +export type EciesEncryptionOutput = { ciphertext: Uint8Array; ephemeralPublicKey: Uint8Array; initializationVector: Uint8Array; messageAuthenticationCode: Uint8Array; -} +}; -export interface EciesEncryptionInput { +export type EciesEncryptionInput = { privateKey: Uint8Array; ephemeralPublicKey: Uint8Array; initializationVector: Uint8Array; messageAuthenticationCode: Uint8Array; ciphertext: Uint8Array; -} +}; /** * Utility class for performing common, non-DWN specific encryption operations. */ export class Encryption { + /** - * Converts a key to base64url encoding - * @param key - Uint8Array to convert + * Encrypts the given plaintext stream using AES-256-CTR algorithm. */ + public static isEphemeralKeyCompressed: boolean = true; // Set default value private static toBase64Url(buffer: Buffer): string { @@ -89,6 +90,11 @@ export class Encryption { return cipherStream; // Return the cipher stream } + + /** + * Decrypts the given cipher stream using AES-256-CTR algorithm. + */ + public static async aes256CtrDecrypt( key: Uint8Array, initializationVector: Uint8Array, @@ -139,6 +145,7 @@ export class Encryption { const plaintextBuffer = Buffer.from(plaintext); const cryptogram = eciesjs.encrypt(publicKey, plaintextBuffer); + // split cryptogram returned into constituent parts let start = 0; let end = Encryption.isEphemeralKeyCompressed ? 33 : 65; @@ -162,6 +169,12 @@ export class Encryption { }; } + /** + * Decrypt the given plaintext using ECIES (Elliptic Curve Integrated Encryption Scheme) + * with SECP256K1 for the asymmetric calculations, HKDF as the key-derivation function, + * and AES-GCM for the symmetric encryption and MAC algorithms. + */ + public static async eciesSecp256k1Decrypt( input: EciesEncryptionInput ): Promise { @@ -173,6 +186,9 @@ export class Encryption { input.ciphertext, ]); + /** + * Expose eciesjs library configuration + */ return eciesjs.decrypt(privateKeyBuffer, eciesEncryptionOutput); } } diff --git a/src/utils/hd-key.ts b/src/utils/hd-key.ts index 7ccbd9544..24ad49e18 100644 --- a/src/utils/hd-key.ts +++ b/src/utils/hd-key.ts @@ -1,8 +1,8 @@ +import type { PrivateJwk, PublicJwk } from '../types/jose-types.js'; import { Encoder } from './encoder.js'; import { getWebcryptoSubtle } from '@noble/ciphers/webcrypto'; import { Secp256k1 } from './secp256k1.js'; import { DwnError, DwnErrorCode } from '../core/dwn-error.js'; -import type { PrivateJwk, PublicJwk } from '../types/jose-types.js'; export enum KeyDerivationScheme { /** From 20156fa650a83b043ac455601beb0064eabf14f6 Mon Sep 17 00:00:00 2001 From: Tobi Ojuolape <45619240+Toheeb-Ojuolape@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:40:24 +0100 Subject: [PATCH 5/5] fix: use bytesToPrivateKey --- src/utils/encryption.ts | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts index 5eb493649..e4d984bcf 100644 --- a/src/utils/encryption.ts +++ b/src/utils/encryption.ts @@ -25,24 +25,19 @@ export type EciesEncryptionInput = { * Utility class for performing common, non-DWN specific encryption operations. */ export class Encryption { - /** * Encrypts the given plaintext stream using AES-256-CTR algorithm. */ public static isEphemeralKeyCompressed: boolean = true; // Set default value - private static toBase64Url(buffer: Buffer): string { - return buffer.toString('base64') // Convert to base64 - .replace(/\+/g, '-') // Replace + with - - .replace(/\//g, '_') // Replace / with _ - .replace(/=+$/, ''); // Remove any trailing '=' - } - private static async convertToJwk(key: Uint8Array): Promise { + // Construct the private key in JWK format using bytesToPrivateKey method. + const privateKey = await AesCtr.bytesToPrivateKey({ privateKeyBytes: key }); + + // Assign the algorithm and extractability flag. return { - kty : 'oct', - k : this.toBase64Url(Buffer.from(key)), // Use the new base64url method + ...privateKey, alg : 'A256CTR', ext : 'true', }; @@ -90,7 +85,6 @@ export class Encryption { return cipherStream; // Return the cipher stream } - /** * Decrypts the given cipher stream using AES-256-CTR algorithm. */ @@ -120,7 +114,7 @@ export class Encryption { data : buffer, key : jwkKey, counter : initializationVector, - length : 128, // FIX: Counter length must be between 1 and 128 + length : 128, }); plaintextStream.push(decryptedData); @@ -187,14 +181,13 @@ export class Encryption { ]); /** - * Expose eciesjs library configuration - */ + * Expose eciesjs library configuration + */ return eciesjs.decrypt(privateKeyBuffer, eciesEncryptionOutput); } } - export enum EncryptionAlgorithm { Aes256Ctr = 'A256CTR', EciesSecp256k1 = 'ECIES-ES256K', -} \ No newline at end of file +}