v1.21.0 Docs

Xipher Documentation

Xipher is a lightweight, server-free tool for password-based asymmetric encryption. Share sensitive data over any channel (the CLI, your browser, a Go program, or WebAssembly) without trusting a server with your secrets.

Introduction

Xipher lets two parties exchange encrypted data over an insecure channel using asymmetric encryption. A receiver shares a public key (often derived from a password); a sender encrypts data against that public key; only the receiver, who holds the matching secret key or password, can decrypt it.

It is available in several forms that all share the same cryptography and wire format:

  • CLI: a single binary for Linux, macOS, and Windows.
  • Web app: runs entirely in your browser via WebAssembly. Nothing is sent to a server.
  • Go library: xipher.org/xipher for embedding in your apps.
  • WebAssembly module: call Xipher directly from JavaScript.
  • GitHub Action: bring Xipher into your CI pipelines.

Core concepts

Xipher uses a handful of string types, each with a recognizable prefix:

Prefix Type Meaning
XSK_ Secret key Private. Decrypts data. Never share it.
XPK_ Public key Safe to share. Senders encrypt with it.
XCT_ Ciphertext Encrypted data, safe to send over any medium.

There are two ways to obtain a secret key:

  • Password-based: derived from a password using Argon2id. Convenient and portable: the same password always yields the same identity, so you can decrypt on any device.
  • Random: generated from 64 bytes of secure randomness (XSK_…). Maximum entropy, but you must store it safely, since it can't be recovered if lost.
💡

Enable quantum-safe mode when deriving a public key to use ML-KEM (Kyber-1024) alongside Curve25519, protecting against future quantum attacks. It produces larger keys and ciphertext.

Quick start

With the CLI

# Encrypt a message with a password (you'll be prompted)
xipher encrypt text -t "Hello, World!"

# Decrypt it back
xipher decrypt text -c "XCT_..."

In the browser

  1. Open the web app. A key pair is generated and stored in your browser automatically.
  2. Copy your shareable link and send it to whoever should send you a secret.
  3. They open the link, type their message, and click Encrypt.
  4. They send the resulting ciphertext (or encrypted link) back to you.
  5. You paste it into the same browser and click Decrypt.

CLI

Installation

Homebrew (macOS):

brew install --cask shibme/tap/xipher

Install script (Linux / macOS):

# Latest version
curl -fsSL https://xipher.org/install/install.sh | sh

# Specific version
curl -fsSL https://xipher.org/install/install.sh | sh -s v1.21.0

Install script (Windows / PowerShell):

# Latest version
irm https://xipher.org/install/install.ps1 | iex

# Specific version
$v="1.21.0"; irm https://xipher.org/install/install.ps1 | iex

Docker:

docker run --rm -v $PWD:/data -it shibme/xipher help

Generating keys

# Derive a public key from a password (you'll be prompted)
xipher keygen

# Auto-generate a random secret key and show its public key
xipher keygen --auto

# Quantum-safe public key
xipher keygen --quantum-safe

# Write the public key to a file
xipher keygen --public-key-file mykey.xpk
FlagShortDescription
--auto-aAuto-generate a secret key
--quantum-safe-qUse quantum-safe cryptography
--public-key-file-pPath to write the public key file
--ignore-password-policySkip the password strength check

Encrypting

# Encrypt text (prompts for a key/password if -k is omitted)
xipher encrypt text -t "Hello, World!"

# Encrypt text against a public key
xipher encrypt text -k "XPK_..." -t "Secret message"

# Read the plaintext from stdin
echo "piped secret" | xipher encrypt text -t -

# Encrypt a file
xipher encrypt file -k "XPK_..." -f report.pdf -o report.pdf.xipher

# Encrypt a stream (stdin -> stdout)
cat backup.tar | xipher encrypt stream -k "XPK_..." > backup.tar.xipher
FlagShortDescription
--key-kPublic key, secret key, or password
--text-tText to encrypt (- reads stdin)
--file-fPath to the input file
--out-oPath to the output file
--compress-cCompress data before encryption
--xiphertextEncode output as Xipher text

Decrypting

You are prompted for the secret key or password unless it is set via the XIPHER_SECRET environment variable.

# Decrypt text
xipher decrypt text -c "XCT_..."

# Decrypt a file (output path inferred by stripping .xipher)
xipher decrypt file -f report.pdf.xipher

# ...or set an explicit output path
xipher decrypt file -f report.pdf.xipher -o report.pdf

# Decrypt a stream (stdin -> stdout)
cat backup.tar.xipher | xipher decrypt stream > backup.tar
FlagShortDescription
--ciphertext-cCiphertext to decrypt (text only)
--file-fPath to the encrypted input file
--out-oPath to the output file (inferred if omitted)
--overwriteOverwrite the output file if it exists

Environment & JSON output

For non-interactive use (CI, scripts), provide your secret key or password via the XIPHER_SECRET environment variable. The stream subcommands require it.

