nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
105.2k stars 28.5k forks source link

Better api for crypto #52131

Open s1nistr4 opened 3 months ago

s1nistr4 commented 3 months ago

What is the problem this feature will solve?

Right now the api for the built in crypto module is a mess and is extremely confusing to use, and also frequently fails to decrypt things resulting in corrupted files. To me it is completely unusable, if there is a way to use it, it is too confusing.

What is the feature you are proposing to solve the problem?

Better api

What alternatives have you considered?

This is merely conceptual but it could work something like this

import { encrypt, decrypt } from "crypto"

const encrypted = encrypt({
    algorithm: "aes-256-cbc",
    buffer: // insert file buffer here, 
    password: "mypassword",
    key: fs.readFileSync("iv")
})

const decrypted = decrypt({
    algorithm: "aes-256-cbc",
    buffer: encrypted, 
    password: "mypassword",
    key: fs.readFileSync("iv")

})

Keeping encrypt and decrypt as similar as possible in terms of usability so you don't have to keep looking up docs in order to find out how to use it.

kwpartist commented 3 months ago

I agree that it is a little messy compared to say the WebCrypto API, however I have not had any failing/corruption issues when using a crypto scheme correctly---and I use it on every authenticated HTTPS request that comes to my API as an additional layer of encryption (using WebCrypto API for client-side in browsers---and they play nice together).

From my own experience it feels like Crypto APIs are designed in a way where you have to have full understanding of the crypto scheme you are using, data entropy concepts, byte arrays and encoding/decoding to use it properly (and this is great from a security standpoint because there are too many things to get wrong that compromises the integrity of the cryptography). For instance, let's take AES-256-GCM. Knowing that IVs have to be used only once (a nonce) per ciphertext and recommended at 12 bytes instead of 16 bytes, knowing that---being an authenticated scheme---that authentication tags are created, attached and sent with the ciphertext to successfully decrypt/decipher. That it is a stream cipher, etc. These kinds of things affect the flow of your code and that flow changes depending what you are doing and what crypto scheme you are trying to use.

So it is a real good thing that these crypto APIs are already in effect, mirrored conceptually, because they require the knowledge of the concepts behind the cryptography schemes to use it well.

For a simple example, to encrypt AES-256-GCM conceptually:

let plaintext; // Would be a byte array of the plain data in raw form
let nonce; // Would be a high entropy random 12 bytes or a 12 byte counter byte array IV (96 bits)
let key; // Would be the key byte array from a key derivation function or high entropy bytes (32 bytes/256 bits)
let additionalData; // Would be a byte array consisting of any additional data to have domain over authentication
let encryptedData; // Will be the ciphertext byte array after encryption
let authTag; // Will be the authentication tag byte array after encryption used to authenticate when decrypting

let cipher = crypto.createCipheriv('aes-256-gcm', key, nonce); // Create the cipher, set algo, key and nonce

cipher.setAAD(additionalData); // Add the additional data to cipher

encryptedData.push(cipher.update(plaintext)); // Encrypted bytes
encryptedData.push(cipher.final()); // Final encrypted bytes and signal completion
Buffer.concat(encryptedData); // Merge

authTag = cipher.getAuthTag(); // Get the authentication tag after encryption

For CBC you can adapt accordingly, the reason I specified GCM is because it adds authentication and integrity checks to the process for better security depending on use (encrypted bytes can't be bit flipped). Play around with it, while learning the ins and outs of the crypto scheme you wish to use and its known vulnerabilities/mitigations.

The decryption example for above:

let decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
decipher.setAuthTag(authTag);
decipher.setAAD(additionalData);
decryptedData.push(decipher.update(encryptedData));
decryptedData.push(decipher.final());

I only explained it in depth like this because I noticed in your example you have the IV data under "key".

Hopefully this provides clarity, best wishes!

aduh95 commented 3 months ago

it is a little messy compared to say the WebCrypto API

Worth noting the WebCrypto is available on all non-EOL release lines (exposed as globalThis.crypto or require('node:crypto').webcrypto)