<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Suradet PS</title>
    <description>The latest articles on DEV Community by Suradet PS (@suradet-ps).</description>
    <link>https://dev.to/suradet-ps</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3374104%2F1c0b82a7-ed17-4c5c-a613-dc463ab21670.jpg</url>
      <title>DEV Community: Suradet PS</title>
      <link>https://dev.to/suradet-ps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/suradet-ps"/>
    <language>en</language>
    <item>
      <title>CodeFrame: Turning Source Code Into Beautiful Images, Right In Your Browser</title>
      <dc:creator>Suradet PS</dc:creator>
      <pubDate>Sat, 15 Aug 2026 04:00:15 +0000</pubDate>
      <link>https://dev.to/suradet-ps/codeframe-turning-source-code-into-beautiful-images-right-in-your-browser-4j1e</link>
      <guid>https://dev.to/suradet-ps/codeframe-turning-source-code-into-beautiful-images-right-in-your-browser-4j1e</guid>
      <description>&lt;p&gt;Every developer who has ever needed to share code in a slide deck, a blog post, or a GitHub issue has reached for the same two tools: a screenshot, or a manual copy into a presentation tool. Screenshots are blurry and fixed-size. Manual copies lose syntax highlighting and formatting. And the popular web tools that render code to images? They cap out at 2x, because they're secretly taking screenshots of the DOM.&lt;/p&gt;

&lt;p&gt;That's the problem &lt;strong&gt;CodeFrame&lt;/strong&gt; solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is CodeFrame?
&lt;/h2&gt;

&lt;p&gt;CodeFrame is a web app that converts source code into high-resolution PNG (or SVG) images, in the style of carbon.now.sh and ray.so, but with one hard architectural difference: it never screenshots the DOM.&lt;/p&gt;