export XIPHER_SECRET="XSK_..."   # or a password
cat data.bin | xipher encrypt stream -k "XPK_..." > data.bin.xipher
cat data.bin.xipher | xipher decrypt stream > data.bin

# Machine-readable output (works on any command)
xipher keygen --auto --json

# Version and help
xipher version
xipher encrypt --help

The global --json / -j flag emits structured output (and errors) as JSON on any command. Every command also accepts --help / -h.

Web app

The web app at xipher.org performs all cryptography locally using WebAssembly. Your keys are stored encrypted in your browser's local storage and never transmitted anywhere.

🔒

It works offline and can be installed as a Progressive Web App (PWA) on desktop and mobile. Once loaded, no network connection is required to encrypt or decrypt.

Receiving a secret

When you open the app fresh, you are the receiver. The app shows a shareable link containing your public key. Anyone who opens that link can encrypt data that only you can decrypt.

  1. Copy the link with the button, or share it with the button.
  2. When you receive ciphertext back, paste it into the text box and press Decrypt.

Sending a secret

When you open someone else's shareable link, you become the sender. The app loads their public key automatically and any text or file you encrypt is locked to their identity.

  1. Type your message or pick a file.
  2. Press Encrypt.
  3. Copy or share the resulting ciphertext / link back to the receiver.

Keys & passwords

By default the web app generates a random secret key the first time you visit. To use the same identity across devices, open ⚙️ Settings in the top bar and choose one of:

  • Password: set a password; the same password recreates the same identity anywhere.
  • Secret key: paste an existing XSK_… key (e.g. generated by the CLI).
  • Random: generate a fresh, ephemeral key for one-off use.

Settings also has a quantum-safe toggle under Encryption. Because the public key is always re-derived from your stored identity, you can switch it on or off at any time, and your shareable link updates immediately.

⚠️

Changing your key or password changes your public link. Secrets encrypted to your previous identity can still be decrypted only with that previous key or password.

Encrypting files

Drag & drop a file onto the text box, or use Pick a file. Files are processed as a stream, so even large files are handled efficiently without loading them fully into memory.

  • Encrypted files are saved with a .xipher extension.
  • To decrypt, drop a .xipher file and press Decrypt.

Go library

go get -u xipher.org/xipher

Usage

package main

import (
	"fmt"
	"xipher.org/xipher"
)

func main() {
	// Create a secret key from a password
	secretKey, err := xipher.NewSecretKeyForPassword([]byte("your-secure-password"))
	if err != nil {
		panic(err)
	}

	// Derive the public key (pass true for quantum-safe)
	publicKey, err := secretKey.PublicKey(false)
	if err != nil {
		panic(err)
	}

	// Encrypt with the public key (compress = true, encode = true)
	ciphertext, err := publicKey.Encrypt([]byte("Hello, World!"), true, true)
	if err != nil {
		panic(err)
	}

	// Decrypt with the secret key
	plaintext, err := secretKey.Decrypt(ciphertext)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Decrypted: %s\n", plaintext)
}

To use a random key instead of a password:

secretKey, _ := xipher.NewSecretKey()
keyStr, _ := secretKey.String()           // export "XSK_..."
imported, _ := xipher.ParseSecretKeyStr(keyStr) // re-import

Streams

For large data, encrypt and decrypt with streams to keep memory usage low:

// Encrypt: src -> dst, compress = true, encode = true
err = publicKey.EncryptStream(dst, src, true, true)

// Decrypt: src -> dst
err = secretKey.DecryptStream(dst, src)

Full API reference: pkg.go.dev/xipher.org/xipher.

WebAssembly

Load the Xipher WASM module to call its functions directly from JavaScript. Every exported function returns an object shaped like { result } or { error }.

<script src="https://xipher.org/wasm/wasm_exec.js"></script>
<script>
  const go = new Go();
  WebAssembly.instantiateStreaming(
    fetch("https://xipher.org/wasm/xipher.wasm"),
    go.importObject
  ).then((result) => {
    go.run(result.instance);
  });
</script>
// Generate a random secret key
const sk = await xipherNewSecretKey();          // { result: "XSK_..." }

// Derive a public key (second arg = quantum-safe)
const pk = await xipherGetPublicKey(sk.result, false);

// Encrypt and decrypt a string
const ct = await xipherEncryptStr(pk.result, "Hello");
const pt = await xipherDecryptStr(sk.result, ct.result);
console.log(pt.result); // "Hello"
FunctionPurpose
xipherNewSecretKey()Generate a random secret key
xipherGetPublicKey(secret, quantumSafe)Derive a public key
xipherEncryptStr(key, text)Encrypt a string
xipherDecryptStr(secret, ct)Decrypt a string

GitHub Action

Set up the Xipher CLI in a workflow:

steps:
  - name: Setup Xipher
    uses: shibme/xipher@v1
    with:
      version: 1.21.0  # optional

  - name: Decrypt a secret
    env:
      XIPHER_SECRET: ${{ secrets.XIPHER_SECRET }}
    run: |
      echo "$ENCRYPTED" | xipher decrypt stream > secret.txt

