Quick notes on cryptography for js devs
Tags: javascript, cryptography, api
Published: 2022-12-20 10:29:51

## Principles of Encryption API Selection

- The standard library first.
- Prefer well-known and audited libraries.
- Before choosing a third-party library, do some investigation first.

## Basics

- Encode / Decode: `Buffer` Class.
- Random numbers
    - The basis of cryptography.
    - It is extremely difficult to obtain true random numbers, commonly, pseudo-random numbers are used.
    - Don't use `Math.random`!
    - Use the `random` functions in the `crypto` module, e.g. `crypto.randomBytes`.
- Hash
    - Irreversible.
    - The output is a fixed-length string.
    - Same input, same output.
    - Different inputs, different outputs.
    - Typical applications
        - Data integrity verification
        - Passphrase hashing
        - Derived Keys
- Key
    - Derived Key: A key generated from a master key.
    - Key Encapsulation: encrypt and export the key.
    - Key Decapsulation: decrypts and imports the key
- Authenticated Encryption: encryption and tamper-proof
- Symmetric Encryption
    - Use the same key for encryption and decryption
    - Fast
    - Suitable for large data blocks
    - Key exchange is difficult
- Asymmetric encryption
    - Public key + Private key
        - Encryption: public key encryption, private key decryption
        - Signing: private key encryption, public key decryption
    - Slow
    - Not suitable for large data blocks
    - Typical applications
        - Key exchange
        - Digital certificates
        - Digital signatures
- Hybrid encryption
    - Combination of the above two
        - Asymmetric encryption for key exchange
        - Symmetric encryption for data manipulation
- Encryption modes
    - Block encryption, grouping before encryption.
        - **Never use ECB mode.**
    - Stream encryption, dealing with continuous or unknown length byte streams, advantageous when the plaintext is small.

## Hash

- Don't use:
    - MD5
    - SHA-1
    - PBKDF2
    - bcrypt
- Message digest: SHA-256
    - `crypto.createHash('sha256')`
    - Use stream mode for large data
    - Do not use for sensitive data such as passphrases
- Message Authentication Code (HMAC)
    - hash and key (key to be known by both parties)
    - `crypto.createHmac('sha256', 'secret')`
- Password hashing and KDF (Key Derivation Function)
    - Features.
        - salt built-in
        - Deliberated slow algorithms to offset the computational power boost caused by newer hardware.
    - argon2 (recommended)
        - argon2i, resistant to side-channel attacks
        - argon2d, resistant to GPU attacks
        - argon2id (recommended), combination of the above
        - https://www.npmjs.com/package/argon2
    - scrypt
        - `crypto.scrypt`

## Symmetric Encryption

- Don't use:
    - DES, including 3DES.
    - Blowfish
        - TWofish is its successor, but not popular.
    - AES-ECB
        - No ECB mode, please.
- AES
    - Common modes.
        - AES-CBC, when authentication is not required
            - `crypto.createCipheriv('aes-256-cbc', ...)`
            - `crypto.createDecipheriv('aes-256-cbc', ...)`
        - AES-GCM, when authentication is required
            - ` crypto.createCipheriv('aes-256-gcm', ...)`
            - ` crypto.createDecipheriv('aes-256-gcm', ...)`
    - For big data use stream mode
- ChaCha20-Poly1305
    - ChaCha20 encryption + Poly1305 hashing
    - Equivalent to AES-GCM
    - Suitable for environments without AES hardware acceleration support
    - `crypto.createCipheriv('chacha20-poly1305', ...)`
    - `crypto.createDecipheriv('chacha20-poly1305', ...)`

## Asymmetric Encryption

- ASN.1 Data Structure
    - https://www.npmjs.com/package/asn1js
- Key encoding format: PEM format
    - Public key: SPKI
        ```js
        // import
        crypto.createPrivateKey(fs.readFileSync("public.pem"));
        ```
    - Private key.
        - PKCS #1 (old, RSA only)
        - PKCS #8 (new, for multiple algorithms)
        ```js
        // import
        crypto.createPrivateKey(fs.readFileSync("private.pem"));
        ```
    - Export: `crypto.KeyObject.export(option)`
