{"id":19257553,"url":"https://github.com/ericvera/keydude","last_synced_at":"2026-06-15T08:32:15.649Z","repository":{"id":38361534,"uuid":"150337797","full_name":"ericvera/keydude","owner":"ericvera","description":"Simplified and opinionated crypto library (wraps the Web Crypto API)","archived":false,"fork":false,"pushed_at":"2023-01-07T04:14:51.000Z","size":1510,"stargazers_count":1,"open_issues_count":15,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-05-08T10:07:41.779Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/ericvera.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-25T22:41:13.000Z","updated_at":"2021-02-02T01:48:47.000Z","dependencies_parsed_at":"2023-02-06T11:30:34.823Z","dependency_job_id":null,"html_url":"https://github.com/ericvera/keydude","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ericvera/keydude","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fkeydude","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fkeydude/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fkeydude/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fkeydude/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericvera","download_url":"https://codeload.github.com/ericvera/keydude/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fkeydude/sbom","scorecard":{"id":380768,"data":{"date":"2025-08-11","repo":{"name":"github.com/ericvera/keydude","commit":"9e6737a7e4498fcd73919bc30588747fa1a08f17"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/27 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"41 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-c2jc-4fpr-4vhg","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-cph5-m8f7-6c5x","Warn: Project is vulnerable to: GHSA-wf5p-g6vw-rhxx","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-74fj-2j2h-c42q","Warn: Project is vulnerable to: GHSA-pw2r-vq6v-hr8c","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-xvf7-4v9q-58w6","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-3rfm-jhwj-7488","Warn: Project is vulnerable to: GHSA-hhq3-ff78-jv3g","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-hc6q-2mpp-qw7j","Warn: Project is vulnerable to: GHSA-4vvj-4cpr-p986","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T15:23:19.812Z","repository_id":38361534,"created_at":"2025-08-18T15:23:19.812Z","updated_at":"2025-08-18T15:23:19.812Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34355157,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-11-09T19:10:37.856Z","updated_at":"2026-06-15T08:32:15.615Z","avatar_url":"https://github.com/ericvera.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# keydude\n\n[![github license](https://img.shields.io/github/license/ericvera/keydude.svg?style=flat-square)](https://github.com/ericvera/keydude/blob/master/LICENSE)\n[![npm version](https://img.shields.io/npm/v/keydude.svg?style=flat-square)](https://npmjs.org/package/keydude)\n[![tested with jest](https://img.shields.io/badge/tested_with-jest-99424f.svg?style=flat-square)](https://github.com/facebook/jest)\n\nSimplified and opinionated crypto library (wraps the Web Crypto API) for end-to-end encryption.\n\n## Why keydude?\n\nThis cryptography thing is a pain to figure out and easy to get wrong. The library was built to implement symmetric end-to-end encryption in a web app.\n\n- `SHA-256` for password hashing\n- `AES-GCM` for wrapping and encryption\n- `Base64` as the output\n\n## The basics\n\nDISCLAIMER: This is a high level explanation to get you started and not meant to be a replacement for security training.\n\nIn a simplistic way, symmetric encryption is when you use the same key for both encryption and decryption. As opposed to asymmetric encryption where you have a public key that you can freely share used to encrypt the data and a private key that you have to keep secret used to decrypt the data. Symmetric encryption is better for when you will be both encrypting and decrypting your own data and asymmetric is better when you are sending/receiving the data between different people since anybody can use the public key to encrypt data that only you (or anyone with the private key) can read.\n\n### Key management\n\nFor symmetric encryption you need a key. You can generate this key using `keydude.generateEncryptionDecryptionKey()`. You must keep this key secret. You also want to make it so that if somebody gets access to it they can't just use it to decode all of your data. For this you 'wrap' the key before putting it in persistent storage using `keydude.wrapKey()`. This is the equivalent of putting a physical key inside a safe box with password.\n\nWrapping a key requires two things, a passphrase and an initialization vector (IV). The passphrase is easy to understand, you can generate one for each user (you, the developer, will be able to decode the data if you want to) or you can let the user provide it in the client and not store it anywhere (the user data will be completely inaccessible to you the developer). The initialization vector sounds fancy, but it is just an array of cryptographicaly random bytes used to make the encryption more secure.\n\nOK that may require a bit more of explaining. The AES-GCM algorithm is a block-cypher which is fancy speak for 'it encrypts blocks of data of a predetermined size at a time'. So if you had repeating blocks encrypted with the same key they would look the same. The initialization vector provided with each encrypting is used by the algorithm to prevent these blocks from being the same.\n\nYou can generate the IV using `keydude.generateIV()` which returns a base64 encoded string. You will have to generate one and store it as you will have to provide it every time you wrap and unwrap your encryption/decryption key.\n\nIn summary you, generate a new key for a user with `keydude.generateEncryptionDecryptionKey()`, generate an IV using `keydude.generateIV()`, then wrap that key with `keydude.wrapKey(\u003cpassphrase\u003e, \u003cgenerated IV\u003e, \u003cgenerated key\u003e)`. You can store the generated IV and the wrapped key in the database. When you need to use the key to encrypt/decrypt just use `keydude.unwrapKey(\u003cpassphrase\u003e, \u003cgenerated IV\u003e, \u003cwrapped key\u003e)`. Finally, for convenience, if you are in a trusted client, once the user provides the passphrase and you download and unwrap the key you can re-wrap it and store it locally using some other passphrase so that the user does not have to keep entering the password. This could be using a PIN or some piece of user data like a user id.\n\n### Encryption and decryption\n\nAfter key management, this part is going to be very easy. Using the key that you extracted from `keydude.unwrapKey()` you can encrypt your data using `keydude.encrypt(\u003cdata object\u003e, \u003cunwrapped key\u003e)`. This will return a single base64 encoded string containing both a new initialization vector (`encrypt` generates a new one for every encryption/decryption for the algorithm to be secure) and the encrypted data. You can safely store this in you database or local storage.\n\nWhenever you want to access this information again just call `keydude.decrypt(\u003cencrypted data generated with keydude.encrypt()\u003e, \u003cunwrapped key\u003e)`.\n\n## Usage\n\n### Install module\n\n`npm install --save keydude`\n\n`yarn add keydude`\n\n### In browser\n\n`\u003cscript crossorigin src=\"https://unpkg.com/keydude@1/dist/keydude.js\"\u003e\u003c/script\u003e`\n\n### Sample\n\nAll functions return a Promise. The sample uses async/await because it is easier to read.\n\n```javascript\nimport keydude from 'keydude'\n\n// This should come from the user or something else\n// like a user id (if storing localy) different for every user\nconst passphrase = 'my-C0mpl3x-p@ssKe7!$s.'\n\nconst newKey = await keydude.generateEncryptionDecryptionKey()\n\nconst keyWrappingIV = await keydude.generateIV()\n// sample: 'T0IsmW6JljSCU1jC'\n\nconst wrappedKey = await keydude.wrapKey(passphrase, passphraseIV, key)\n// sample: 'oc7/LI1u7tTPGuOZm3oOC20ztbEOTU0Dgp7I5QJPawXGMv44mNqJLIgZ9VNVVgpVbZBUJGpFr7GLJDu5',\n\n// ...store keyWrappingIV and wrappedKey somewhere\n// as you will need to unwrap the key in every\n// session\n\nconst unwrappedKey = await keydude.unwrapKey(\n  passphrase,\n  keyWrappingIV,\n  wrappedKey\n)\n\nconst encryptedData = await keydude.encrypt(\n  { id: 'someid', other: Date.parse('2025-02-06') },\n  unwrappedKey\n)\n// sample: 'VL3RX4U9b55Y6OyTV2/3ifKeNS7/wgnr9ZXiiajADL8bHVu8dyZj9RjzA/Vi4z1M0L0wQ5nV84NHG+FHzrzB9BqghEhpqmzwbQ=='\n\nconst decryptedData = await keydude.decrypt(encryptedData, unwrappedKey)\n// sample: { id: 'someid', other: Date.parse('2025-02-06') }\n```\n\n## Functions\n\n| Function                                                      | Description                                                                                                                                   |\n| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |\n| `generateEncryptionDecryptionKey()`                           | Generates a new encryption/decryption key. You should use wrapKey before storing the key anywhere and then unwrapKey when you need to use it. |\n| `generateIV()`                                                | Generate a secure 96-bit initialization vector and returns it as a base64 encoded string.                                                     |\n| `wrapKey(passphrase, base64PassphraseIV, keyToWrap)`          | Wrap (encode) the key using a key generated from the passphrase.                                                                              |\n| `unwrapKey(passphrase, base64PassphraseIV, wrappedKeyObject)` | Unwraps a previously wrapped key so that it can be used.                                                                                      |\n| `encrypt(dataObject, encryptionDecryptionKey)`                | This will call JSON.stringify, compress, and finally encrypt the provided dataObject.                                                         |\n| `decrypt(encryptedDataObject, encryptionDecryptionKey)`       | Call this on the result of an encrypt call in order to decrypt the object.                                                                    |\n\n## Opinionated\n\n### AES-GCM\n\nAES-GCM is used as the algorithm for encryption and decryption as well as wrapping and unwrapping keys.\n\nAfter hours of research I found that many articles point to AES-GCM as the algorithm that strikes the best balance of security and performance. Here are a couple of quotes from the Wikipedia article on GCM.\n\n\u003e \"Galois/Counter Mode (GCM) is a mode of operation for symmetric key cryptographic block ciphers that has been widely adopted because of its efficiency and performance.\"\n\n\u003e \"GCM mode is used in the IEEE 802.1AE (MACsec) Ethernet security, IEEE 802.11ad (also known as WiGig), ANSI (INCITS) Fibre Channel Security Protocols (FC-SP), IEEE P1619.1 tape storage, IETF IPsec standards,[4][5] SSH[6] and TLS 1.2.[7][8] AES-GCM is included in the NSA Suite B Cryptography. GCM mode is used in the SoftEther VPN server and client,[9] as well as OpenVPN since version 2.4.\"\n\n\u003e _from [Wikipedia article](https://en.wikipedia.org/wiki/Galois/Counter_Mode)_\n\n### base64 encoding\n\nThe output of all data that is meant to be stored (everything except the generated keys) is encoded in base64. While this encoding increases the size of the data, it significantly simplifies moving the data around as it uses web safe characters. This means that it is easy to store the data as string in local storage options as well as NoSQL databases as string.\n\n### SHA-256\n\nThe key used to wrap/unwrap the encryption/decryption keys is generated from a passphrase. A SHA-256 hash is generated from the passphrase which is then used to generate a 256-bit key.\n\n### 96-bit initialization vector\n\nI was not able to find a reliable source with recommendations for iv length. What I did find was a lot of recommendations to use 96-bits with no documented source. The closest I could find was the following quote from a [NIST report](https://www.nist.gov/).\n\n\u003e \"The default length of the IV is 96 bits,[...]\"\n\n\u003e _from [Authentication Failures in NIST version of GCM](https://csrc.nist.gov/csrc/media/projects/block-cipher-techniques/documents/bcm/joux_comments.pdf)_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericvera%2Fkeydude","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericvera%2Fkeydude","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericvera%2Fkeydude/lists"}