Pure-MoonBit OpenPGP implementation (RFC 4880/9580): email encryption, decryption, signing and verification, interoperable with GnuPG and gopenpgp.
Dependencies
moon add justinwongcn/moonpgpimport {
"justinwongcn/moonpgp/api",
"justinwongcn/moonpgp/mime", // PGP/MIME (RFC 3156) parsing/verification
"justinwongcn/moonpgp/mail", // RFC 5322 / MIME assembly
}git clone https://github.com/justinwongcn/moonpgp
cd moonpgp
moon run cmd/main -- helpmoon check --target all --deny-warn
moon build --target all
moon test --target native # 347 tests
moon fmt --check| Domain | What's implemented |
|---|---|
| ASCII Armor | CRC-24 checksum, multi-block parsing, CRLF tolerance |
| Packet engine | RFC 9580 packet headers (old/new formats, partial lengths), PKESK v3+v6, SKESK v4/v5/v6, PublicKey/SecretKey/SecretSubkey (v4+v6), UserID, Signature v4+v6 (subpacket parsing), OnePassSig v3+v6, Literal, Padding, Compressed, SEIPD v1+MDC and v2 |
| Symmetric encryption | AES-128/192/256, OpenPGP-CFB (both the resync and no-resync variants), MDC integrity protection, and the v2 SEIPD chunked-AEAD layer (salt + HKDF message key/IV, 0xD2 header, per-chunk AD, final length-bound tag) |
| S2K | Simple / Salted / Iterated+Salted (SHA-256/SHA-1/RIPEMD-160/MD5) and Argon2 (type 4, RFC 9580 §3.7.1.4) |
| Argon2 primitives | Self-implemented BLAKE2b (RFC 7693), Argon2id (RFC 9106, v=0x13) and HKDF-SHA256 (RFC 5869) in primitives, with an explicit memory cap before allocating |
| RSA | PKCS#1 v1.5 encryption, signing, verification; key generation (Miller-Rabin, CRT, extended-Euclid modular inverse) |
| Ed25519 | EdDSA legacy (algorithm 22) signing/verification and key generation (self-implemented on @bigint, pki/ed25519.mbt) |
| ECDH / X25519 | X25519 v6 (algorithm 25) native-key wrap/unwrap: HKDF-SHA256 with info = "OpenPGP X25519" + AES-128 Key Wrap, v6 PKESK by fingerprint; Curve25519Legacy (algorithm 18) X25519 + SHA-256 KDF (§11.4) + AES Key Wrap (RFC 3394) with the PKESK v3 ephemeral-point format |
| Key management | Transferable-key assembly/parsing, creation and verification of self-signatures and subkey binding signatures, passphrase locking/unlocking (usage 254 CFB, and usage 253 AEAD + HKDF with Argon2id or iterated S2K), armored import/export, keyring |
| AEAD modes | OCB (RFC 7253, MTI), EAX and GCM (RFC 9580 §5.13.3–5.13.5), all three usable for v2 SEIPD, v5/v6 SKESK and usage-253 secret keys |
| Compression | Compressed packet: ZIP (raw DEFLATE) / ZLIB / uncompressed; the decompressor fully supports dynamic Huffman |
| MIME | PGP/MIME (RFC 3156): multipart/signed construction + verification, multipart/encrypted parsing, RFC 5322 header parsing; email pipeline aligned with gopenpgp v3 mime/: Content-Type parameter parsing, Content-Transfer-Encoding decoding (base64/quoted-printable), byte-exact MIME tree parsing, body/attachment collection (gomime semantics), one-shot decrypt_mime with callback delivery and gopenpgp signature-status merge (Ok/NotSigned/NoVerifier/Failed) |
| Email assembly (library) | mail: RFC 5322 headers (Date/Message-ID/atext-quoting addresses, RFC 2047 encoded words), multipart/mixed with the RFC 3156 §3 7-bit strategy (ASCII→7bit / non-ASCII→QP / attachments base64), generalized multipart/signed over pre-built entities, one-shot build_encrypted_mail — the composing half of PGP/MIME, symmetric with mime parsing |
| v6 message encryption | v6 PKESK + v2 SEIPD for v6 X25519 subkeys, v6 SKESK + v2 SEIPD for passphrases, honoring the recipient's Preferred AEAD Ciphersuites; generate_ed25519_v6_key now emits the X25519 encryption subkey with a v6 binding signature |
| Top-level API | pgp().encryption()/decryption()/key_generation() builders, KeyRing |
./scripts/interop_check.shmoon run cmd/main -- keygen "Demo <demo@example.com>" sec.asc pub.asc # RSA key (default 3072 bits)
moon run cmd/main -- edkeygen "Demo <demo@example.com>" sec.asc pub.asc # Ed25519 + cv25519 key
moon run cmd/main -- encpass "passphrase" plain.txt msg.asc # passphrase encrypt
moon run cmd/main -- decpass "passphrase" msg.asc restored.txt # passphrase decrypt
moon run cmd/main -- encpub pub.asc plain.txt msg2.asc # public-key encrypt (RSA/ECDH auto)
moon run cmd/main -- decpriv sec.asc msg2.asc restored2.txt # private-key decrypt
moon run cmd/main -- sign sec.asc plain.txt sig.asc # detached signature
moon run cmd/main -- verify pub.asc plain.txt sig.asc # verify// passphrase encrypt/decrypt
///|
let armored = @api.encrypt_with_password(plaintext, "passphrase")
///|
let plain = @api.decrypt_with_password(armored, "passphrase")
// key generation + public-key encrypt / private-key decrypt
///|
let key = @api.generate_key("Alice <alice@example.com>")
///|
let armored = @api.encrypt_to_recipients(data, [key.to_public()])
///|
let plain = @api.decrypt_with_private_key(armored, key)
// gopenpgp-style builders
///|
let encrypt = @api.pgp().encryption().password("pw").finish()
///|
let decrypt = @api.pgp().decryption().password("pw").finish()primitives/ # isolation layer: the only third-party crypto entry point; BlockCipher trait, HashId, asserted entropy
armor/ # CRC-24 + armor (PGP-specific, never replaced)
s2k/ # S2K (PGP-specific, never replaced)
packet/ # packet format engine (PGP-specific, never replaced)
cipher/ # OpenPGP-CFB variants + SEIPD v1/MDC + AES Key Wrap (RFC 3394)
pki/ # RSA (keygen/PKCS#1 v1.5), Ed25519/X25519 (self-implemented per RFC 8032/7748, `curve25519.mbt`)
compress/ # DEFLATE/ZLIB (currently moonbit-community/flate, replaceable)
mime/ # PGP/MIME (RFC 3156), MIME tree parsing/collectors, transfer-encoding decoding
mail/ # RFC 5322 mail assembly (headers, QP/7bit strategy, attachments) — no transport
api/ # key management, signing, ECDH/RSA session-key wrapping, keyring, builders
interop/ # interop tests against gopenpgp/GnuPG/openssl// cleartext signing (RFC 9580 §7)
let msg = @api.sign_cleartext("text\n", key)
let (text, status) = @api.verify_cleartext(msg, key.to_public())
// PGP/MIME (RFC 3156)
let mime_msg = @mime.build_pgp_mime_signed("body\n", key)
let (text, status) = @mime.verify_pgp_mime_signed(mime_msg, key.to_public())
let payload = @mime.extract_pgp_encrypted(mime_msg) // extract the PGP MESSAGE
// Ed25519 primary key (EdDSA legacy, algorithm 22) + Curve25519Legacy ECDH
// encryption subkey — the gopenpgp "Default" profile shape, verified live
// against gpg in both directions
let key = @api.generate_ed25519_key("Ed <ed@example.com>")
// Argon2 S2K (RFC 9580 §3.7.1.4, specifier type 4) — the RFC's recommended
// passphrase KDF, in the gopenpgp RFC 9580 profile's default shape
// (t=3, p=4, m=2^16 KiB); unlock() handles usage 253 and 254 alike.
let locked = key.lock_argon2("passphrase", memory_exp=16)
let unlocked = locked.unlock("passphrase")
let msg = @api.encrypt_with_password_argon2(plaintext, "passphrase")
let back = @api.decrypt_with_password(msg, "passphrase")
// Email (MIME) support aligned with gopenpgp v3's mime/ domain:
// decrypt an armored PGP mail, walk the MIME tree, verify a root
// multipart/signed and deliver body + attachments via callbacks
// (gpg cross-verified end-to-end; charset support is UTF-8/lossy).
let recorder = ... // any value implementing @mime.MimeCallbacks
@mime.decrypt_mime(
armored_mail, // armored PGP message (RFC 3156 payload)
[private_key],
verifiers=[signer_public_key],
callbacks=recorder, // on_body / on_attachment / on_verified / on_error
)
// Building blocks are public too: Content-Type parameter parsing,
// Content-Transfer-Encoding decoding (base64/quoted-printable), and a
// byte-exact MIME tree parser with a gomime-compatible collector.
let root = @mime.parse_mime_tree(payload_bytes)
let (body, attachments) = @mime.collect_body_and_attachments(root)
// One-shot mail composition (RFC 3156 §6.1: sign then encrypt) — run
// `./example/mailer/e2e.sh` for the gpg-verified end-to-end demo.
let mail = @mail.build_encrypted_mail(
from, to,
subject=..., text=..., attachments=...,
signer=private_key, recipients=[public_key],
domain="mailpgp.example",
)Install
Download zipPure-MoonBit OpenPGP implementation (RFC 4880/9580): email encryption, decryption, signing and verification, interoperable with GnuPG and gopenpgp.
Dependencies