{"id":13781228,"url":"https://github.com/FiloSottile/typage","last_synced_at":"2025-05-11T14:34:51.315Z","repository":{"id":185096743,"uuid":"669870894","full_name":"FiloSottile/typage","owner":"FiloSottile","description":"A TypeScript implementation of the age file encryption format, available as an npm package or as a bundled .js file.","archived":false,"fork":false,"pushed_at":"2025-04-26T12:12:40.000Z","size":1566,"stargazers_count":173,"open_issues_count":5,"forks_count":17,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-06T00:38:53.451Z","etag":null,"topics":["age-encryption"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FiloSottile.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"custom":"https://filippo.io/maintenance#funding"}},"created_at":"2023-07-23T17:42:01.000Z","updated_at":"2025-05-03T12:53:12.000Z","dependencies_parsed_at":"2024-08-03T18:12:29.569Z","dependency_job_id":"96d6bd11-3717-48a2-b639-13b575fbb902","html_url":"https://github.com/FiloSottile/typage","commit_stats":null,"previous_names":["filosottile/age.ts","filosottile/typage"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FiloSottile%2Ftypage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FiloSottile%2Ftypage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FiloSottile%2Ftypage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FiloSottile%2Ftypage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FiloSottile","download_url":"https://codeload.github.com/FiloSottile/typage/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253580340,"owners_count":21930928,"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":["age-encryption"],"created_at":"2024-08-03T18:01:24.052Z","updated_at":"2025-05-11T14:34:51.307Z","avatar_url":"https://github.com/FiloSottile.png","language":"TypeScript","funding_links":["https://filippo.io/maintenance#funding"],"categories":["TypeScript","Implementations"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cpicture\u003e\n        \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/FiloSottile/age/blob/main/logo/logo_white.svg\"\u003e\n        \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/FiloSottile/age/blob/main/logo/logo.svg\"\u003e\n        \u003cimg alt=\"The age logo, a wireframe of St. Peters dome in Rome, with the text: age, file encryption\" width=\"600\" src=\"https://github.com/FiloSottile/age/blob/main/logo/logo.svg\"\u003e\n    \u003c/picture\u003e\n\u003c/p\u003e\n\n[`age-encryption`](https://www.npmjs.com/package/age-encryption) is a TypeScript\nimplementation of the [age](https://age-encryption.org) file encryption format.\n\nIt depends only on the [noble](https://paulmillr.com/noble/) cryptography\nlibraries, and uses the Web Crypto API when available.\n\nIt also provides support for symmetric encryption using passkeys and hardware\nsecurity keys in the browser via WebAuthn, and an interoperable CLI plugin and\nGo library for FIDO2 tokens.\n\n## Installation\n\n```sh\nnpm install age-encryption\n```\n\n## Usage\n\n`age-encryption` is a modern ES Module with built-in types.\n\nIt's compiled for ES2022, and compatible with Node.js 18+, Bun, Deno, and all recent browsers.\n\n#### Encrypt and decrypt a file with a new recipient / identity pair\n\n```ts\nimport * as age from \"age-encryption\"\n\nconst identity = await age.generateIdentity()\nconst recipient = await age.identityToRecipient(identity)\nconsole.log(identity)\nconsole.log(recipient)\n\nconst e = new age.Encrypter()\ne.addRecipient(recipient)\nconst ciphertext = await e.encrypt(\"Hello, age!\")\n\nconst d = new age.Decrypter()\nd.addIdentity(identity)\nconst out = await d.decrypt(ciphertext, \"text\")\nconsole.log(out)\n```\n\n#### ASCII armoring\n\nage encrypted files (the inputs of `Decrypter.decrypt` and outputs of\n`Encrypter.encrypt`) are binary files, of type `Uint8Array`. There is an official ASCII\n\"armor\" format, based on PEM, which provides a way to encode an encrypted file as text.\n\n```ts\nimport * as age from \"age-encryption\"\n\nconst identity = await age.generateIdentity()\nconst recipient = await age.identityToRecipient(identity)\nconsole.log(identity)\nconsole.log(recipient)\n\nconst e = new age.Encrypter()\ne.addRecipient(recipient)\nconst ciphertext = await e.encrypt(\"Hello, age!\")\nconst armored = age.armor.encode(ciphertext)\n\nconsole.log(armored)\n// -----BEGIN AGE ENCRYPTED FILE-----\n// YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0QXVkQmNwZ3ZzYnNRZDJP\n// WlFId3hyeFNmRS9SdUVUTkFhY1FXSno5VUFBClNOSWhEbnhoK21TaEs3SWRGdklw\n// OW9pdlBZbDg3SEVSQ1FZZHBvUS90YjgKLS0tIGRCVXNNWmdJS0ZkNlNZbStPZWh4\n// N2FBNUJZdTFxMmYwVTEzUWwvTFVNeUkKrNZnrZjMlXvoCHz0FUS/bp9129XtSV1Q\n// 2twDjjAOwgBtBYoji9gKWgOG4w==\n// -----END AGE ENCRYPTED FILE-----\n\nconst d = new age.Decrypter()\nd.addIdentity(identity)\nconst decoded = age.armor.decode(armored)\nconst out = await d.decrypt(decoded, \"text\")\nconsole.log(out)\n```\n\n#### Encrypt and decrypt a file with a passphrase\n\n```ts\nimport { Encrypter, Decrypter } from \"age-encryption\"\n\nconst e = new Encrypter()\ne.setPassphrase(\"burst-swarm-slender-curve-ability-various-crystal-moon-affair-three\")\nconst ciphertext = await e.encrypt(\"Hello, age!\")\n\nconst d = new Decrypter()\nd.addPassphrase(\"burst-swarm-slender-curve-ability-various-crystal-moon-affair-three\")\nconst out = await d.decrypt(ciphertext, \"text\")\nconsole.log(out)\n```\n\n### Browser usage\n\n`age-encryption` is compatible with modern bundlers such as [esbuild](https://esbuild.github.io/).\n\nTo produce a classic library file that sets `age` as a global variable, you can run\n\n```sh\ncd \"$(mktemp -d)\" \u0026\u0026 npm init -y \u0026\u0026 npm install esbuild age-encryption\nnpx esbuild --target=es2022 --bundle --minify --outfile=age.js --global-name=age age-encryption\n```\n\nor download a pre-built one from the [Releases page](https://github.com/FiloSottile/typage/releases).\n\n\u003c!-- TODO: why doesn't\n\n  npx --package esbuild --package age-encryption -- esbuild ...\n\nwork? It should run esbuild in an environment where age-encryption is available. --\u003e\n\nThen, you can use it like this\n\n```html\n\u003cscript src=\"age.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n(async () =\u003e {\n    const identity = await age.generateIdentity()\n    const recipient = await age.identityToRecipient(identity)\n    console.log(identity)\n    console.log(recipient)\n\n    const e = new age.Encrypter()\n    e.addRecipient(recipient)\n    const ciphertext = await e.encrypt(\"Hello, age!\")\n\n    const d = new age.Decrypter()\n    d.addIdentity(identity)\n    const out = await d.decrypt(ciphertext, \"text\")\n    console.log(out)\n})()\n\u003c/script\u003e\n```\n\n#### Encrypt and decrypt a file with a passkey\n\nIn the browser, `age-encryption` supports *symmetric* encryption with passkeys,\ndiscoverable credentials that can be stored and synced by platforms (e.g. iCloud\nKeychain) or password managers (e.g. 1Password).\n\nThis functionality uses the WebAuthn PRF extension, which is supported by recent\nbrowsers and authenticators. When encrypting or decrypting a file, the user will\nbe prompted to select a passkey associated with the replying party ID (usually\nthe website origin). Passkeys not generated by `createCredential` can be used if\nthey have the `prf` extension enabled. The identity string returned by\n`createCredential` can be optionally provided at encryption/decryption time to\nprevent the user from selecting other passkeys.\n\n```ts\nawait age.webauthn.createCredential({ keyName: \"age encryption key 🦈\" })\n\nconst e = new age.Encrypter()\ne.addRecipient(new age.webauthn.WebAuthnRecipient())\nconst ciphertext = await e.encrypt(\"Hello, age!\")\nconst armored = age.armor.encode(ciphertext)\nconsole.log(armored)\n\nconst d = new age.Decrypter()\nd.addIdentity(new age.webauthn.WebAuthnIdentity())\nconst decoded = age.armor.decode(armored)\nconst out = await d.decrypt(decoded, \"text\")\nconsole.log(out)\n```\n\nEach encryption and decryption operation requires the authenticator and user\nconfirmation, there is no extractable key, and encrypted files can't be linked\nto an identity or to each other without the ability to decrypt them.\n\n#### Encrypt and decrypt a file with a security key\n\n`age-encryption` also supports non-discoverable FIDO2 credentials, usually\nuseful to encrypt files with hardware security keys (e.g. YubiKeys).\n\nEncryption and decryption work the same as with passkeys, but the identity\nstring is mandatory, because these credentials are not discoverable.\n\n```ts\nconst identity = await age.webauthn.createCredential({\n    type: \"security-key\", keyName: \"age encryption key\" })\nconsole.log(identity) // AGE-PLUGIN-FIDO2PRF-1...\n\nconst e = new age.Encrypter()\ne.addRecipient(new age.webauthn.WebAuthnRecipient({ identity: identity }))\nconst ciphertext = await e.encrypt(\"Hello, age!\")\nconst armored = age.armor.encode(ciphertext)\nconsole.log(armored)\n\nconst d = new age.Decrypter()\nd.addIdentity(new age.webauthn.WebAuthnIdentity({ identity: identity }))\nconst decoded = age.armor.decode(armored)\nconst out = await d.decrypt(decoded, \"text\")\nconsole.log(out)\n```\n\n##### age-plugin-fido2prf\n\nIf a credential is associated with a USB FIDO2 security key (e.g. a YubiKey),\nits identity string can be used outside the browser with the provided\n`age-plugin-fido2prf` plugin.\n\nFiles encrypted in the browser will decrypt from the CLI, and vice-versa. Since\nWebAuthn encryption is symmetric, there is no recipient encoding, only\nidentities. To encrypt to an identity, use `age -e -i`.\n\n```sh\ngo install filippo.io/typage/fido2prf/cmd/age-plugin-fido2prf@latest\n\ncat \u003c\u003c EOF \u003e identity.txt\nAGE-PLUGIN-FIDO2PRF-1Q9VGPY2E7S5FJJS3N7P03TZMMEJ94S6HYLDJLU8WVX2HP8SXQUGJUZ68LN6GP705662VS06UEX5J42W80NZT8Y2DQ8GTDN50VGATCNYLJ4HY2W5J67KYCTM858UFDCNUUDZ6U28WEMUKGVG9RNELRJDH8NFEP999Z8XFSS8XLS448A3TSQKWG9DMPL8ZCRRA02KSUC2UXTYDFNVYAE5KCMMRV9KXSMMNWJQKXATNVGTXV35G\nEOF\n\nage -d -i identity.txt \u003c\u003c EOF\n-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGFnZS1lbmNyeXB0aW9uLm9yZy9maWRv\nMnByZiBJSFFHd0poUkNSYThuVnB6b1R1bjdBClJ3d3dRUUtiRjN4TkU0VWx1SUZU\nWnJtTVBFUTZoR0d4eUx2WXFOSFBsQzQKLS0tIFNWVGRnNzV4L00wblRENUZyYlFh\nWU5wQmVsdG5hL0lmcXhTVzZHTVRtdFkK2rYiueXr8dgM1GiLVrBMC/LQRzkDacMw\nGEtVcMZyh7b90z6VR3KT92EIlA==\n-----END AGE ENCRYPTED FILE-----\nEOF\n```\n\nCredentials can be generated from the command line with `age-plugin-fido2prf\n-generate RPID`. Note that they will be usable inside the browser only if the\nrelying party ID matches the website's origin.\n\nAll the features of the plugin are also available as a Go library at\n[filippo.io/typage/fido2prf](https://pkg.go.dev/filippo.io/typage/fido2prf).\n\n#### Web Crypto identities\n\nYou can use a CryptoKey as an identity. It must have an `algorithm` of `X25519`,\nand support the `deriveBits` key usage. It doesn't need to be extractable.\n\n```ts\nconst keyPair = await crypto.subtle.generateKey({ name: \"X25519\" }, false, [\"deriveBits\"])\nconst identity = (keyPair as CryptoKeyPair).privateKey\nconst recipient = await age.identityToRecipient(identity)\nconsole.log(recipient)\n\nconst e = new age.Encrypter()\ne.addRecipient(recipient)\nconst file = await e.encrypt(\"age\")\n\nconst d = new age.Decrypter()\nd.addIdentity(identity)\nconst out = await d.decrypt(file, \"text\")\nconsole.log(out)\n```\n\n### Custom recipients and identities\n\nYou can implement the `Recipient` and `Identity` interfaces to use custom types\nas recipients and identities.\n\nThis lets you use use remote APIs and secrets managers to wrap files keys, and\ninteroperate with [age plugins](https://github.com/FiloSottile/awesome-age?tab=readme-ov-file#plugins).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFiloSottile%2Ftypage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFiloSottile%2Ftypage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFiloSottile%2Ftypage/lists"}