JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 51
  • Score
    100M100P100Q67253F
  • License MIT

Node.js Cipher cryptograph utility library

Package Exports

    Readme

    Crypto Cipher 🔐

    NPM Latest Version Coverage Status NPM Monthly Downloads Dependencies

    Node.js Cipher cryptograph utility library

    Table of Contents


    Getting started

    The crypto-cipher library provides AES encryption and decryption functionality, supporting both in-memory buffers and streams. It adheres to the NIST SP 800-38D standard recommendations.

    ⚠️ Note that every performed operation cannot be accomplished client-side and must be executed on a back-end server.

    It is part of the crypto utility libraries and can be installed by running the following command:

    npm i @alessiofrittoli/crypto-cipher

    or using pnpm

    pnpm i @alessiofrittoli/crypto-cipher

    Key features

    • Supports multiple AES algorithms (CCM, GCM, OCB, CBC) and even chacha20-poly1305.
    • In-memory buffer encryption and decrpytion.
    • Robust support for encrypting and decrypting streams (in-memory and file based), with seamless handling of key/IV extraction.
    • Hybrid encryption methods for combining symmetric and asymmetric cryptography.

    Options Management

    • A solid options resolver mechanism ensures consistent handling of defaults and constraints.

    Security Considerations

    • Random salt and IV generation.
    • Authenticated encryption modes with proper authTag and Additional Authenticated Data handling.

    Readable and Modular

    • Separation of concerns with clear method responsibilities.
    • Comprehensive JSDoc comments enhance maintainability and readability.

    API Reference

    Constants

    Cipher.SALT_LENGTH

    Defines the minimum, maximum, and default lengths for salt.

    Properties
    Property Value
    min 16
    max 64
    default 32

    Cipher.IV_LENGTH

    Defines the minimum, maximum, and default lengths for initialization vectors (IV).

    Properties
    Property Value
    min 8
    max 32
    default 16

    Cipher.AUTH_TAG_LENGTH

    Defines the minimum, maximum, and default lengths for authentication tags.

    Properties
    Property Value
    min 4
    max 16
    default 16

    Cipher.AAD_LENGTH

    Defines the minimum, maximum, and default lengths for additional authenticated data (AAD).

    Properties
    Property Value
    min 16
    max 4096
    default 32

    Cipher.DEFAULT_ALGORITHM

    Specifies default AES algorithms for buffer and stream operations.

    Properties
    Operation Algorithm Description
    buffer aes-256-gcm Default algorithm used for buffer data encryption/decryption
    stream aes-256-cbc Default algorithm used for stream encryption/decryption

    Cipher.ALGORITHMS

    Supported AES algorithms:

    Properties
    • aes-128-gcm
    • aes-192-gcm
    • aes-256-gcm
    • aes-128-ccm
    • aes-192-ccm
    • aes-256-ccm
    • aes-128-ocb
    • aes-192-ocb
    • aes-256-ocb
    • aes-128-cbc
    • aes-192-cbc
    • aes-256-cbc
    • chacha20-poly1305

    Methods

    Cipher.encrypt()

    Encrypts an in-memory data buffer.

    ⚠️ This is not suitable for large data. Use Cipher.streamEncrypt() or Cipher.hybridEncrypt() methods for large data encryption.

    Parameters
    Name Type Description
    data CoerceToUint8ArrayInput Data to encrypt.
    secret CoerceToUint8ArrayInput Secret key for encryption.
    options Cph.Options (Optional) Additional encryption options.

    Returns

    Type: Buffer

    • The encrypted result buffer.


    Cipher.decrypt()

    Decrypts an in-memory data buffer.

    ⚠️ This is not suitable for large data. Use Cipher.streamDecrypt() or Cipher.hybridDecrypt() methods for large data decryption.

    Parameters
    Name Type Description
    data CoerceToUint8ArrayInput Data to decrypt.
    secret CoerceToUint8ArrayInput Secret key for decryption.
    options Cph.Options (Optional) Decryption options (must match encryption).

    Returns

    Type: Buffer

    • The decrypted result buffer.


    Cipher.streamEncrypt()

    Encrypts a Readable stream to a Writable stream.

    Parameters
    Name Type Description
    secret CoerceToUint8ArrayInput Secret key for encryption.
    options Cph.Stream.Symmetric.EncryptOptions Stream encryption options.

    Returns

    Type: Cph.Stream.Symmetric.EncryptReturnType

    • An object containing:
      • a new instance of crypto.Cipher allowing you to add listeners to the cipher encryption process.
      • the actual encrypt callback that must be called and awaited in order to start the encryption process.


    Cipher.streamDecrypt()

    Decrypts a Readable stream to a Writable stream.

    Parameters
    Name Type Description
    secret CoerceToUint8ArrayInput Secret key for decryption.
    options Cph.Stream.Symmetric.DecryptOptions Stream decryption options.

    Returns

    Type: Promise<Cph.Stream.Symmetric.DecryptReturnType>

    • A new Promise that resolves when Key IV extraction completes returning an object containing:
      • a new instance of crypto.Decipher allowing you to add listeners to the decipher decryption process.
      • the actual decrypt callback that must be called and awaited in order to start the decryption process.


    Cipher.hybridEncrypt()

    Encrypts a stream using hybrid encryption (symmetric + RSA).

    Parameters
    Name Type Description
    secret CoerceToUint8ArrayInput Symmetric secret key.
    publicKey crypto.RsaPublicKey | crypto.KeyLike RSA public key used to encrypt the generated symmetric key.
    options Cph.Stream.Hybrid.EncryptOptions Stream encryption options.

    Returns

    Type: Cph.Stream.Hybrid.EncryptReturnType

    • An object containing:
      • a new instance of cipher allowing you to add listeners to the cipher encryption process.
      • the actual encrypt callback that must be called and awaited to start the encryption process.


    Cipher.hybridDecrypt()

    Decrypts a stream using hybrid decryption (symmetric + RSA).

    Parameters
    Name Type Description
    privateKey crypto.RsaPrivateKey | crypto.KeyLike RSA private key for used to decrpyt the encrypted symmetric key.
    options Cph.Stream.Hybrid.DecryptOptions Stream decryption options.

    Returns

    Type: Promise<Cph.Stream.Hybrid.DecryptReturnType>

    • A new Promise that resolves when Key IV extraction completes returning an object containing:
      • a new instance of crypto.Decipher allowing you to add listeners to the decipher decryption process.
      • the actual decrypt callback that must be called and awaited in order to start the decryption process.


    Types

    CoerceToUint8ArrayInput

    This module supports different input data types and it uses the coerceToUint8Array utility function from @alessiofrittoli/crypto-buffer to convert it to a Uint8Array.


    Cph.CBCTypes

    Cipher CBC algorithm types.


    Cph.AesAlgorithm

    Supported AES algorithm types.


    Cph.Options<T>

    Common options in encryption/decryption processes.

    Type parameters
    Parameter Default Description
    T Cph.AesAlgorithm Accepted algorithm in Cph.Options. This is usefull to constraint specifc algorithms.

    Properties
    Property Type Default Description
    algorithm T aes-256-gcm | aes-256-cbc Accepted algorithms.
    salt number 32 The salt length in bytes. Minimum: 16, Maximum: 64.
    iv number 16 The Initialization Vector length in bytes. Minimum: 8, Maximum: 32.
    authTag number 16 The authTag length in bytes. Minimum: 4, Maximum: 16.
    aad CoerceToUint8ArrayInput - Custom Additional Authenticated Data. aadLength is then automatically resolved. If not provided, a random AAD is generated with a max length of aadLength.
    aadLength number 32 The auto generated AAD length in bytes. Minimum: 16, Maximum: 128.

    Cph.Stream.Symmetric.EncryptOptions

    Stream symmetric encryption options.

    Properties
    Property Type Description
    input Readable The Readable Stream from where raw data to encrypt is read.
    output Writable The Writable Stream where encrypted data is written.

    Cph.Stream.Symmetric.EncryptReturnType

    Returnign object from Cipher.streamEncrypt() method.

    Properties
    Property Type Description
    cipher crypto.Cipher The crypto.Cipher instance.
    encrypt () => Promise<void> The actual encrypt callback that must be called and awaited in order to start the encryption process.

    Cph.Stream.Symmetric.DecryptOptions

    Stream symmetric decryption options.

    Properties
    Property Type Description
    input Readable The Readable Stream from where encrypted data is read.
    output Writable The Writable Stream where decrypted data is written.

    Cph.Stream.Symmetric.DecryptReturnType

    Returnign object from awaited Cipher.streamDecrypt() method.

    Properties
    Property Type Description
    decipher crypto.Decipher The crypto.Decipher instance.
    decrypt () => Promise<void> The actual decrypt callback that must be called and awaited in order to start the decryption process.

    Cph.Stream.Hybrid.EncryptOptions

    Stream hybrid encryption options.


    Cph.Stream.Hybrid.EncryptReturnType

    Returnign object from Cipher.hybridEncrypt() method.


    Cph.Stream.Hybrid.DecryptOptions

    Stream hybrid decryption options.

    Properties
    Property Type Description
    rsaKeyLength number The RSA key length in bytes used while encrypting data. This is used to properly extract the encrypted Cipher Key and Initialization Vector from the encrypted data.

    Cph.Stream.Hybrid.DecryptReturnType

    Returnign object from awaited Cipher.hybridDecrypt() method.


    Examples

    Importing the library

    import { Cipher } from '@alessiofrittoli/crypto-cipher'
    import type { Cph as CipherTypes } from '@alessiofrittoli/crypto-cipher/types'

    In-memory data buffer encryption/decryption

    The simpliest way to encrypt/decrypt in-memory data buffers.

    // encrypt
    const data      = 'my top-secret data'
    const password  = 'my-very-strong-password'
    
    const encrypted = Cipher.encrypt( data, password )
    
    // decrypt
    const decrypted = Cipher.decrypt( encrypted, password )
    console.log( decrypted ) // Outputs: my top-secret data

    In-memory data stream encryption/decryption

    The in-memory data stream comes pretty handy when, for example, we need to stream encrypted data within a Server Response or to decrypt stream data from a Server Response.

    Streaming
    // /api/stream-encrypt
    
    import { Readable, Writable } from 'stream'
    
    const routeHandler = () => {
      const data      = 'my top-secret data'
      const password  = 'my-very-strong-password'
      const stream    = new TransformStream()
      const writer    = stream.writable.getWriter()
      
      // Create a `Readable` Stream with raw data.
      const input = new Readable( {
        read()
        {
          this.push( data )
          this.push( null ) // Signal end of stream
        },
      } )
          
      // `Writable` Stream where encrypted data is written
      const output = new Writable( {
        write( chunk, encoding, callback )
        {
          writer.write( chunk )
          callback()
        },
        final( callback )
        {
          writer.close()
          callback()
        }
      } )
      
      Cipher.streamEncrypt( password, { input, output } )
        .encrypt()
      
      return (
        // encrypted stream
        new Response( stream.readable )
      )
    }
    Decrypting received stream
    // /api/stream-decrypt
    
    import { Transform, Writable } from 'stream'
    import { StreamReader } from '@alessiofrittoli/stream-reader'
    
    const password = 'my-very-strong-password'
    
    const routeHandler = () => (
      fetch( '/api/stream-encrypt' )
        .then( response => {
    
          if ( ! response.body ) {
            return (
              new Respone( null, { status: 400 } )
            )
          }
    
          const stream  = new TransformStream()
          const writer  = stream.writable.getWriter()
          const reader  = new StreamReader( response.body )
          const input   = new Transform()
          
          reader.read()
          
    
          reader.on( 'read', chunk => {
            input.push( chunk )
          } )
          reader.on( 'close', () => {
            input.push( null )
          } )
    
          const output = new Writable( {
            write( chunk, encoding, callback )
            {
              writer.write( chunk )
              callback()
            },
            final( callback ) {
              writer.close()
              callback()
            },
          } )
          
          const { decrypt } = await Cipher.streamDecrypt( password, { input, output } )
    
          decrypt()
          
          return (
            // decrypted stream
            new Response( stream.readable )
          )
          
        } )
    )

    In-memory data stream with hybrid encryption/decryption

    Hybrid encryption offers an higher level of security by encrypting the generated symmetric key with asymmetric RSA keys.

    Keypair
    const password  = 'my-very-strong-password'
    
    /** RSA modulus length is required for proper key extraction during decryption process. */
    const rsaKeyLength = 512 // bytes
    const keyPair = crypto.generateKeyPairSync( 'rsa', {
      modulusLength       : rsaKeyLength * 8, // 4096 bits
      publicKeyEncoding   : { type: 'spki', format: 'pem' },
      privateKeyEncoding  : { type: 'pkcs1', format: 'pem' },
    } )
    
    
    // or you can optionally set a custom passphrase
    const keyPair = crypto.generateKeyPairSync( 'rsa', {
      modulusLength       : rsaKeyLength * 8, // 4096 bits
      publicKeyEncoding   : { type: 'spki', format: 'pem' },
      privateKeyEncoding  : { type: 'pkcs1', format: 'pem', passphrase: password, cipher: 'aes-256-cbc' },
    } )
    Encrypt
    const data = 'my top-secret data'
    /** Store encrypted chunks for next example. */
    const encryptedChunks: Buffer[] = []
    
    // Create a `Readable` Stream with raw data.
    const input = new Readable( {
      read()
      {
        this.push( data ) // Push data to encrypt
        this.push( null ) // Signal end of stream
      },
    } )
        
    // Create a `Writable` Stream where encrypted data is written
    const output = new Writable( {
      write( chunk, encoding, callback )
      {
        // push written chunk to `encryptedChunks` for further usage.
        encryptedChunks.push( chunk )
        callback()
      }
    } )
    
    const { encrypt } = Cipher.hybridEncrypt( password, {
      key       : keyPair.publicKey,
      padding   : crypto.constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash  : 'SHA-256',
    }, { input, output } )
    
    await encrypt()

    Decrypt
    /** Store decrypted chunks. */
    const chunks: Buffer[] = []
    // Create a `Readable` Stream with encrypted data.
    const input = new Readable( {
      read()
      {
        this.push( Buffer.concat( encryptedChunks ) ) // Push data to decrypt
        this.push( null ) // Signal end of stream
      },
    } )
    
    // Create a `Writable` Stream where decrypted data is written
    const output = new Writable( {
      write( chunk, encoding, callback )
      {
        chunks.push( chunk )
        callback()
      },
    } )
    
    const { decrypt } = await Cipher.hybridDecrypt(
      {
        key       : keyPair.privateKey,
        passphrase: password, // optional passhrase (required if set while generating keypair).
        padding   : crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash  : 'SHA-256',
      }, { input, output, rsaKeyLength }
    )
    
    await decrypt()
    
    console.log( Buffer.concat( chunks ).toString() ) // Outputs: 'my top-secret data'

    File based data stream encryption/decryption

    Nothig differs from the In-memory data stream encryption/decryption example, except for input and output streams which now comes directly from files reading/writing.

    Encrypt a file
    import fs from 'fs'
    
    const password = 'my-very-strong-password'
    
    // input where raw data to encrypt is read
    const input = fs.createReadStream( 'my-very-large-top-secret-file.pdf' )
    // output where encrypted data is written
    const output = fs.createWriteStream( 'my-very-large-top-secret-file.encrypted' )
    // encrypt
    await Cipher.streamEncrypt( password, { input, output } )
      .encrypt()

    Decrypt a file
    import fs from 'fs'
    
    const password = 'my-very-strong-password'
    
    // input where encrypted data is read
    const input = fs.createReadStream( 'my-very-large-top-secret-file.encrypted' )
    // output where decrypted data is written
    const output = fs.createWriteStream( 'my-very-large-top-secret-file-decrypted.pdf' )
    // decrypt
    const { decrypt } = await Cipher.streamDecrypt( password, { input, output } )
    await decrypt()

    File based data stream with hybrid encryption/decryption

    Nothig differs from the In-memory data stream with hybrid encryption/decryption example, except for input and output streams which now comes directly from files reading/writing.

    Keypair
    const password  = 'my-very-strong-password'
    
    /** RSA modulus length is required for proper key extraction during decryption process. */
    const rsaKeyLength = 512 // bytes
    const keyPair = crypto.generateKeyPairSync( 'rsa', {
      modulusLength       : rsaKeyLength * 8, // 4096 bits
      publicKeyEncoding   : { type: 'spki', format: 'pem' },
      privateKeyEncoding  : { type: 'pkcs1', format: 'pem' },
    } )
    
    
    // or you can optionally set a custom passphrase
    const keyPair = crypto.generateKeyPairSync( 'rsa', {
      modulusLength       : rsaKeyLength * 8, // 4096 bits
      publicKeyEncoding   : { type: 'spki', format: 'pem' },
      privateKeyEncoding  : { type: 'pkcs1', format: 'pem', passphrase: password, cipher: 'aes-256-cbc' },
    } )
    Encrypt a file
    import fs from 'fs'
    
    const password = 'my-very-strong-password'
    
    // input where raw data to encrypt is read
    const input = fs.createReadStream( 'my-very-large-top-secret-file.pdf' )
    // output where encrypted data is written
    const output = fs.createWriteStream( 'my-very-large-top-secret-file.encrypted' )
    // encrypt
    const { encrypt } = Cipher.hybridEncrypt( password, {
      key       : keyPair.publicKey,
      padding   : crypto.constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash  : 'SHA-256',
    }, { input, output } )
    await encrypt()

    Decrypt a file
    import fs from 'fs'
    
    const password = 'my-very-strong-password'
    
    // input where encrypted data is read
    const input = fs.createReadStream( 'my-very-large-top-secret-file.encrypted' )
    // output where decrypted data is written
    const output = fs.createWriteStream( 'my-very-large-top-secret-file-decrypted.pdf' )
    // decrypt
    const { decrypt } = await Cipher.hybridDecrypt(
      {
        key       : keyPair.privateKey,
        passphrase: password, // optional passhrase (required if set while generating keypair).
        padding   : crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash  : 'SHA-256',
      }, { input, output, rsaKeyLength }
    )

    Development

    Install depenendencies

    npm install

    or using pnpm

    pnpm i

    Build the source code

    Run the following command to test and build code for distribution.

    pnpm build

    ESLint

    warnings / errors check.

    pnpm lint

    Jest

    Run all the defined test suites by running the following:

    # Run tests and watch file changes.
    pnpm test:watch
    
    # Run tests in a CI environment.
    pnpm test:ci

    You can eventually run specific suits like so:

    pnpm test:buffer-in-memory
    pnpm test:file-symmetric
    pnpm test:file-hybrid
    pnpm test:stream-symmetric
    pnpm test:stream-hybrid
    pnpm test:misc

    Run tests with coverage.

    An HTTP server is then started to serve coverage files from ./coverage folder.

    ⚠️ You may see a blank page the first time you run this command. Simply refresh the browser to see the updates.

    test:coverage:serve

    Contributing

    Contributions are truly welcome!
    Please refer to the Contributing Doc for more information on how to start contributing to this project.


    Security

    If you believe you have found a security vulnerability, we encourage you to responsibly disclose this and NOT open a public issue. We will investigate all legitimate reports. Email security@alessiofrittoli.it to disclose any security vulnerabilities.

    Made with ☕

    avatar
    Alessio Frittoli
    https://alessiofrittoli.it | info@alessiofrittoli.it