Overview
The Paillier cryptosystem is a probabilistic, asymmetric encryption scheme with a remarkable property: arithmetic can be performed directly on encrypted data — no decryption required. The server performing the computation never sees the underlying values.
This implementation exposes a set of CLI commands under the crypto topic that let you
generate keys, encrypt and decrypt integers and strings, and perform addition, subtraction, and
scalar multiplication on ciphertext.
Add, subtract, and multiply (by a scalar) without decrypting.
Keys use 2048-bit primes, matching NIST minimum for asymmetric encryption.
Each encryption of the same value produces a different ciphertext.
Keys are stored as human-readable JSON with BigInteger fields.
How the Paillier Cryptosystem Works
Key Components
Paillier encryption uses four mathematical values. Two form the public key (safe to share) and three form the private key (must be kept secret).
| Symbol | Name | Visibility | Description |
|---|---|---|---|
n |
Modulus | Public | Product of two large primes p × q. All arithmetic is performed mod n². |
g |
Generator | Public | Fixed as n + 1 in this implementation (a common simplification). |
λ |
Lambda | Private | lcm(p-1, q-1). Used as the decryption exponent. |
μ |
Mu | Private | Modular inverse of λ mod n. Scales the result during decryption. |
Encryption
To encrypt a plaintext integer m, a fresh random value r (coprime with n)
is chosen. The ciphertext is:
Because r is random and discarded, encrypting the same m twice produces
different ciphertexts — this is the probabilistic nature of the scheme.
Decryption
Given the private key, the original plaintext is recovered in two steps. First, the L-function
removes the randomness; then μ rescales the result:
Homomorphic Addition
Multiplying two ciphertexts together in the encrypted domain produces the ciphertext of their plaintext sum:
Homomorphic Subtraction
Multiplication by the modular inverse of a ciphertext computes the difference:
Homomorphic Scalar Multiplication
Raising a ciphertext to an integer power multiplies the plaintext by that scalar:
Note: this is multiplication by a known plaintext scalar, not by another encrypted value. Fully encrypted multiplication requires a different (more expensive) scheme.
Key Generation
The generate-key command creates a fresh Paillier key pair and writes two JSON files:
a public key (safe to distribute) and a private key (keep secret).
Public key file (public.json)
Contains only N (the modulus). Safe to share freely.
Private key file (private.json)
Contains N, Lambda, and Mu. Written with owner-only
Unix permissions (600) and must never be shared.
Numeric Operations
Encrypt a number
Takes a non-negative integer and a public key file. Writes the ciphertext to a JSON file.
Decrypt a number
Reads an encrypted number file and private key, writes the plaintext to stdout.
Homomorphic Operations on Encrypted Numbers
These commands operate entirely on ciphertext. No private key is needed and the plaintext values are never revealed to the process performing the computation.
add pbtk crypto add
Multiplies the two ciphertexts together. Decrypting the result yields a + b.
subtract pbtk crypto subtract
Multiplies the first ciphertext by the modular inverse of the second.
multiply pbtk crypto multiply
Raises the ciphertext to the power of a known plaintext scalar k.
String Operations
Strings are encrypted character-by-character: each Unicode code point is independently encrypted as an integer using the Paillier scheme. The result is a JSON array of encrypted numbers, one per character. This representation enables position-aware operations on the ciphertext without decryption.
encrypt-string
Encrypts a UTF-8 string as an array of encrypted Unicode code points.
decrypt-string
Decrypts each code point and reconstructs the original string.
replace
Overwrites characters at a known position in an encrypted string without decrypting it. The replacement plaintext is re-encrypted with the same public key (recovered from the ciphertext metadata) and substituted into the array at the specified index.
substring
Slices an encrypted string by position, returning a new encrypted array of the selected range. No private key is needed.
Command Reference
| Command | Required options | Optional options | Output |
|---|---|---|---|
crypto generate-key |
--public-key-file --private-key-file |
— | Two JSON files (public + private key) |
crypto encrypt |
--number --public-key-file --out-file |
— | Encrypted number JSON file |
crypto decrypt |
--in-file --private-key-file |
— | Plaintext integer (stdout) |
crypto add |
--in-file1 --in-file2 --out-file |
— | Encrypted sum JSON file |
crypto subtract |
--in-file1 --in-file2 --out-file |
— | Encrypted difference JSON file |
crypto multiply |
--in-file --scalar --out-file |
— | Encrypted product JSON file |
crypto encrypt-string |
--string --public-key-file --out-file |
— | Encrypted string JSON file (array of encrypted code points) |
crypto decrypt-string |
--in-file --private-key-file |
— | Plaintext string (stdout) |
crypto replace |
--in-file --start --replacement --out-file |
— | Updated encrypted string JSON file |
crypto substring |
--in-file --start --out-file |
--length (defaults to end of string) |
Encrypted substring JSON file |
Constraints and Limits
| Constraint | Value | Reason |
|---|---|---|
| Minimum key size | 2048 bits | NIST minimum recommendation for asymmetric security |
| Plaintext range (numbers) | 0 ≤ m < n | Paillier requires the message to be smaller than the modulus |
| Plaintext integers | Non-negative only | Paillier works in Z/nZ; negative values are not natively supported |
| Max string length | 100 characters | Each character requires one full 2048-bit ciphertext; enforced to limit file size |
| String character set | Full Unicode (UTF-32 code points) | Characters are stored as Unicode scalar values, supporting any language |
| Scalar multiplier | Non-negative integer | Negative scalars not supported; multiply by 0 produces Enc(0) |