Self-hosting the web app

Publish your own copy of the web interface to GitHub Pages using the reusable workflow:

name: Publish Xipher Web
on:
  workflow_dispatch:
jobs:
  pages:
    uses: shibme/xipher/.github/workflows/pages.yaml@main

The web app is fully static, so you can also serve the web/ directory from any static host. It must be served over HTTPS for the service worker and clipboard APIs to work.

Security

  • Algorithms: Argon2id (key derivation), Curve25519 with ephemeral key exchange, ML-KEM / Kyber-1024 (post-quantum, optional), XChaCha20-Poly1305 (symmetric), Zlib (compression).
  • Password strength matters: password-based keys are only as strong as the password. Prefer long passphrases.
  • Random keys can't be recovered: store XSK_… keys safely.
  • Compression can leak information about plaintext patterns; use it thoughtfully.
⚠️

This project is experimental. See the architecture section for cryptographic details, and report security issues through GitHub security advisories.

FAQ

Where are my keys stored in the web app?

Encrypted in your browser's local storage on your device. They are never transmitted to any server.

Can I decrypt on a different device than I encrypted on?

Yes, if you use a password or the same secret key. The default random key lives only in the browser that generated it, so set a password via ⚙️ Settings to go cross-device.

Is there a size limit for files?

No hard limit. Files are streamed, so size is bounded only by your device and browser.

Are CLI and web outputs compatible?

Yes. All Xipher implementations share the same wire format. Ciphertext from one decrypts in any other with the right key.

Architecture

Xipher enables password-based asymmetric encryption: public keys can be derived from a memorable password, with no certificate authority or PKI to manage. It uses a hybrid KEM + DEM scheme: a key-exchange layer establishes a shared symmetric key, which a stream cipher then uses to encrypt the data in chunks for constant-memory processing of any size.

LayerMechanism
Key derivationArgon2id (password) or 64 bytes from crypto/rand (direct)
Key exchange (KEM)Curve25519 (X25519), or ML-KEM / Kyber-1024 in quantum-safe mode
Data encryption (DEM)XChaCha20-Poly1305 AEAD over 64 KB chunks, optional zlib
EncodingBase32 with an XCT_ prefix (optional, for text transport)

Primitives

RolePrimitiveParameters
Stream cipherXChaCha20-Poly1305256-bit key, 192-bit nonce, 128-bit tag
Classical KEXCurve25519 (X25519)32-byte keys, ephemeral (forward secrecy)
Post-quantum KEXML-KEM / Kyber-1024NIST Level 5, 1568-byte key & ciphertext
Password KDFArgon2idDefault: 16 iterations, 64 MB, 1 thread
Compressionzlib (DEFLATE)Best compression, applied before encryption
ModeClassicalQuantumPublic key
ECC (Curve25519)~128-bitnone32 bytes
Quantum-safe (Kyber-1024)~256-bit~230-bit1568 bytes

The 64-byte secret key seeds every algorithm: the ECC private key is SHA-256 of the base key, while Kyber uses the base key as its seed. Password-based secret keys are never serialized; only the public key they derive can be shared.

Data format

Ciphertext begins with a one-byte type tag, optionally followed by a 19-byte KDF spec (iterations, memory, threads, 16-byte salt) for password-based keys. The key-exchange material and a 24-byte nonce come next, then a compression flag, then the AEAD-encrypted chunks.

[type] [KDF spec?] [KEX material] [nonce] [compress flag] [chunks…]

KEX material   ECC  : 1-byte algo + 32-byte ephemeral public key
               Kyber: 1-byte algo + 1568-byte encapsulation
chunk          64 KB ciphertext + 16-byte Poly1305 tag (last chunk shorter)
encoded form   "XCT_" + Base32(binary)   # ~1.6× larger, text-safe

Every chunk shares the session nonce, which is safe because each encryption generates a fresh random nonce against a fresh key (ephemeral for asymmetric, freshly derived for password modes), and the 192-bit nonce space makes collisions infeasible.

Security analysis

  • Confidentiality & integrity: XChaCha20-Poly1305 is AEAD; the Poly1305 tag is verified per chunk, so tampering is caught immediately rather than after full decryption.
  • Forward secrecy: asymmetric encryption uses a fresh ephemeral key per operation, so compromising one message's key does not expose others.
  • Quantum resistance: ECC mode is vulnerable to Shor's algorithm; enable quantum-safe mode (Kyber-1024, Module-LWE) for long-term security.
  • Password strength: password-based keys are only as strong as the password. Argon2id is memory-hard to resist GPU/ASIC attacks, but weak passwords remain dictionary-attackable, so prefer long passphrases.
  • Compression leakage: compressing before encryption can leak information about plaintext patterns (CRIME-style). Use it thoughtfully on sensitive data.

Standards: FIPS 203 (ML-KEM), RFC 8439 (ChaCha20-Poly1305), RFC 7748 (Curve25519), RFC 9106 (Argon2). All randomness comes from crypto/rand and all primitives use constant-time implementations from the Go standard library.