ZyVOP Logo
Content That Connects
SeriesAI NewsLeaderboardWrite for Us
ZyVOP Logo
Content That Connects

Empowering developers and creators with cutting-edge insights, comprehensive tutorials, and innovative solutions for the digital future.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • API Documentation
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

ยฉ 2026 ZyVOP. Crafted with care for the developer community.

Made with โค๏ธ by the ZyVOP team
All systems operational
HomeEncrypting Secrets in Rust Without Writing Crypto Glue Code

Encrypting Secrets in Rust Without Writing Crypto Glue Code

Suradet PS
Suradet PSPharmacist by day. Rustacean by passion.
August 6, 2026
4 min read
Encrypting Secrets in Rust Without Writing Crypto Glue Code
#security#cryptography#encryption#Rust
๐Ÿ‘1

Every Rust developer who needs application-level encryption eventually reaches for crates like aes-gcm or chacha20poly1305. Those crates provide excellent cryptographic primitives - but building a complete encryption workflow still means deciding how to derive keys, generate nonces, encode ciphertext, and store master keys safely.

That's the problem encryptman solves.

What is encryptman?

encryptman is a Rust crate that gives you a dead-simple API for encrypting and decrypting strings - passwords, API keys, tokens, anything you'd rather not store in plaintext.

Under the hood, it uses:

  • AES-256-GCM for authenticated encryption (confidentiality + integrity)

  • HKDF-SHA256 for deriving purpose-specific keys from a single master key

  • Random 12-byte nonces - encrypting the same plaintext twice yields different ciphertexts

  • Zeroize on drop - master key memory is wiped automatically

use encryptman::{encrypt, decrypt, generate_master_key};

let master_key = generate_master_key();

let ciphertext = encrypt(&master_key, "my_database_password")?;let plaintext = decrypt(&master_key, &ciphertext)?;

assert_eq!(plaintext, "my_database_password");

Enter fullscreen mode Exit fullscreen mode

That's it. No algorithm selection. No nonce management. No encoding headaches.

Why not just use aes-gcm directly?

You absolutely can. But here's what you'll need to handle yourself:

  • Key derivation from a master key (HKDF setup)

  • Random nonce generation per encryption call

  • Base64 encoding for safe storage

  • Zeroizing key material from memory

  • Version byte for future algorithm migration

  • Context isolation to prevent cross-domain ciphertext substitution

encryptman bundles all of this into a single, well-documented API with missing_docs = "deny" enforcing complete documentation.

Context isolation

One feature that's easy to overlook: encryptman supports context strings for HKDF key derivation. Different contexts produce different subkeys from the same master key, so ciphertext from one domain can't be decrypted in another. Contexts are implemented using HKDF domain separation rather than additional encryption metadata.

use encryptman::{encrypt_with_context, decrypt_with_context, generate_master_key};

let key = generate_master_key();

// Database passwordslet db_ct = encrypt_with_context(&key, "database", "postgres://secret")?;

// API keyslet api_ct = encrypt_with_context(&key, "api-keys", "sk-12345")?;

// Same plaintext, different contexts -> different ciphertextassert_ne!(db_ct, api_ct);

// Cross-context decryption failsassert!(decrypt_with_context(&key, "database", &api_ct).is_err());

Enter fullscreen mode Exit fullscreen mode

This prevents a whole class of attacks where an attacker swaps ciphertexts between unrelated parts of your application.

URL-safe encoding

Need to put encrypted values in JWTs, cookies, or URLs? encryptman has you covered with Encoding::UrlSafeNoPad:

use encryptman::{encrypt_with_encoding, decrypt_with_encoding, generate_master_key, Encoding};

let key = generate_master_key();

let encrypted = encrypt_with_encoding(&key, "jwt", "token", Encoding::UrlSafeNoPad)?;let decrypted = decrypt_with_encoding(&key, "jwt", &encrypted, Encoding::UrlSafeNoPad)?;

assert_eq!(decrypted, "token");

Enter fullscreen mode Exit fullscreen mode

No more worrying about + and / characters breaking your URLs.

The key storage problem

Here's where things get interesting. encryptman handles encryption beautifully, but it doesn't solve the next question: where do you store the master key?

The README says "use an OS keychain or KMS" - but that still means writing platform-specific code. This is where encryptman-keyring comes in.

encryptman-keyring: Zero file management

encryptman-keyring is a companion crate that stores the master key directly in your OS native credential store:

Platform

Credential Store

Windows

Credential Manager

macOS

Keychain

Linux

Secret Service (DBus)

One call. No key files. No .env headaches. Applications never access the raw master key directly unless they explicitly request it.

use encryptman_keyring::Vault;

// First call: generates key -> stores in OS keychain// Later calls: loads existing key from keychainlet vault = Vault::new("my-app")?;

let encrypted = vault.encrypt("my_database_password")?;let decrypted = vault.decrypt(&encrypted)?;

assert_eq!(decrypted, "my_database_password");

Enter fullscreen mode Exit fullscreen mode

The service name ("my-app") doubles as the HKDF context, so different applications automatically get domain isolation.