&lt;p&gt;Under the hood, it uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Canvas2D rendering via WebAssembly&lt;/strong&gt;: every token is drawn pixel-by-pixel with &lt;code&gt;fillText&lt;/code&gt;, at whatever scale you ask for&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;syntect&lt;/code&gt;&lt;/strong&gt; (the same engine behind Sublime Text's syntax highlighting) compiled to WASM with the &lt;code&gt;fancy-regex&lt;/code&gt; backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7 bundled themes&lt;/strong&gt;: Dracula, One Dark, Nord, GitHub Light, Tokyo Night, Catppuccin Mocha, Monokai&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 bundled monospace fonts&lt;/strong&gt;: JetBrains Mono, Fira Code, Cascadia Code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero backend&lt;/strong&gt;: no server, no accounts, no telemetry. Your code never leaves your browser
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;codeframe_renderer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;compute_layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;codeframe_renderer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;codeframe_models&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ExportOptions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExportOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// user-selectable, not capped at 2x&lt;/span&gt;
    &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;48.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;window_frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;line_numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="nn"&gt;ExportOptions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// draw(ctx, tokens, palette, options) paints the whole card:&lt;/span&gt;
&lt;span class="c1"&gt;// background -&amp;gt; frame -&amp;gt; traffic lights -&amp;gt; token text -&amp;gt; line numbers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole point. No DOM screenshotting. No &lt;code&gt;html2canvas&lt;/code&gt;. No resolution ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just screenshot the page?
&lt;/h2&gt;

&lt;p&gt;You absolutely can. But here's what you're accepting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your resolution is capped at &lt;code&gt;devicePixelRatio&lt;/code&gt;&lt;/strong&gt; (typically 2x, occasionally 3x). A 2x screenshot of a 1200px-wide card gives you 2400px. CodeFrame gives you 8x (or custom scales up to 12x) because the canvas backing store is &lt;em&gt;created&lt;/em&gt; at full size: &lt;code&gt;canvas.width = logical_width * scale&lt;/code&gt;, then one &lt;code&gt;ctx.scale()&lt;/code&gt; call, and everything draws in logical coordinates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fonts get rasterized at 2x too&lt;/strong&gt;: text edges stay soft no matter how much you "upscale" later. &lt;code&gt;fillText&lt;/code&gt; on a canvas at 8x produces genuinely crisp glyph edges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backgrounds, shadows, and gradients are baked in&lt;/strong&gt;: a screenshot captures whatever the browser happened to composite, including scrollbars, focus rings, and cursor blink states.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CodeFrame draws the entire card from scratch: background gradient, rounded card, macOS traffic lights, padding, and one &lt;code&gt;fillText&lt;/code&gt; per token with the exact theme colors. The output is deterministic: the same input always produces the same image.&lt;/p&gt;

&lt;h2&gt;
  
  
  The resolution rules (the core differentiator)
&lt;/h2&gt;

&lt;p&gt;These four rules are load-bearing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Canvas pixel size = logical size × export_scale&lt;/strong&gt;, computed up-front, never rendered small and upscaled later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export scale is user-selectable&lt;/strong&gt;: 1x, 2x, 4x, 8x, or a custom value, with a "target width" mode that computes the scale for you (1200px for Twitter, 1920px for a slide).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview and export use separate canvases.&lt;/strong&gt; The preview renders at a screen-friendly scale (capped at 2x, clamped against &lt;code&gt;devicePixelRatio&lt;/code&gt;) so typing stays smooth; a fresh full-scale canvas is created only when the export button is pressed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;document.fonts.ready&lt;/code&gt; is awaited before every draw&lt;/strong&gt; (preview and export), so a not-yet-loaded font can never silently fall back to a system font mid-export.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="nf"&gt;.set_width&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;logical_width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="nf"&gt;.set_height&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;logical_height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="nf"&gt;.scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// draw in normal logical coordinates from here on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And exports use &lt;code&gt;canvas.toBlob("image/png")&lt;/code&gt;, never &lt;code&gt;toDataURL&lt;/code&gt;. At 8x, the PNG can be tens of megabytes in memory; &lt;code&gt;toDataURL&lt;/code&gt; would base64-encode the whole buffer, wasting roughly a third of your memory for no reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering layer order
&lt;/h2&gt;

&lt;p&gt;Every frame is drawn bottom-to-top in a fixed order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Background gradient or solid color
2. Code card: rounded rect + drop shadow
3. macOS traffic-light dots (if window frame is on)
4. Padding area
5. Token text: fillText per token, x-cursor advanced manually
6. Line numbers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note step 5: text wrapping is &lt;em&gt;not&lt;/em&gt; left to the browser. Each token's width is measured, the cursor advances by exactly the glyph widths, and the renderer controls every pixel. That's what makes the output identical at 1x and 8x.&lt;/p&gt;

&lt;h2&gt;
  
  
  A live editor that mirrors the export
&lt;/h2&gt;

&lt;p&gt;CodeFrame's code input is a full syntax-highlighted editor: a transparent-text textarea overlaid on a &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; rendered from the same &lt;code&gt;syntect&lt;/code&gt; token stream that drives the canvas. What you type matches what you export, down to the theme's exact colors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;codeframe_highlighter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;highlight_to_html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;codeframe_models&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ThemeChoice&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;highlight_to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fn main() {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Rust&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;ThemeChoice&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Dracula&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;span style="color:#ff79c6;"&amp;gt;fn&amp;lt;/span&amp;gt;&amp;lt;span ...&amp;gt; main() {}&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The editor background and caret color come from the theme palette itself, so switching from Dracula to GitHub Light restyles the whole input box. Both layers share identical font metrics, wrapping rules, and scroll positions, so the highlight is always pixel-aligned with the caret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond PNG: SVG, split screens, and presets
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SVG export&lt;/strong&gt;: the same layout math emits &lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;tspan&amp;gt;&lt;/code&gt; elements, so the result scales infinitely for docs and slides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split-screen comparison&lt;/strong&gt;: two code panels side-by-side, each with its own code, theme, and language, exported as a single image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy to clipboard&lt;/strong&gt;: one click puts the PNG on the clipboard for pasting straight into Slack or Figma.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background presets&lt;/strong&gt;: snow white, top glow, bottom glow, left beam, right beam, plus padding, corner radius, line height, and font-size controls, all live.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the crate boundaries matter
&lt;/h2&gt;

&lt;p&gt;CodeFrame is a Cargo workspace with four crates, split so the hard parts are testable without a browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;models ──► highlighter ──► renderer
   │            │              │
   └────────────┴──────────────┘
                │
                ▼
               app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;models&lt;/code&gt;&lt;/strong&gt;: shared types (&lt;code&gt;Token&lt;/code&gt;, &lt;code&gt;ExportOptions&lt;/code&gt;, &lt;code&gt;Background&lt;/code&gt;), zero dependencies beyond &lt;code&gt;serde&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;highlighter&lt;/code&gt;&lt;/strong&gt;: wraps &lt;code&gt;syntect&lt;/code&gt;; takes &lt;code&gt;&amp;amp;str&lt;/code&gt; + &lt;code&gt;Language&lt;/code&gt; and returns &lt;code&gt;Vec&amp;lt;Token&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;renderer&lt;/code&gt;&lt;/strong&gt;: pure canvas-drawing logic; the layout math has no web-sys at all and is fully unit-tested&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;app&lt;/code&gt;&lt;/strong&gt;: the only crate that knows Leptos: components, signals, event handlers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The token stream is the contract: &lt;code&gt;highlighter&lt;/code&gt; produces it, &lt;code&gt;renderer&lt;/code&gt; consumes it, and a browser test can't accidentally hide a bug that unit tests would catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to use CodeFrame
&lt;/h2&gt;

&lt;p&gt;CodeFrame is designed for &lt;strong&gt;presenting short snippets&lt;/strong&gt;: a function, a config block, a diff. It's not suited for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Screenshots of real UIs&lt;/strong&gt;: it's a drawing engine, not a DOM capture tool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long documents&lt;/strong&gt;: it renders a single card; a whole file export is better done with a real editor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animated code&lt;/strong&gt;: output is a static image (that's the point)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side generation&lt;/strong&gt;: everything runs in the browser, by design. No headless Chrome, no server rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;code string
    |
    v
syntect (WASM, fancy-regex backend)
    |
    v
Vec&amp;lt;Token&amp;gt;  (text + color + font style)
    |
    v
layout.rs  (pure math: measure, wrap, position)
    |
    v
Canvas2D fillText per token  ──►  PNG blob (toBlob)
    |                              └──► download / clipboard
    └──► same tokens ──► SVG &amp;lt;text&amp;gt; elements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Highlighting&lt;/strong&gt;: &lt;code&gt;syntect&lt;/code&gt; with &lt;code&gt;default-fancy&lt;/code&gt;; the C &lt;code&gt;onig&lt;/code&gt; backend doesn't compile to wasm32, so the pure-Rust &lt;code&gt;fancy-regex&lt;/code&gt; backend is the only portable option&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendering&lt;/strong&gt;: Canvas2D via &lt;code&gt;web-sys&lt;/code&gt; (full pixel control, no DOM screenshots)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt;: separate high-scale canvas + &lt;code&gt;toBlob&lt;/code&gt;, not &lt;code&gt;toDataURL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fonts&lt;/strong&gt;: Web Font Loading API; &lt;code&gt;document.fonts.ready&lt;/code&gt; is checked on every draw&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;CodeFrame intentionally focuses on one thing: code to image, at the highest resolution the browser can draw, instead of becoming another screenshot utility. If all you need is a beautiful, crisp code image for your slides, your docs, or your README, it gives you a focused, opinionated workflow while doing the rendering the right way: pixel by pixel, in WASM.&lt;/p&gt;

&lt;p&gt;The stack covers the whole story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Real canvas rendering (no DOM screenshots, no &lt;code&gt;html2canvas&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ Export up to 8x (custom scales supported)&lt;/li&gt;
&lt;li&gt;✅ 7 syntax themes, 3 bundled fonts, 15 languages&lt;/li&gt;
&lt;li&gt;✅ SVG export + split-screen comparison&lt;/li&gt;
&lt;li&gt;✅ Live syntax-highlighted editor mirroring the export&lt;/li&gt;
&lt;li&gt;✅ Offline-first PWA (service worker, installable)&lt;/li&gt;
&lt;li&gt;✅ Zero server, zero tracking: code never leaves the browser&lt;/li&gt;
&lt;li&gt;✅ Built with Rust + Leptos + Trunk, compiled to WASM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paste some code and export.&lt;/p&gt;




&lt;h2&gt;
  
  
  Credit &amp;amp; Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/suradet-ps/codeframe" rel="noopener noreferrer"&gt;CodeFrame GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codeframe.rxdevman.com/" rel="noopener noreferrer"&gt;CodeFrame demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>code</category>
      <category>leptos</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Encrypting Secrets in Rust Without Writing Crypto Glue Code</title>
      <dc:creator>Suradet PS</dc:creator>
      <pubDate>Fri, 24 Jul 2026 08:54:38 +0000</pubDate>
      <link>https://dev.to/suradet-ps/encrypting-secrets-in-rust-without-writing-crypto-glue-code-41aa</link>
      <guid>https://dev.to/suradet-ps/encrypting-secrets-in-rust-without-writing-crypto-glue-code-41aa</guid>
      <description>&lt;p&gt;Every Rust developer who needs application-level encryption eventually reaches for crates like &lt;code&gt;aes-gcm&lt;/code&gt; or &lt;code&gt;chacha20poly1305&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;That's the problem &lt;strong&gt;encryptman&lt;/strong&gt; solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is encryptman?
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Under the hood, it uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AES-256-GCM&lt;/strong&gt; for authenticated encryption (confidentiality + integrity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HKDF-SHA256&lt;/strong&gt; for deriving purpose-specific keys from a single master key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Random 12-byte nonces&lt;/strong&gt; - encrypting the same plaintext twice yields different ciphertexts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zeroize on drop&lt;/strong&gt; - master key memory is wiped automatically
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;encryptman&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generate_master_key&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;master_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_master_key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;master_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"my_database_password"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;master_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"my_database_password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No algorithm selection. No nonce management. No encoding headaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use aes-gcm directly?
&lt;/h2&gt;

&lt;p&gt;You absolutely can. But here's what you'll need to handle yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key derivation from a master key (HKDF setup)&lt;/li&gt;
&lt;li&gt;Random nonce generation per encryption call&lt;/li&gt;
&lt;li&gt;Base64 encoding for safe storage&lt;/li&gt;
&lt;li&gt;Zeroizing key material from memory&lt;/li&gt;
&lt;li&gt;Version byte for future algorithm migration&lt;/li&gt;
&lt;li&gt;Context isolation to prevent cross-domain ciphertext substitution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;encryptman bundles all of this into a single, well-documented API with &lt;code&gt;missing_docs = "deny"&lt;/code&gt; enforcing complete documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context isolation
&lt;/h2&gt;

&lt;p&gt;One feature that's easy to overlook: encryptman supports &lt;strong&gt;context strings&lt;/strong&gt; 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;encryptman&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;encrypt_with_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrypt_with_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generate_master_key&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_master_key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Database passwords&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;db_ct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt_with_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"postgres://secret"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// API keys&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;api_ct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt_with_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"api-keys"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sk-12345"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Same plaintext, different contexts -&amp;gt; different ciphertext&lt;/span&gt;
&lt;span class="nd"&gt;assert_ne!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_ct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Cross-context decryption fails&lt;/span&gt;
&lt;span class="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;decrypt_with_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;api_ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents a whole class of attacks where an attacker swaps ciphertexts between unrelated parts of your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  URL-safe encoding
&lt;/h2&gt;

&lt;p&gt;Need to put encrypted values in JWTs, cookies, or URLs? encryptman has you covered with &lt;code&gt;Encoding::UrlSafeNoPad&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;encryptman&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;encrypt_with_encoding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrypt_with_encoding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generate_master_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_master_key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt_with_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"jwt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UrlSafeNoPad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decrypt_with_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"jwt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UrlSafeNoPad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decrypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more worrying about &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt; characters breaking your URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key storage problem
&lt;/h2&gt;

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

&lt;p&gt;The README says "use an OS keychain or KMS" - but that still means writing platform-specific code. This is where &lt;strong&gt;encryptman-keyring&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  encryptman-keyring: Zero file management
&lt;/h2&gt;

&lt;p&gt;encryptman-keyring is a companion crate that stores the master key directly in your OS native credential store:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Credential Store&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Credential Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;Keychain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Secret Service (DBus)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One call. No key files. No &lt;code&gt;.env&lt;/code&gt; headaches. Applications never access the raw master key directly unless they explicitly request it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;encryptman_keyring&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Vault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// First call: generates key -&amp;gt; stores in OS keychain&lt;/span&gt;
&lt;span class="c1"&gt;// Later calls: loads existing key from keychain&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vault&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-app"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="nf"&gt;.encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my_database_password"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="nf"&gt;.decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decrypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"my_database_password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service name (&lt;code&gt;"my-app"&lt;/code&gt;) doubles as the HKDF context, so different applications automatically get domain isolation.&lt;/p&gt;

&lt;p&gt;The architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Application
                     |
                     v
          encryptman-keyring
                     |
      +--------------+--------------+
      v              v              v
   Windows       macOS Keychain   Secret Service
   Credential
   Manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Multiple contexts with one vault
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;encryptman_keyring&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Vault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vault&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-app"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;db_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="nf"&gt;.encrypt_with_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"postgres://..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;api_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="nf"&gt;.encrypt_with_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api-keys"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sk-12345"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;assert_ne!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;db_plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="nf"&gt;.decrypt_with_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;db_secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Separating environments
&lt;/h2&gt;

&lt;p&gt;Need different keys for dev and production? Use &lt;code&gt;new_with_target&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;encryptman_keyring&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Vault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;dev_vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vault&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_with_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;prod_vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vault&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_with_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each target stores a separate key in the OS keychain under the same service name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating from key files
&lt;/h2&gt;

&lt;p&gt;Already have &lt;code&gt;.key&lt;/code&gt; files lying around? Import them and delete the originals in one step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;encryptman_keyring&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Vault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vault&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;migrate_from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/path/to/.key"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// File is deleted after successful migration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Typical use cases
&lt;/h2&gt;

&lt;p&gt;encryptman is designed for application-level secrets such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connection strings&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;OAuth refresh tokens&lt;/li&gt;
&lt;li&gt;SMTP credentials&lt;/li&gt;
&lt;li&gt;Third-party service secrets&lt;/li&gt;
&lt;li&gt;Local application configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When NOT to use these crates
&lt;/h2&gt;

&lt;p&gt;encryptman is designed for encrypting &lt;strong&gt;small strings&lt;/strong&gt; - passwords, API keys, tokens. It's not suited for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Password hashing&lt;/strong&gt; - use &lt;code&gt;argon2&lt;/code&gt; or &lt;code&gt;bcrypt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File encryption&lt;/strong&gt; - use a streaming encryption library instead of encrypting the whole file in memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database-at-rest encryption&lt;/strong&gt; - use your database's built-in encryption (TDE, disk encryption, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large data&lt;/strong&gt; - the entire plaintext is held in memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;encryptman-keyring isn't ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Headless/CI environments&lt;/strong&gt; - OS keychain may not be available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-user servers&lt;/strong&gt; - keyring entries are per-user; consider Vault or AWS Secrets Manager&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;master key
    |
    v
HKDF-SHA256("encryptman:{context}")
    |
    v
AES-256-GCM key
    |
    v
plaintext + random nonce
    |
    v
ciphertext
    |
    v
version || nonce || ciphertext
    |
    v
Base64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key Derivation&lt;/strong&gt;: HKDF-SHA256 generates domain-separated subkeys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticated Encryption&lt;/strong&gt;: AES-256-GCM detects any tampering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fresh Nonce&lt;/strong&gt;: 12 random bytes per encryption call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Byte&lt;/strong&gt;: Reserved for future algorithm migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The ecosystem covers the full workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ AES-256-GCM authenticated encryption&lt;/li&gt;
&lt;li&gt;✅ HKDF-SHA256 key derivation&lt;/li&gt;
&lt;li&gt;✅ Context isolation&lt;/li&gt;
&lt;li&gt;✅ OS keychain integration (no key files)&lt;/li&gt;
&lt;li&gt;✅ URL-safe encoding for JWTs/cookies&lt;/li&gt;
&lt;li&gt;✅ Zeroize on drop&lt;/li&gt;
&lt;li&gt;✅ MSRV: Rust 1.85 (Edition 2024)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just generate a key and start encrypting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Credit &amp;amp; Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/suradet-ps/encryptman" rel="noopener noreferrer"&gt;encryptman GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://crates.io/crates/encryptman" rel="noopener noreferrer"&gt;encryptman on crates.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.rs/encryptman" rel="noopener noreferrer"&gt;encryptman docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/suradet-ps/encryptman-keyring" rel="noopener noreferrer"&gt;encryptman-keyring GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://crates.io/crates/encryptman-keyring" rel="noopener noreferrer"&gt;encryptman-keyring on crates.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.rs/encryptman-keyring" rel="noopener noreferrer"&gt;encryptman-keyring docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>security</category>
      <category>cryptography</category>
      <category>encryption</category>
    </item>
  </channel>
</rss>
