npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

encrypted-attr

v1.1.0

Published

Encrypted model attributes in your favourite ORM.

Downloads

64,061

Readme

encrypted-attr

travis   npm   license

Encrypted model attributes in your favourite ORM.

Security model

  • AES-256-GCM:
    • 96-bit random nonce
    • 128-bit authentication tag
  • Additional authenticated data:
    • Key id: use different keys for different attributes (or different users), rotate keys for new data over time without re-encrypting old data
    • Object id: prevent substitution of encrypted values

All keys should be 32 bytes long, and cryptographically random. Manage these keys as you would any other sensitive credentials (environment config, vault, keychain). You can generate random keys with this snippet:

node -p "require('crypto').randomBytes(32).toString('base64')"

Refer to NIST Special Publication 800-38D for additional recommendations. In particular, you should pay attention to uniqueness requirements for keys and IVs, and constraints on the number of invocations with a given key (Section 8). These should inform key rotation policies.

Threat model

This is designed to protect you from leaking sensitive user data under very specific scenarios:

  • Full data dump
    • Misplaced unencrypted backups
    • Compromised database host
  • Partial data dump
    • Query injection via unsanitized input

Specifically, this does not provide any protection in cases of a compromised app host, app-level vulnerabilities, or accidentally leaking sensitive data into logs. It is also not a substitute for actually encrypting your backups, sanitizing your input, et cetera.

Install

This module requires at least node v4.0.

npm install encrypted-attr

Use

This module can be used stand-alone to encrypt individual values, or wrapped into a plugin or hook to your favourite ORM.

Here is a quick example to get started:

const EncryptedAttributes = require('encrypted-attr')

let encryptedAttributes = EncryptedAttributes(null, {
  keys: {
    k1: crypto.randomBytes(32).toString('base64') // use a persistent key
  },
  keyId: 'k1'
})

let secretNumber = '555-55-5555'
let encryptedNumber = encryptedAttributes.encryptAttribute(null, secretNumber)
// YWVzLTI1Ni1nY20kJGsx$r2JF/XHvpsgNwJDs$c/P6GwnUZGokEjQ=$sEMv0cyKPBL90mo2zZ1MpQ

EncryptedAttributes(attributes, options)

Construct an instance to handle encryption and decryption. You should construct for each unique set of attributes and keys that you want to encrypt.

| Parameter | Type | Required? | Details | | :----------- | :--------------- | :--------- | :---------------------------- | | attributes | array of strings | Optional | List attributes which should be encrypted. These can be specified as top-level string keys, or nested paths using dot-separated notation. This list is used by encryptAll/decryptAll, and is also useful for creating helper methods on your models. | | options | dictionary | Optional | See below. |

These options are supported:

| Option | Type | Required? | Details | | :----------- | :--------------- | :--------- | :---------------------------- | | keys | dictionary | Required | Dictionary of all relevant data encryption keys, as base64 strings. Since encrypted strings embed the key id that was used to encrypt them, it's important that keys contains the appropriate key for any previously encrypted data you might run across. | | keyId | string | Required | The id of the key to use for all new encryptions. This is not necessarily the only key that will be used for decryptions, because the key id gets embedded into the encrypted string. When that string is decrypted, this module unpacks that key id and uses it to determine the appropriate decryption key. This approach allows multiple keys to be used for the same attribute. (Note that this option is only technically required if you need to encrypt new data. If you are only decrypting existing data, you do not need to provide it.) | | verifyId | string | Optional | The property name to use as the primary id for objects. If not set, object id will not be included during encryption, nor verified during decryption. If set to a truthy value that isn't a string, "id" will be used instead. |

If the verifyId option is specified, the value of that property on the source object passed during encryption will be included as part of the authenticated metadata; during decryption, this value is expected to match the value of the same property on the object passed during decryption, otherwise an exception is thrown.

encryptAttribute(sourceObject, plaintextString)

Encrypt a value using one of your keys (specifically, the key indicated by the keyId option specified in the constructor).

