├── .gitignore ├── deno.jsonc ├── .github ├── workflows │ ├── publish.yaml │ └── test.yaml └── dependabot.yaml ├── tests └── mod.test.ts ├── .editorconfig ├── LICENSE ├── README.md ├── deno.lock └── src └── mod.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vim 4 | **/cov/ 5 | crypto/_wasm/target 6 | cli/testdata/unicode_width_crate/target 7 | html_cov/ 8 | cov.lcov 9 | http/testdata/%25A.txt 10 | http/testdata/file#2.txt 11 | http/testdata/test file.txt 12 | coverage/ 13 | docs/ 14 | codecov* 15 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mogeko/aes-gcm", 3 | "version": "0.1.1", 4 | "license": "MIT", 5 | "exports": { 6 | ".": "./src/mod.ts" 7 | }, 8 | "publish": { 9 | "include": ["src/*", "LICENSE"] 10 | }, 11 | "imports": { 12 | "@/": "./src/", 13 | "@std/crypto": "jsr:@std/crypto@^1.0.3", 14 | "@std/encoding": "jsr:@std/encoding@^1.0.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: ["*"] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v4.2.2 15 | - name: Set up Deno 16 | uses: denoland/setup-deno@v2.0.1 17 | - run: deno publish 18 | -------------------------------------------------------------------------------- /tests/mod.test.ts: -------------------------------------------------------------------------------- 1 | import { decrypt, encrypt } from "@/mod.ts"; 2 | import { expect } from "jsr:@std/expect"; 3 | import { describe, it } from "jsr:@std/testing/bdd"; 4 | 5 | describe("mod", () => { 6 | it("Basic usage", async () => { 7 | expect( 8 | await decrypt(await encrypt("Hello, World!", "pa$$w0rd"), "pa$$w0rd"), 9 | ).toStrictEqual("Hello, World!"); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # Basic dependabot.yml file with 2 | # minimum configuration for two package managers 3 | 4 | version: 2 5 | updates: 6 | # Enable version updates for GitHub Actions 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | open-pull-requests-limit: 5 12 | groups: 13 | github-actions: 14 | patterns: ["*"] 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.{js,ts}] 15 | quote_type = double 16 | indent_size = 2 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | indent_size = 2 21 | 22 | [*.{json,jsonc,yaml,yml}] 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [master] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | test: 11 | permissions: 12 | id-token: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4.2.2 16 | - name: Set up Deno 17 | uses: denoland/setup-deno@v2.0.1 18 | - run: deno fmt --check 19 | - run: deno lint 20 | - run: | 21 | deno test --frozen --coverage --doc 22 | deno coverage --lcov > ./coverage/cov.lcov 23 | - run: deno coverage 24 | - name: Push the coverage report to Codecov 25 | uses: codecov/codecov-action@v5.0.7 26 | with: 27 | files: ./coverage/cov.lcov 28 | use_oidc: true 29 | - run: deno publish --dry-run 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Zheng Junyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @mogeko/aes-gcm 2 | 3 | [![Test](https://github.com/mogeko/aes-gcm/actions/workflows/test.yaml/badge.svg)](https://github.com/mogeko/aes-gcm/actions/workflows/test.yaml) 4 | [![Codecov](https://codecov.io/gh/mogeko/aes-gcm/graph/badge.svg?token=TxIJDlUwIc)](https://codecov.io/gh/mogeko/aes-gcm) 5 | [![JSR](https://jsr.io/badges/@mogeko/aes-gcm)](https://jsr.io/@mogeko/aes-gcm) 6 | 7 | A library that implements AES-GCM algorithm encryption and decryption for 8 | TypeScript/JavaScript. 9 | 10 | Galois/Counter Mode (GCM) uses a block cipher with block size 128 bits (commonly 11 | AES-128) operated in counter mode for encryption, and uses arithmetic in the 12 | Galois field GF(2128) to compute the authentication tag; hence the name. 13 | 14 | ## Installation 15 | 16 | ```sh 17 | deno add @mogeko/aes-gcm 18 | ``` 19 | 20 | For Node.js users: 21 | 22 | ```sh 23 | npx jsr add @mogeko/aes-gcm 24 | ``` 25 | 26 | ## Usage 27 | 28 | ```ts 29 | import { decrypt, encrypt } from "jsr:@mogeko/aes-gcm"; 30 | import { assertEquals } from "jsr:@std/assert/equals"; 31 | 32 | const cipher = await encrypt("Hello, world!", "pa$$w0rd"); 33 | 34 | assertEquals(await decrypt(cipher, "pa$$w0rd"), "Hello, world!"); 35 | ``` 36 | 37 | ## LICENSE 38 | 39 | The code in this project is released under the [MIT License](./LICENSE). 40 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4", 3 | "specifiers": { 4 | "jsr:@std/assert@*": "1.0.6", 5 | "jsr:@std/assert@^1.0.6": "1.0.6", 6 | "jsr:@std/crypto@^1.0.3": "1.0.3", 7 | "jsr:@std/encoding@^1.0.5": "1.0.5", 8 | "jsr:@std/expect@*": "1.0.5", 9 | "jsr:@std/internal@^1.0.4": "1.0.4", 10 | "jsr:@std/testing@*": "1.0.3" 11 | }, 12 | "jsr": { 13 | "@std/assert@1.0.6": { 14 | "integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207", 15 | "dependencies": [ 16 | "jsr:@std/internal" 17 | ] 18 | }, 19 | "@std/crypto@1.0.3": { 20 | "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" 21 | }, 22 | "@std/encoding@1.0.5": { 23 | "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" 24 | }, 25 | "@std/expect@1.0.5": { 26 | "integrity": "8c7ac797e2ffe57becc6399c0f2fd06230cb9ef124d45229c6e592c563824af1", 27 | "dependencies": [ 28 | "jsr:@std/assert@^1.0.6", 29 | "jsr:@std/internal" 30 | ] 31 | }, 32 | "@std/internal@1.0.4": { 33 | "integrity": "62e8e4911527e5e4f307741a795c0b0a9e6958d0b3790716ae71ce085f755422" 34 | }, 35 | "@std/testing@1.0.3": { 36 | "integrity": "f98c2bee53860a5916727d7e7d3abe920dd6f9edace022e2d059f00d05c2cf42", 37 | "dependencies": [ 38 | "jsr:@std/assert@^1.0.6", 39 | "jsr:@std/internal" 40 | ] 41 | } 42 | }, 43 | "workspace": { 44 | "dependencies": [ 45 | "jsr:@std/crypto@^1.0.3", 46 | "jsr:@std/encoding@^1.0.5" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Zheng Junyi. All rights reserved. MIT license. 2 | 3 | /** 4 | * A library that implements AES-GCM algorithm encryption and decryption 5 | * for TypeScript/JavaScript. 6 | * 7 | * Galois/Counter Mode (GCM) uses a block cipher with block size 128 bits 8 | * (commonly AES-128) operated in counter mode for encryption, and uses 9 | * arithmetic in the Galois field GF(2128) to compute the authentication 10 | * tag; hence the name. 11 | * 12 | * ## Usage 13 | * 14 | * ```ts 15 | * import { decrypt, encrypt } from "jsr:@mogeko/aes-gcm"; 16 | * import { assertEquals } from "jsr:@std/assert/equals"; 17 | * 18 | * const cipher = await encrypt("Hello, world!", "pa$$w0rd"); 19 | * 20 | * assertEquals(await decrypt(cipher, "pa$$w0rd"), "Hello, world!"); 21 | * ``` 22 | * 23 | * @module 24 | */ 25 | 26 | import { crypto } from "@std/crypto/crypto"; 27 | import { decodeBase64, encodeBase64 } from "@std/encoding/base64"; 28 | import { decodeHex, encodeHex } from "@std/encoding/hex"; 29 | 30 | /** 31 | * Encrypt plain text to cipher text with AES-GCM algorithm. 32 | * 33 | * @param plain Plain text that needs to be encrypted. 34 | * @param passwd The password used for encryption. 35 | * @returns Cipher text encrypted with AES-GCM algorithm. 36 | * 37 | * @example Usage 38 | * ```ts 39 | * import { encrypt } from "jsr:@mogeko/aes-gcm"; 40 | * 41 | * const cipher = await encrypt("Hello, world!", "pa$$w0rd"); 42 | * ``` 43 | */ 44 | export async function encrypt(plain: string, passwd: string): Promise { 45 | const pwUtf8 = new TextEncoder().encode(passwd); 46 | const pwHash = await crypto.subtle.digest("SHA-256", pwUtf8); 47 | 48 | const iv = crypto.getRandomValues(new Uint8Array(12)); 49 | const alg = { name: "AES-GCM", iv }; 50 | const key = await crypto.subtle.importKey("raw", pwHash, alg, false, [ 51 | "encrypt", 52 | ]); 53 | 54 | const ptUint8 = new TextEncoder().encode(plain); 55 | 56 | const ctBuffer = await crypto.subtle.encrypt(alg, key, ptUint8); 57 | 58 | return encodeHex(iv) + encodeBase64(ctBuffer); 59 | } 60 | 61 | /** 62 | * Decrypt the cipher text encrypted by AES-GCM algorithm to plain text. 63 | * 64 | * @param cipher Cipher text encrypted with AES-GCM algorithm. 65 | * @param passwd The password used for decryption. 66 | * @returns Plain text decrypted from cipher text. 67 | * 68 | * @example Usage 69 | * ```ts 70 | * import { decrypt } from "jsr:@mogeko/aes-gcm"; 71 | * import { assertEquals } from "jsr:@std/assert/equals"; 72 | * 73 | * const cipher = "aca99b97756adc21cf62e71agw1jW7Zmthd9vylHTNYs3vzH2hFthaWmom0D8k4="; 74 | * 75 | * assertEquals(await decrypt(cipher, "pa$$w0rd"), "Hello, world!"); 76 | * ``` 77 | */ 78 | export async function decrypt(cipher: string, passwd: string): Promise { 79 | const pwUtf8 = new TextEncoder().encode(passwd); 80 | const pwHash = await crypto.subtle.digest("SHA-256", pwUtf8); 81 | 82 | const alg = { name: "AES-GCM", iv: decodeHex(cipher.slice(0, 24)) }; 83 | const key = await crypto.subtle.importKey("raw", pwHash, alg, false, [ 84 | "decrypt", 85 | ]); 86 | 87 | const ctUint8 = decodeBase64(cipher.slice(24)); 88 | 89 | const ptBuffer = await crypto.subtle.decrypt(alg, key, ctUint8); 90 | 91 | return new TextDecoder().decode(ptBuffer); 92 | } 93 | --------------------------------------------------------------------------------