{"id":13517222,"url":"https://github.com/fnando/keyring-node","last_synced_at":"2025-04-17T00:51:00.216Z","repository":{"id":48207602,"uuid":"161882617","full_name":"fnando/keyring-node","owner":"fnando","description":"Simple encryption-at-rest with key rotation support for Node.js.","archived":false,"fork":false,"pushed_at":"2021-08-05T00:36:48.000Z","size":296,"stargazers_count":43,"open_issues_count":3,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-10T22:01:44.554Z","etag":null,"topics":["encryption","key-rotation","keyring","nodejs","rotation-encryption"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fnando.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["fnando"],"custom":["https://www.paypal.me/nandovieira/🍕"]}},"created_at":"2018-12-15T07:48:47.000Z","updated_at":"2025-04-04T11:26:36.000Z","dependencies_parsed_at":"2022-09-17T11:11:48.265Z","dependency_job_id":null,"html_url":"https://github.com/fnando/keyring-node","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnando%2Fkeyring-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnando%2Fkeyring-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnando%2Fkeyring-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fnando%2Fkeyring-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fnando","download_url":"https://codeload.github.com/fnando/keyring-node/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249294819,"owners_count":21246005,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["encryption","key-rotation","keyring","nodejs","rotation-encryption"],"created_at":"2024-08-01T05:01:31.260Z","updated_at":"2025-04-17T00:51:00.177Z","avatar_url":"https://github.com/fnando.png","language":"JavaScript","funding_links":["https://github.com/sponsors/fnando","https://www.paypal.me/nandovieira/🍕"],"categories":["JavaScript"],"sub_categories":[],"readme":"# keyring\n\nSimple encryption-at-rest with key rotation support for Node.js.\n\n![keyring: Simple encryption-at-rest with key rotation support for Node.js.](https://raw.githubusercontent.com/fnando/keyring-node/main/keyring.png)\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://travis-ci.org/fnando/keyring-node\"\u003e\u003cimg src=\"https://travis-ci.org/fnando/keyring-node.svg\" alt=\"Travis-CI\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nN.B.: **keyring** is _not_ for encrypting passwords--for that, you should use\nsomething like [bcrypt](https://www.npmjs.com/package/bcrypt). It's meant for\nencrypting sensitive data you will need to access in plain text (e.g. storing\nOAuth token from users). Passwords do not fall in that category.\n\nThis package is completely independent from any storage mechanisms; the goal is\nproviding a few functions that could be easily integrated with any ORM. With\nthat said, this package bundles a small plugin that works with\n[Sequelize](https://sequelizejs.com).\n\n## Installation\n\nAdd package to your `package.json` using Yarn.\n\n```console\nyarn add -E @fnando/keyring\n```\n\n## Usage\n\n### Encryption\n\nBy default, AES-128-CBC is the algorithm used for encryption. This algorithm\nuses 16 bytes keys, but you're required to use a key that's double the size\nbecause half of that keys will be used to generate the HMAC. The first 16 bytes\nwill be used as the encryption key, and the last 16 bytes will be used to\ngenerate the HMAC.\n\nUsing random data base64-encoded is the recommended way. You can easily generate\nkeys by using the following command:\n\n```console\n$ dd if=/dev/urandom bs=32 count=1 2\u003e/dev/null | openssl base64 -A\nqUjOJFgZsZbTICsN0TMkKqUvSgObYxnkHDsazTqE5tM=\n```\n\nInclude the result of this command in the `value` section of the key description\nin the keyring. Half this key is used for encryption, and half for the HMAC.\n\n#### Key size\n\nThe key size depends on the algorithm being used. The key size should be double\nthe size as half of it is used for HMAC computation.\n\n- `aes-128-cbc`: 16 bytes (encryption) + 16 bytes (HMAC).\n- `aes-192-cbc`: 24 bytes (encryption) + 24 bytes (HMAC).\n- `aes-256-cbc`: 32 bytes (encryption) + 32 bytes (HMAC).\n\n#### About the encrypted message\n\nInitialization vectors (IV) should be unpredictable and unique; ideally, they\nwill be cryptographically random. They do not have to be secret: IVs are\ntypically just added to ciphertext messages unencrypted. It may sound\ncontradictory that something has to be unpredictable and unique, but does not\nhave to be secret; it is important to remember that an attacker must not be able\nto predict ahead of time what a given IV will be.\n\nWith that in mind, _keyring_ uses\n`base64(hmac(unencrypted iv + encrypted message) + unencrypted iv + encrypted message)`\nas the final message. If you're planning to migrate from other encryption\nmechanisms or read encrypted values from the database without using _keyring_,\nmake sure you account for this. The HMAC is 32-bytes long and the IV is 16-bytes\nlong.\n\n### Keyring\n\nKeys are managed through a keyring--a short JSON document describing your\nencryption keys. The keyring must be a JSON object mapping numeric ids of the\nkeys to the key values. A keyring must have at least one key. For example:\n\n```json\n{\n  \"1\": \"uDiMcWVNTuz//naQ88sOcN+E40CyBRGzGTT7OkoBS6M=\",\n  \"2\": \"VN8UXRVMNbIh9FWEFVde0q7GUA1SGOie1+FgAKlNYHc=\"\n}\n```\n\nThe `id` is used to track which key encrypted which piece of data; a key with a\nlarger id is assumed to be newer. The value is the actual bytes of the\nencryption key.\n\n### Key Rotation\n\nWith **keyring** you can have multiple encryption keys at once and key rotation\nis fairly straightforward: if you add a key to the _keyring_ with a higher id\nthan any other key, that key will automatically be used for encryption when\nobjects are either created or updated. Any keys that are no longer in use can be\nsafely removed from the _keyring_.\n\nIt's extremely important that you save the keyring id returned by `encrypt()`;\notherwise, you may not be able to decrypt values (you can always decrypt values\nif you still possess _all_ encryption keys).\n\nIf you're using **keyring** to encrypt database columns, it's recommended to use\na separated _keyring_ for each table you're planning to encrypt: this allows an\neasier key rotation in case you need (e.g. key leaking).\n\nN.B.: Keys are hardcoded on these examples, but you shouldn't do it on your code\nbase. You can retrieve _keyring_ from environment variables if you're deploying\nto [Heroku](https://heroku.com) and alike, or deploy a JSON file with your\nconfiguration management software (e.g. Ansible, Puppet, Chef, etc).\n\n### Basic usage of keyring\n\n```js\nimport { keyring } from \"@fnando/keyring\";\n\nconst keys = { 1: \"uDiMcWVNTuz//naQ88sOcN+E40CyBRGzGTT7OkoBS6M=\" };\nconst encryptor = keyring(keys, { digestSalt: \"\u003ccustom salt\u003e\" });\n\n// STEP 1: Encrypt message using latest encryption key.\nconst [encrypted, keyringId, digest] = encryptor.encrypt(\"super secret\");\n\nconsole.log(`🔒 ${encrypted}`);\nconsole.log(`🔑 ${keyringId}`);\nconsole.log(`🔎 ${digest}`);\n//=\u003e 🔒 Vco48O95YC4jqj44MheY8zFO2NLMPp/KILiUGbKxHvAwLd2/AN+zUG650CJzogttqnF1cGMFb//Idg4+bXoRMQ==\n//=\u003e 🔑 1\n//=\u003e 🔎 e24fe0dea7f9abe8cbb192702578715079689a3e\n\n// STEP 2: Decrypted message using encryption key defined by keyring id.\nconst decrypted = encryptor.decrypt(encrypted, keyringId);\nconsole.log(`✉️ ${decrypted}`);\n//=\u003e ✉️ super secret\n```\n\n#### Change encryption algorithm\n\nYou can choose between `AES-128-CBC`, `AES-192-CBC` and `AES-256-CBC`. By\ndefault, `AES-128-CBC` will be used.\n\nTo specify the encryption algorithm, set the `encryption` option. The following\nexample uses `AES-256-CBC`.\n\n```js\nimport { keyring } from \"@fnando/keyring\";\n\nconst keys = { 1: \"uDiMcWVNTuz//naQ88sOcN+E40CyBRGzGTT7OkoBS6M=\" };\nconst encryptor = keyring(keys, {\n  encryption: \"aes-256-cbc\",\n  digestSalt: \"\u003ccustom salt\u003e\",\n});\n```\n\n### Using with Sequelize\n\nIf you're using Sequelize, you probably don't want to manually handle the\nencryption as above. With that in mind, `keyring` ships with a small plugin that\neases all the pain of manually handling encryption/decryption of properties, as\nwell as key rotation and digesting.\n\nFirst, you have to load a different file that set ups models.\n\n````js\nconst Sequelize = require(\"sequelize\");\nconst sequelize = new Sequelize(\"postgres:///test\", { logging: false });\nconst Keyring = require(\"@fnando/keyring/sequelize\");\n\nconst User = await sequelize.define(\n  \"users\",\n  {\n    id: {\n      type: Sequelize.UUIDV4,\n      primaryKey: true,\n      allowNull: false,\n      defaultValue: Sequelize.UUIDV4,\n    },\n\n    // This column is required and will store the\n    // keyring id (which encryption key was used).\n    keyring_id: Sequelize.INTEGER,\n\n    // All encrypted columns must be prefixed with `encrypted_`.\n    // Optionally, you may have a `\u003cattribute\u003e_digest` column,\n    // will store a SHA1 digest from the value, making\n    // unique indexing and searching easier.\n    // Finally, notice that you're responsible for defining\n    // a VIRTUAL property for all columns you're encrypting.\n    encrypted_email: Sequelize.TEXT,\n    email_digest: Sequelize.TEXT,\n    email: Sequelize.VIRTUAL,\n  },\n  { timestamps: false },\n);\n\n// Retrieve encryption keys from `USER_KEYRING` environment variable.\n// It's recommended that you use one keyring for each model, to make\n// a rollout easier (e.g. an encryption key leaked).\n//\n// ```js\n// const keys = JSON.parse(process.env.USER_KEYRING);\n// ```\n//\n// Alternatively, you can load a JSON file deployed by some config management software like Ansible, Chef or Puppet.\n//\n// ```js\n// const fs = require(\"fs\");\n// const keys = JSON.parse(fs.readFileSync(\"user_keyring.json\"));\n// ```\n//\n// For the purposes of this example, we're going to set keys manually.\n// WARNING: DON'T EVER DO THAT FOR REAL APPS.\nconst keys = { 1: \"uDiMcWVNTuz//naQ88sOcN+E40CyBRGzGTT7OkoBS6M=\" };\n\n// This is the step you set up your model with hooks to encrypt/decrypt\n// columns. You can specify the encryption keys, which columns are going\n// to be encrypted, how the column will be encrypted and the name of the\n// keyring id column. You can see below the default values for `encryption`\n// and keyring id column.\nKeyring(User, {\n  keys, // [required]\n  columns: [\"email\"], // [required]\n  digestSalt: \"\u003ccustom salt\u003e\", // [required]\n  keyringIdColumn: \"keyring_id\", // [optional]\n  encryption: \"aes-128-cbc\", // [optional]\n});\n\n(async () =\u003e {\n  // Now you can create records, like you usually do.\n  const user = await User.create({ email: \"john@example.com\" });\n\n  console.log(JSON.stringify(user, null, 2));\n\n  // Let's update the email address.\n  await user.update({ email: \"john.doe@example.com\" });\n\n  console.log(JSON.stringify(user, null, 2));\n\n  // Now let's pretend that you set USER_KEYRING env var to a\n  // {1: old_key, 2: new_key} or rollout a new JSON file via your\n  // config management software, and restarted the app.\n  keys[2] = \"VN8UXRVMNbIh9FWEFVde0q7GUA1SGOie1+FgAKlNYHc=\";\n\n  // To simply roll out a new encryption, just call `.save()`.\n  // This will trigger a `beforeSave` hook, which will re-encrypt\n  // all properties again.\n  await user.save();\n\n  console.log(JSON.stringify(user, null, 2));\n\n  // Attributes are also re-encrypted when you call `.update()`.\n  await user.update({ email: \"john@example.com\" });\n\n  console.log(JSON.stringify(user, null, 2));\n})();\n````\n\n#### Lookup\n\nOne tricky aspect of encryption is looking up records by known secret. To solve\nthis problem, you can generate SHA1 digests for strings that are encrypted and\nsave them to the database.\n\n`keyring` detects the presence of `\u003cattribute\u003e_digest` columns and update them\naccordingly with a SHA1 digest that can be used for unique indexing or\nsearching. You don't have to use a hashing salt, but it's highly recommended;\nthis way you can avoid leaking your users' info via rainbow tables.\n\n```js\n// A utility function to generate SHA1s out of strings.\nconst { sha1 } = require(\"@fnando/keyring\");\n\nawait User.create({ email: \"john@example.com\" });\n\nconst user = await User.findOne({\n  where: {\n    email_digest: sha1(\"john@example.com\", { digestSalt: \"\u003ccustom salt\u003e\" }),\n  },\n});\n```\n\n### Exchange data with Ruby\n\nIf you use Ruby, you may be interested in\n\u003chttps://github.com/fnando/attr_keyring\u003e, which is able to read and write\nmessages using the same format.\n\n## Development\n\nAfter checking out the repo, run `yarn install` to install dependencies. Then,\nrun `yarn test` to run the tests.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at\n\u003chttps://github.com/fnando/keyring-node\u003e. This project is intended to be a safe,\nwelcoming space for collaboration, and contributors are expected to adhere to\nthe [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the\n[MIT License](https://opensource.org/licenses/MIT).\n\n## Icon\n\nIcon made by [Icongeek26](https://www.flaticon.com/authors/icongeek26) from\n[Flaticon](https://www.flaticon.com/) is licensed by Creative Commons BY 3.0.\n\n## Code of Conduct\n\nEveryone interacting in the **keyring** project’s codebases, issue trackers,\nchat rooms and mailing lists is expected to follow the\n[code of conduct](https://github.com/fnando/keyring-node/blob/main/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffnando%2Fkeyring-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffnando%2Fkeyring-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffnando%2Fkeyring-node/lists"}