Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: replaced built-in crypto library with @web5/crypto #816

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
102 changes: 79 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
118 changes: 84 additions & 34 deletions src/utils/encryption.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
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
// Compress publicKey for message encryption
eciesjs.ECIES_CONFIG.isEphemeralKeyCompressed = true;

/**
Expand All @@ -12,65 +13,112 @@ export class Encryption {
/**
* Encrypts the given plaintext stream using AES-256-CTR algorithm.
*/
public static async aes256CtrEncrypt(key: Uint8Array, initializationVector: Uint8Array, plaintextStream: Readable): Promise<Readable> {
const cipher = crypto.createCipheriv('aes-256-ctr', key, initializationVector);

private static async convertToJwk(key: Uint8Array): Promise<Jwk> {
return {
kty : 'oct',
k : Buffer.from(key).toString('base64url'),
alg : 'A256CTR',
ext : 'true',
};
}

public static async aes256CtrEncrypt(
key: Uint8Array,
initializationVector: Uint8Array,
plaintextStream: Readable
): Promise<Readable> {
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);
cipherStream.push(encryptedChunk);
let buffer = Buffer.alloc(0);

plaintextStream.on('data', async (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
});

plaintextStream.on('end', () => {
const finalChunk = cipher.final();
cipherStream.push(finalChunk);
cipherStream.push(null);
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);
cipherStream.emit('error', err); // Propagate errors
});

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<Readable> {
const decipher = crypto.createDecipheriv('aes-256-ctr', key, initializationVector);

public static async aes256CtrDecrypt(
key: Uint8Array,
initializationVector: Uint8Array,
cipherStream: Readable
): Promise<Readable> {
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);
let buffer = Buffer.alloc(0);

cipherStream.on('data', async (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
});

cipherStream.on('end', () => {
const finalChunk = decipher.final();
plaintextStream.push(finalChunk);
plaintextStream.push(null);
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);
plaintextStream.emit('error', err); // Propagate errors
});

return plaintextStream;
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<EciesEncryptionOutput> {
public static async eciesSecp256k1Encrypt(
publicKeyBytes: Uint8Array,
plaintext: Uint8Array
): Promise<EciesEncryptionOutput> {
// underlying library requires Buffer as input
const publicKey = Buffer.from(publicKeyBytes);
const plaintextBuffer = Buffer.from(plaintext);
Expand All @@ -96,7 +144,7 @@ export class Encryption {
ciphertext,
ephemeralPublicKey,
initializationVector,
messageAuthenticationCode
messageAuthenticationCode,
};
}

Expand All @@ -105,14 +153,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<Uint8Array> {
public static async eciesSecp256k1Decrypt(
input: EciesEncryptionInput
): Promise<Uint8Array> {
// 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);
Expand All @@ -123,7 +173,7 @@ export class Encryption {
/**
* Expose eciesjs library configuration
*/
static get isEphemeralKeyCompressed():boolean {
static get isEphemeralKeyCompressed(): boolean {
return eciesjs.ECIES_CONFIG.isEphemeralKeyCompressed;
}
}
Expand All @@ -141,5 +191,5 @@ export type EciesEncryptionInput = EciesEncryptionOutput & {

export enum EncryptionAlgorithm {
Aes256Ctr = 'A256CTR',
EciesSecp256k1 = 'ECIES-ES256K'
}
EciesSecp256k1 = 'ECIES-ES256K',
}
3 changes: 1 addition & 2 deletions src/utils/hd-key.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand Down