The architecture looks like this:

                Application
                     |
                     v
          encryptman-keyring
                     |
      +--------------+--------------+
      v              v              v
   Windows       macOS Keychain   Secret Service
   Credential
   Manager

Enter fullscreen mode Exit fullscreen mode

Multiple contexts with one vault

use encryptman_keyring::Vault;

let vault = Vault::new("my-app")?;

let db_secret = vault.encrypt_with_context("database", "postgres://...")?;let api_secret = vault.encrypt_with_context("api-keys", "sk-12345")?;

assert_ne!(db_secret, api_secret);

let db_plain = vault.decrypt_with_context("database", &db_secret)?;

Enter fullscreen mode Exit fullscreen mode

Separating environments

Need different keys for dev and production? Use new_with_target:

use encryptman_keyring::Vault;

let dev_vault = Vault::new_with_target("my-app", "development")?;let prod_vault = Vault::new_with_target("my-app", "production")?;

Enter fullscreen mode Exit fullscreen mode

Each target stores a separate key in the OS keychain under the same service name.

Migrating from key files

Already have .key files lying around? Import them and delete the originals in one step:

use encryptman_keyring::Vault;

let vault = Vault::migrate_from_file("my-app", std::path::Path::new("/path/to/.key"))?;// File is deleted after successful migration

Enter fullscreen mode Exit fullscreen mode

Typical use cases

encryptman is designed for application-level secrets such as:

  • Database connection strings

  • API keys

  • OAuth refresh tokens

  • SMTP credentials

  • Third-party service secrets

  • Local application configuration

When NOT to use these crates

encryptman is designed for encrypting small strings - passwords, API keys, tokens. It's not suited for:

  • Password hashing - use argon2 or bcrypt

  • File encryption - use a streaming encryption library instead of encrypting the whole file in memory

  • Database-at-rest encryption - use your database's built-in encryption (TDE, disk encryption, etc.)

  • Large data - the entire plaintext is held in memory

encryptman-keyring isn't ideal for:

  • Headless/CI environments - OS keychain may not be available

  • Multi-user servers - keyring entries are per-user; consider Vault or AWS Secrets Manager

The pipeline

master key
    |
    v
HKDF-SHA256("encryptman:{context}")
    |
    v
AES-256-GCM key
    |
    v
plaintext + random nonce
    |
    v
ciphertext
    |
    v
version || nonce || ciphertext
    |
    v
Base64

Enter fullscreen mode Exit fullscreen mode

  • Key Derivation: HKDF-SHA256 generates domain-separated subkeys

  • Authenticated Encryption: AES-256-GCM detects any tampering

  • Fresh Nonce: 12 random bytes per encryption call

  • Version Byte: Reserved for future algorithm migrations

Final thoughts

encryptman intentionally focuses on application secrets rather than exposing low-level cryptographic primitives. If all you need is to encrypt configuration values, API tokens, or passwords, it provides a simple, opinionated API while following modern cryptographic practices.

The ecosystem covers the full workflow:

  • โœ… AES-256-GCM authenticated encryption

  • โœ… HKDF-SHA256 key derivation

  • โœ… Context isolation

  • โœ… OS keychain integration (no key files)

  • โœ… URL-safe encoding for JWTs/cookies

  • โœ… Zeroize on drop

  • โœ… MSRV: Rust 1.85 (Edition 2024)

Just generate a key and start encrypting.


Credit & Reference

  • encryptman GitHub

  • encryptman on crates.io

  • encryptman docs

  • encryptman-keyring GitHub

  • encryptman-keyring on crates.io

  • encryptman-keyring docs

Suradet PS

Suradet PS

Pharmacist by day. Rustacean by passion.

Pharmacist with a passion for coding and continuous learning, always seeking innovative solutions at the intersection of healthcare and technology.

Comments (0)

Login to post a comment.

Related Posts

PREDICTION-20260801-0010

PREDICTION-20260801-0010: ideology-faith-nation [2026-Q3 through 2027-Q4] Created: 2026-08-01 Pattern: ideology-faith-nation Substrate: Commercial end-to-end en...

Read article

Centralized Secret Management with Spring Cloud Config & Vault: A Production Guide

Hardcoding DB credentials, JWT secrets or API keys inside application.yml or Docker images is a security disaster waiting to happen. Learn how to set up a production-grade Spring Cloud Config Server integrated with HashiCorp Vault for zero-downtime secret rotation across microservices.

Read article

The Alpine Mirage: How Upgrading Python Broke My Build and Led to a Truer Security Posture

A deep dive into how upgrading a Python Docker image to Alpine 3.15 caused cascading build failures across C/Rust dependenciesโ€”and why switching to Debian-slim with non-root privileges resulted in a faster, truly secure container architecture.

Read article

Authentication in React: Buy It, and Where the Line Really Is

Authentication always starts as a login form. Two fields and a button. You could build that in an afternoon, and that afternoon is exactly the trap. Because the...

Read article

Payments in React: The Client Never Decides You Got Paid

On the surface, taking a payment looks like the most ordinary thing in the world: a form with some fields, a submit button, a success message. You've built a hu...

Read article