- RSA
    - key
        - `crypto.generateKeyPair('rsa', ...) `
    - encryption and decryption
        - padding: add random data to the plaintext before encryption to improve the security of RSA.
            - Always used if no special reasons.
            - crypto.constants.RSA_PKCS1_OAEP_PADDING (recommended)
            - crypto.constants.RSA_PKCS1_PADDING (not recommended)
        - encryption
            ```js
            crypto.publicEncrypt(
                {
                    key: publicKey,
                    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                    oaepHash: "sha256",
                },
                plaintext
            );
            ```
        - decryption
            ```js
            crypto.privateDecrypt(
                {
                    key: privateKey,
                    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                    oaepHash: "sha256",
                },
                message
            );
            ```
    - Digital signature
        - padding:
            - crypto.constants.RSA_PKCS1_PADDING (default)
            - crypto.constants.RSA_PKCS1_PSS_PADDING (new version, recommended)
        - signing
        ```js
        crypto.sign("sha256", message, {
            key: privateKey,
            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
        });
        ```
        - verification
        ```js
        crypto.verify(
            "sha256",
            message,
            {
                key: publicKey,
                padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
            },
            signature
        );
        ```
- Elliptic Curve
    - Equivalent security to RSA
    - Shorter keys, faster and cheaper
    - Unlike RSA, its key pair does not support asymmetric encryption, i.e.: the public key decrypts the message encrypted by the private key, or vice versa.
    - Commonly used curves
        - P-256: secp256r1 / prime256v1, NIST standard
        - Curve25519
            - X25519 for key exchange
            - Ed25519 for digital signatures
        - secp256k1, used by Bitcoin
    - Typical Applications
        - Key exchange (ECDH)
        - Digital signatures
    - key
        - `crypto.generateKeyPair('ec', ...) `
    - ECDH
        - Unlike RSA (where party A encrypts the key with party B's public key and then sends it to party B to decrypt).
        - DH protocol
            - Alice and Bob each generate a key pair
            - They exchange the public keys
            - Combine their private keys to generate a shared key, i.e.
                - `shareSecret(A public key, B private key) = shareSecret(B public key, A private key)`
        - `crypto.diffieHellman({A public key, B private key})`
        - A reference implementation of shared keys.
            - `crypto.diffieHellman` gets shareSecret
            - Generate a random salt
            - `sha256(shareSecret, salt)` is the shared key, note: remember to pass the salt to the other side with the ciphertext.
    - Digital signatures
        - ECDSA (using prime256v1)
            - `crypto.sign('sha256', message, privateKey)`
            - `crypto.verify('sha256', message, publicKey, signature)`
        - EdDSA (using Ed25519), which does not require an externally configured hash function as it already has a built-in digital signature algorithm.
            - `crypto.sign(null, message, privateKey)`
            - `crypto.verify(null, message, publicKey, signature)`

## Web Crypto

- Used in the browser environment
- Encoding
    - `ArrayBuffer`
    - `atob` and `btoa`
    - `TextEncoder`
- Random numbers.
    - `Window.crypto.getRandomValues`
    - `Window.crypto.randomUUID`
- keys
    - `window.crypto.subtle.generateKey(algorithm, extractable, usages)`
    - `window.crypto.subtle.importKey(format, data, algorithm, extractable, usages)`
    - `window.crypto.subtle.exportKey(format, key)`
    - `window.crypto.subtle.wrapKey`
    - `window.crypto.subtle.unwrapKey`
    - `window.crypto.subtle.deriveKey`
- Hash
    - SHA-256: `window.crypto.subtle.digest('SHA-256', ...) `
    - HMAC: `window.crypto.subtle.sign("HMAC", key, encoded)`
    - Passphrase hash: https://www.npmjs.com/package/hash-wasm
- Encryption and decryption (both symmetric and asymmetric)
    - `crypto.subtle.encrypt(algorithm, key, data)`
    - `crypto.subtle.decrypt(algorithm, key, data)`
- Digital signatures
    - `window.crypto.subtle.sign`
    - `window.crypto.subtle.verify`

## References

- [Packt.Essential.Cryptography.for.JavaScript.Developers](https://www.amazon.com/Essential-Cryptography-JavaScript-Developers-cryptographic/dp/1801075336)
- [Full Stack Python Security: Cryptography, TLS, and attack resistance](https://www.amazon.com/Full-Stack-Python-Security-Cryptography/dp/1617298824)
- [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API)
- [Node Crypto](https://nodejs.org/api/crypto.html)