Simple, static, vanilla JavaScript web app for encrypting text with PGP in the browser.
Everything runs entirely client-side using OpenPGP.js loaded from a CDN.
You can try the latest version here:
- Single text area, dual mode:
- Type or paste plaintext and click Encrypt.
- Paste an armored PGP message (starting with
-----BEGIN PGP …) and the button switches to Decrypt.
- Key selection:
- Default public key loaded from a local
default-public-key.ascfile (gitignored). - Your own public key, provided either by uploading a file (
.asc/.pgp/.gpg/.txt) or by pasting the armored key straight into a textbox.
- Default public key loaded from a local
- Private key decryption:
- Upload an ASCII‑armored private key file, or paste the armored key into the textbox.
- Enter its passphrase if the key is encrypted.
- Decrypts PGP messages entirely in the browser.
- Passphrase convenience (optional):
- Checkbox to save the private key passphrase in the browser’s local storage.
- Never sent to any server.
- Keypair generation (optional):
- Collapsible panel to generate a new RSA‑4096 keypair in the browser using OpenPGP.js.
- Downloads separate
*-public.ascand*-private.ascfiles.
- One-click copy: Copy whatever is currently in the text area (encrypted or decrypted text) to the clipboard.
- Client-side only: All crypto and key handling happen locally in your browser; nothing is transmitted to a backend.
The app is plain static files, but it must be served over http://. Opening
index.html directly as file:// breaks two things: the fetch() of
default-public-key.asc (blocked by CORS) and the OpenPGP.js CDN script, so you
get "OpenPGP.js failed to load."
From the project root:
./dev.shThen open:
http://127.0.0.1:8000
Pass a port to use something other than 8000:
./dev.sh 8080dev.sh uses python3 -m http.server and falls back to npx serve. There is no
build step or watch process - edit index.html, styles.css, or main.js and
reload the page.
- Open your browser devtools console. All failures surface there and in the on-page status line under the buttons.
- Serve with cache disabled (devtools → Network → "Disable cache") when editing
main.js, or hard-reload, so you are not testing a stale script. default-public-key.ascis fetched withcache: "no-store"; if the default key is missing the app says so in the status line and you can still paste or upload a key.
From the project root:
docker compose up --build -dThen open:
http://localhost:8989
ssh-deploy.sh copies index.html, styles.css, and main.js to a server over
scp. Pass the destination as host:path:
./ssh-deploy.sh myserver:pgp-lite
./ssh-deploy.sh user@example.com:/var/www/pgp-litedefault-public-key.asc is not copied, so the key already on the server stays in
place.
If you deploy to the same place every time, drop a deploy.sh next to it that
calls the script with your host. That file is gitignored:
#!/usr/bin/env sh
exec "$(dirname "$0")/ssh-deploy.sh" myserver:pgp-liteTo have a default recipient key available without selecting a file each time:
-
Create a file named
default-public-key.ascin the project root (next toindex.html). -
Paste your ASCII-armored PGP public key into that file:
-----BEGIN PGP PUBLIC KEY BLOCK----- ... -----END PGP PUBLIC KEY BLOCK----- -
This file is listed in
.gitignore, so it will not be committed to the repo.
At runtime, the app will:
- Load
default-public-key.ascautomatically, and - Use it whenever Key source is set to “Use default public key”.
The app can also decrypt messages using your PGP private key:
- In the “Private key (Optional, for decryption)” section:
- Choose Upload file and select your ASCII-armored private key (e.g.
private.asc), or choose Paste key and paste the armored key block into the textbox. - Enter the passphrase for that key if it is encrypted (leave blank if not).
- Choose Upload file and select your ASCII-armored private key (e.g.
- Paste an ASCII-armored PGP message into the “Encrypted output” textarea.
- Click Decrypt.
- The decrypted plaintext will appear in the “Text to encrypt / decrypted output” textarea.