diff --git a/.gitignore b/.gitignore index 67b9069..e93d2e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -dist/* \ No newline at end of file +dist/* +.DS_Store \ No newline at end of file diff --git a/.npmignore b/.npmignore index 1f94ad5..015b137 100644 --- a/.npmignore +++ b/.npmignore @@ -14,3 +14,4 @@ coverage/ .husky/ e2e/ *.mjs +dist/.dts \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 83133ee..22c037a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ -dist/*.js -.github/workflows/*.yml \ No newline at end of file +.github/workflows/*.yml +dist/*.js \ No newline at end of file diff --git a/README.md b/README.md index 6af05d0..2ba86a8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ const decrypted = await decryptFile(encrypted, privateKey); ## Background The original use case for this library was a service that supported file uploads, but made these files public under a non-guessable link. -I consider this (security through obscurity)[https://en.wikipedia.org/wiki/Security_through_obscurity]. Encrypting the files added an extra layer of protection in case an URL is leaked accidentally or intentionally. ## How it works @@ -33,11 +32,11 @@ The result is packed in the following format: ### Encryption -![Encryption schema](./doc/encrypt.png) +![Encryption schema](https://github.com/mojadev/simple-file-encryption/raw/main/doc/encrypt.png) ### Decryption -![Decryption schema](./doc/decrypt.png) +![Decryption schema](https://github.com/mojadev/simple-file-encryption/raw/main/doc/decrypt.png) ## Usage diff --git a/keys/README.md b/keys/README.md new file mode 100644 index 0000000..02f9b65 --- /dev/null +++ b/keys/README.md @@ -0,0 +1,6 @@ +The keys in this folder can be generated using `npm run genkeys`. +They are intentionally left here in the repository and are not used in any production environment. + +Consider them as a reference in case you aren't sure how your keys should look. + +The file [test is the private key](./test) while [test.pub is the public key](./test.pub). diff --git a/keys/test b/keys/test new file mode 100644 index 0000000..aa510ac --- /dev/null +++ b/keys/test @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +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 +xp1SwhStmWkIu8HwAeZD3+YS +-----END PRIVATE KEY----- diff --git a/keys/test.pem b/keys/test.pem new file mode 100644 index 0000000..c114f3f --- /dev/null +++ b/keys/test.pem @@ -0,0 +1,11 @@ +-----BEGIN PUBLIC KEY----- +MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA66NtDN57eyOkvETZtZM6 +d8+xn+7twk6SfzAMv78MGfI+MjHsVbFTDG4YT+NiXw29wyIs4AgXayT2hOfbi6yI +6v6SrNrpGwXUJtsgEauctJvnDYMZHT+ZtCZCKZjkoxOarO8TXBPcx1npsc6s0cQS +4JWFc71+rRDwHm/NHwXzjiHL/rXMqdI48rCszqVFR4JfrnCvCWq4MrZILocnngtc +Y1T+/1idaAbtYF2AcYogJy7j87Na8kTXwxRgbS/XWgLfWv1SrdJfmF+CY4Zw4hV0 ++Hv+17Oz8WlOB5gnQ4RIQkCnydXN3Mc4pOGWm9E44t9kfUa8qG9a7qUo0ukr3NgW +T7fp9g2S5eDyaUX7UToLsNZuCigurkYFtsiGqaY673bq9snzTMM3qThzV8/7GuHp +ykVs0J9LUx3r5gyugpL1MuJLctdrkQy9NzFLq3rJnqK1I+oR2l9aon3Ynr/o8Cx/ +Wwjr/OM9DsDyNYZDFtrM3kMYjr+53XZlmSAi0cOCsmApAgMBAAE= +-----END PUBLIC KEY----- diff --git a/keys/test.pub b/keys/test.pub new file mode 100644 index 0000000..e724c8d --- /dev/null +++ b/keys/test.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDro20M3nt7I6S8RNm1kzp3z7Gf7u3CTpJ/MAy/vwwZ8j4yMexVsVMMbhhP42JfDb3DIizgCBdrJPaE59uLrIjq/pKs2ukbBdQm2yARq5y0m+cNgxkdP5m0JkIpmOSjE5qs7xNcE9zHWemxzqzRxBLglYVzvX6tEPAeb80fBfOOIcv+tcyp0jjysKzOpUVHgl+ucK8JargytkguhyeeC1xjVP7/WJ1oBu1gXYBxiiAnLuPzs1ryRNfDFGBtL9daAt9a/VKt0l+YX4JjhnDiFXT4e/7Xs7PxaU4HmCdDhEhCQKfJ1c3cxzik4Zab0Tji32R9Rryob1rupSjS6Svc2BZPt+n2DZLl4PJpRftROguw1m4KKC6uRgW2yIappjrvdur2yfNMwzepOHNXz/sa4enKRWzQn0tTHevmDK6CkvUy4kty12uRDL03MUuresmeorUj6hHaX1qifdiev+jwLH9bCOv84z0OwPI1hkMW2szeQxiOv7nddmWZICLRw4KyYCk= privat@Janniss-MacBook-Pro-10.local diff --git a/package-lock.json b/package-lock.json index aef96df..ea2154a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "husky": "^8.0.3", "rimraf": "^5.0.5", "rollup": "^4.9.4", + "rollup-plugin-dts": "^6.1.0", "typescript": "^5.3.3" } }, @@ -6084,6 +6085,18 @@ "node": "14 || >=16.14" } }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -7401,6 +7414,28 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-dts": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.1.0.tgz", + "integrity": "sha512-ijSCPICkRMDKDLBK9torss07+8dl9UpY9z1N/zTeA1cIqdzMlpkV3MOOC7zukyvQfDyxa1s3Dl2+DeiP/G6DOw==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.4" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^7.22.13" + }, + "peerDependencies": { + "rollup": "^3.29.4 || ^4", + "typescript": "^4.5 || ^5.0" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", diff --git a/package.json b/package.json index ed03b5a..caffffe 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "Simple public/private key encryption and decryption of files in your browser using the WebCrypto API.", "main": "dist/simple-file-encryption.js", + "module": "dist/simple-file-encryption.mjs", "typings": "dist/simple-file-encryption.d.ts", "scripts": { "prebuild": "rimraf ./dist/*", @@ -37,6 +38,7 @@ "husky": "^8.0.3", "rimraf": "^5.0.5", "rollup": "^4.9.4", + "rollup-plugin-dts": "^6.1.0", "typescript": "^5.3.3" }, "config": { @@ -44,4 +46,4 @@ "path": "./node_modules/cz-conventional-changelog" } } -} +} \ No newline at end of file diff --git a/rollup.config.mjs b/rollup.config.mjs index 48cc4c0..c62b35a 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,5 +1,6 @@ import typescript from "@rollup/plugin-typescript"; import terser from "@rollup/plugin-terser"; +import dts from "rollup-plugin-dts"; export default [ { @@ -32,4 +33,9 @@ export default [ ], plugins: [typescript()], }, + { + input: "./dist/.dts/index.d.ts", + output: [{ file: "dist/simple-file-encryption.d.ts", format: "es" }], + plugins: [dts()], + }, ]; diff --git a/src/container.ts b/src/container.ts index 5464abc..271f146 100644 --- a/src/container.ts +++ b/src/container.ts @@ -36,6 +36,12 @@ export const packContainer = ( return result; }; +/** + * Unpack a string containing an encrypted file. + * + * @param payload The payload that should be unpacked + * @returns An object containing a key and the payload + */ export const unpackContainer = ( payload: ContainerBuffer ): { key: KeyType; payload: EncryptedPayload } => { diff --git a/src/decrypt.ts b/src/decrypt.ts index 619da42..0eca1a9 100644 --- a/src/decrypt.ts +++ b/src/decrypt.ts @@ -42,7 +42,7 @@ async function decryptPayload( encryptedPayload: EncryptedPayload ) { try { - return await window.crypto.subtle.decrypt( + return await globalThis.crypto.subtle.decrypt( { name: "AES-CBC", iv, @@ -57,7 +57,7 @@ async function decryptPayload( async function createAESKey(aesSecret: Secret) { try { - return await window.crypto.subtle.importKey( + return await globalThis.crypto.subtle.importKey( "raw", aesSecret, { name: "AES-CBC", length: 512 }, @@ -71,7 +71,7 @@ async function createAESKey(aesSecret: Secret) { async function decryptAESSecret(key: PrivateKey, encryptedKey: KeyType) { try { - return await window.crypto.subtle.decrypt( + return await globalThis.crypto.subtle.decrypt( { name: "RSA-OAEP", }, diff --git a/src/encrypt.ts b/src/encrypt.ts index 989f8e3..8292e5c 100644 --- a/src/encrypt.ts +++ b/src/encrypt.ts @@ -3,6 +3,13 @@ import { packContainer } from "./container"; import { importPublicKey } from "./rsa-key"; import { AESKey, PlaintextPayload, PublicKeyPEMString, Secret } from "./types"; +/** + * Encrypt the given file with the provided public key. + * + * @param file + * @param publicKey + * @returns + */ export async function encryptFile( file: File, publicKey: PublicKeyPEMString @@ -26,9 +33,15 @@ export async function encryptFile( return new File([result], file.name, { type: file.type }); } +/** + * Encrypt the given buffer using the provided symmetric key. + * + * @param inlineKey the AESKey to use for encrypting + * @param buffer the buffer that should be encrypted + */ async function encryptPayload(inlineKey: AESKey, buffer: PlaintextPayload) { try { - const iv = window.crypto.getRandomValues(new Uint8Array(IV_LENGTH)); + const iv = globalThis.crypto.getRandomValues(new Uint8Array(IV_LENGTH)); const encryptedPayload = await crypto.subtle.encrypt( { name: AES_ALGO, iv }, inlineKey, @@ -40,6 +53,13 @@ async function encryptPayload(inlineKey: AESKey, buffer: PlaintextPayload) { } } +/** + * Generate a random symmetric key that can be used for encrypting and decrypting. + * + * The return type contains the CryptoKey and the ArrayBuffer for convenience. + * + * @returns A tuple containing the crypto key and it's raw (arraybuffer) representation + */ async function generateSymmetricKey(): Promise<[AESKey, Secret]> { try { const cryptoKey = await crypto.subtle.generateKey( diff --git a/tsconfig.json b/tsconfig.json index e1b0bbc..3b456a0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,11 @@ "target": "es2016", "module": "ESNext", "esModuleInterop": true, + "declarationDir": ".dts", "forceConsistentCasingInFileNames": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "declaration": true, + "declarationMap": true } }