Returns the encrypted representation of the value. Does nothing if the provided value is already encrypted using this module (so this method is idempotent).

let encryptedString = encryptAttributes.encryptAttribute(sourceObject, plaintextString)

sourceObject is optional, and only relevant if the verifyId option is used; otherwise you may pass null or undefined.

encryptAll(sourceObject)

Encrypt a subset of fields in the provided plain object. The set of fields to be encrypted is specified by the array of attribute paths supplied to the EncryptedAttributes constructor.

Returns the source object with any to-by-encrypted fields replaced by their encrypted representation. Note that this method modifies the provided object.

let partiallyEncryptedObject = encryptedAttributes.encryptAll(sourceObject)

decryptAttribute(sourceObject, encryptedString)

Decrypt a value.

Returns the plaintext string. Does nothing if the provided value does not look like it was encrypted using this module (so this method is idempotent).

let plaintextString = encryptedAttributes.decryptAttribute(sourceObject, encryptedString)

sourceObject is optional, and only relevant if the verifyId option is used; otherwise you may pass null or undefined.

decryptAll(sourceObject)

Decrypt a subset of fields in the provided plain object. The set of fields to be decrypted is specified by the array of attribute paths supplied to the

Returns the source object with any encrypted fields replaced by their plaintext value. Note that this method modifies the provided object.

let decryptedObject = encryptedAttributes.decryptAll(partiallyEncryptedObject)

Use with an ORM

While this module can be used stand-alone, it is designed to be wrapped into a plugin or hook to your favourite ORM. Eventually, this package may include such plugins for common ORMs, but for now, here's an example using thinky:

const EncryptedAttributes = require('encrypted-attr')
const thinky = require('thinky')()
const _ = require('lodash')

let Model = thinky.createModel('Model', {})

let encryptedAttributes = EncryptedAttributes(['secret', 'nested.secret'], {
  keys: {
    k1: crypto.randomBytes(32).toString('base64') // use an actual key here
  },
  keyId: 'k1',
  verifyId: 'id'
})

// Pre-save hook: encrypt model attributes that need to be encrypted.
Model.docOn('saving', function (doc) {
  encryptedAttributes.encryptAll(doc)
})

// Post-save hook: decrypt model attributes that need to be decrypted.
Model.docOn('saved', function (doc) {
  encryptedAttributes.decryptAll(doc)
})

// Post-retrieve hook: ditto.
Model.on('retrieved', function (doc) {
  encryptedAttributes.decryptAll(doc)
})

// Optionally, add some helpers in case we need to set or read the value
// directly (such as an update query), without going through model hooks.
for (let attr of encryptedAttributes.attributes) {
  Model.define(_.camelCase(`encrypt ${attr}`), function (val) {
    return encryptedAttributes.encryptAttribute(this, val)
  })
  Model.define(_.camelCase(`decrypt ${attr}`), function (val) {
    return encryptedAttributes.decryptAttribute(this, val)
  })
}

And a usage example:

async function storeSomeSecrets (doc) {
  await doc.merge({
    secret: 'red',
    nested: {
      hint: 'colour',
      secret: 'yellow'
    }
  }).save()

  console.log(await Model.get(doc.id))
  // {
  //   id: '543bed92-e241-4151-9d8f-1aa942c36d24',
  //   nested: {
  //     hint: 'colour',
  //     secret: 'yellow'
  //   },
  //   secret: 'red'
  // }

  console.log(await Model.get(doc.id).execute())
  // {
  //   id: '543bed92-e241-4151-9d8f-1aa942c36d24',
  //   nested: {
  //     hint: 'colour',
  //     secret: 'YWVzLTI1Ni1nY20k...$NFvcFAaPTDay3uWH$t3G9Jrpy$g+BJT/UvfuboMB3ARiDRIQ'
  //   },
  //   secret: 'YWVzLTI1Ni1nY20k...$6UdqWqv9ox305Wmt$zyNF$S5BOgZSvMG3H24foFaTQjg'
  // }
}

License

MIT