Encrypting / Decrypting Request

All requests sent to the cNGN API is encrypted using AES encryption for request payloads and Ed25519 decryption for response data.

Ed25519 Example:

import sodium from 'libsodium-wrappers';
import {Buffer} from 'buffer';

export class Ed25519Crypto {

    private static isInitialized = false;

    private static async initialize() {
        if (!this.isInitialized) {
            await sodium.ready;
            this.isInitialized = true;
        }
    }

    private static parseOpenSSHPrivateKey(privateKey: string): Uint8Array {
        const lines = privateKey.split('\n');
        const base64PrivateKey = lines.slice(1, -1).join('');
        const privateKeyBuffer = Buffer.from(base64PrivateKey, 'base64');

        const keyDataStart = privateKeyBuffer.indexOf(Buffer.from([0x00, 0x00, 0x00, 0x40]));
        if (keyDataStart === -1) {
            throw new Error('Unable to find Ed25519 key data');
        }

        return new Uint8Array(privateKeyBuffer.subarray(keyDataStart + 4, keyDataStart + 68));
    }

    public static async decryptWithPrivateKey(ed25519PrivateKey: string, encryptedData: string): Promise<string> {
        await this.initialize();

        try {
            const fullPrivateKey = this.parseOpenSSHPrivateKey(ed25519PrivateKey);
            const curve25519PrivateKey = sodium.crypto_sign_ed25519_sk_to_curve25519(fullPrivateKey);
            const encryptedBuffer = Buffer.from(encryptedData, 'base64');

            const nonce = encryptedBuffer.subarray(0, sodium.crypto_box_NONCEBYTES);
            const ephemeralPublicKey = encryptedBuffer.subarray(-sodium.crypto_box_PUBLICKEYBYTES);
            const ciphertext = encryptedBuffer.subarray(sodium.crypto_box_NONCEBYTES, -sodium.crypto_box_PUBLICKEYBYTES);

            const decrypted = sodium.crypto_box_open_easy(
                ciphertext,
                nonce,
                ephemeralPublicKey,
                curve25519PrivateKey
            );

            return sodium.to_string(decrypted)
        } catch (error) {
            throw new Error(`Failed to decrypt with the provided Ed25519 private key: ${error}`);
        }
    }

}

AES Example:

The EAS Example code is helps to encrypt request data before sending to the cNGN api

import * as crypto from 'crypto';

export type AESEncryptionResponse = {
    iv: string,
    content: string
}

export class AESCrypto {
    private static readonly ALGORITHM = 'aes-256-cbc';
    private static readonly IV_LENGTH = 16;
    private static readonly KEY_LENGTH = 32; // 256 bits

    private static prepareKey(key: string): Buffer {
        // Hash the key to ensure it's always the correct length
        const hash = crypto.createHash('sha256');
        hash.update(key);
        return hash.digest();
    }

    public static encrypt(data: string, key: string): AESEncryptionResponse {
        const iv = crypto.randomBytes(this.IV_LENGTH);
        const keyBuffer = this.prepareKey(key);

        const cipher = crypto.createCipheriv(this.ALGORITHM, keyBuffer, iv);

        let encrypted = cipher.update(data, 'utf8', 'base64');
        encrypted += cipher.final('base64');

        return {
            content: encrypted,
            iv: iv.toString('base64')
        };
    }

    public static decrypt(encryptedData: AESEncryptionResponse, key: string): string {
        const iv = Buffer.from(encryptedData.iv, 'base64');
        const keyBuffer = this.prepareKey(key);

        const decipher = crypto.createDecipheriv(this.ALGORITHM, keyBuffer, iv);

        let decrypted = decipher.update(encryptedData.content, 'base64', 'utf8');
        decrypted += decipher.final('utf8');

        return decrypted;
    }
}

Last updated