<?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: Cakra Budiman</title>
    <description>The latest articles on DEV Community by Cakra Budiman (@cakra_budiman_063a0514b4e).</description>
    <link>https://dev.to/cakra_budiman_063a0514b4e</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%2F4074507%2F67f0e618-cd6a-4a84-ba9b-4755209a96b6.jpg</url>
      <title>DEV Community: Cakra Budiman</title>
      <link>https://dev.to/cakra_budiman_063a0514b4e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cakra_budiman_063a0514b4e"/>
    <language>en</language>
    <item>
      <title>The build flag that made our encryption 11 slower (and no one noticed for months)</title>
      <dc:creator>Cakra Budiman</dc:creator>
      <pubDate>Wed, 12 Aug 2026 09:52:43 +0000</pubDate>
      <link>https://dev.to/cakra_budiman_063a0514b4e/the-build-flag-that-made-our-encryption-11x-slower-and-no-one-noticed-for-months-3e7c</link>
      <guid>https://dev.to/cakra_budiman_063a0514b4e/the-build-flag-that-made-our-encryption-11x-slower-and-no-one-noticed-for-months-3e7c</guid>
      <description>&lt;p&gt;Shipping one Rust crypto core to five platforms with Flutter — and the cargokit gotcha that silently disabled hardware AES in every release build.&lt;br&gt;
Users kept telling us transfers were slow. Our benchmarks kept telling us they were fast. Both were right — and the gap between those two sentences cost us months.&lt;/p&gt;

&lt;p&gt;This is a story about shipping one Rust cryptography core to five platforms from a Flutter app, and about the single most instructive bug we hit doing it: a build flag that quietly turned hardware-accelerated AES &lt;em&gt;off&lt;/em&gt; in every release build we shipped, while every build we benchmarked had it &lt;em&gt;on&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I build &lt;a href="https://github.com/BIShare-project/bishare-flutter" rel="noopener noreferrer"&gt;BIShare&lt;/a&gt;, an open-source AirDrop-style app: direct device-to-device file transfer between iPhone, Android, macOS, Windows, and Linux. MIT licensed, Flutter UI, Rust core.&lt;/p&gt;

&lt;p&gt;The architecture decision that matters for this story: all cryptography and protocol framing lives in a single Rust crate, bridged into Dart with &lt;a href="https://github.com/fzyzcjy/flutter_rust_bridge" rel="noopener noreferrer"&gt;flutter_rust_bridge&lt;/a&gt;. X25519 for key agreement, AES-256-GCM for the data path — implemented once, reviewed once, shipped to all five platforms. The Dart side never touches key material.&lt;/p&gt;

&lt;p&gt;On paper this is the clean version of the diagram. One implementation instead of five means one place for bugs instead of five — which is true, and which is exactly why the bug that follows was so effective: we shipped it &lt;em&gt;everywhere, identically&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;Local-network transfers should saturate Wi-Fi. Our release-build benchmarks on real devices showed 40–50 MB/s on ordinary Wi-Fi 5 hardware — a 1 GB video in about 25 seconds. That's the whole pitch of skipping the cloud.&lt;/p&gt;

&lt;p&gt;But App Store users reported transfers crawling at about 2 MB/s. Twenty times slower. Same app, same network conditions, same code — allegedly.&lt;/p&gt;

&lt;p&gt;We did the usual rounds: network path, buffer sizes, the Dart/native boundary, thermal throttling. Everything checked out. The transfer pipeline was fine. The &lt;em&gt;cipher&lt;/em&gt; was not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cause
&lt;/h2&gt;

&lt;p&gt;Modern ARM chips have dedicated AES instructions. With them, AES-256-GCM encrypts multiple gigabytes per second and is never your bottleneck. Without them, the same cipher falls back to a software implementation that is — on the hardware we measured — about 11× slower. Slow enough that encryption, of all things, becomes the ceiling on your transfer speed.&lt;/p&gt;

&lt;p&gt;In Rust, enabling those instructions for aarch64 targets took a compiler flag, which lived in our &lt;code&gt;.cargo/config.toml&lt;/code&gt;. And here's the trap: cargokit — the build helper that compiles Rust crates inside a Flutter build — was invoking &lt;code&gt;cargo&lt;/code&gt; from the &lt;em&gt;build system's&lt;/em&gt; working directory (Xcode's &lt;code&gt;DerivedData&lt;/code&gt;, in the iOS/macOS case), not from the crate's directory.&lt;/p&gt;

&lt;p&gt;Cargo discovers &lt;code&gt;.cargo/config.toml&lt;/code&gt; by walking up from the current working directory. From &lt;code&gt;DerivedData&lt;/code&gt;, our config file was never on that path. So the flag never applied. So every iOS and macOS release build we ever shipped ran software AES.&lt;/p&gt;

&lt;p&gt;Why didn't the benchmarks catch it? Because when you benchmark "the Rust core," you tend to run it from the crate directory — where the config file &lt;em&gt;is&lt;/em&gt; found. Our fast numbers and our users' slow numbers were both completely real. They were just measuring two different binaries that happened to share source code.&lt;/p&gt;

&lt;p&gt;The fix was one line of understanding: make cargokit set &lt;code&gt;workingDirectory&lt;/code&gt; to the crate's manifest directory before invoking cargo. (On Android the same class of problem needed a patch in cargokit's &lt;code&gt;android_environment.dart&lt;/code&gt; to inject the &lt;code&gt;aes&lt;/code&gt; target feature.) After the fix, on-device throughput matched the benchmarks, verified on a mid-range Galaxy A24 — the kind of phone your users actually own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell any team doing crypto behind FRB
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Benchmark the shipped artifact, on a device, in release mode. Not the crate, not the simulator, not a debug build. Debug-mode Dart is roughly 10× slower on hot loops, so debug numbers are noise anyway — and as we learned, even a release &lt;em&gt;crate&lt;/em&gt; benchmark can be measuring a different binary than the one in your &lt;code&gt;.ipa&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Treat build-system working directories as hostile. Any configuration that's discovered by walking up from CWD — &lt;code&gt;.cargo/config.toml&lt;/code&gt; is not the only offender — will betray you the moment a build helper invokes the toolchain from somewhere unexpected. Prefer flags that travel &lt;em&gt;with the invocation&lt;/em&gt; (env vars, explicit &lt;code&gt;--config&lt;/code&gt;) or pin the working directory yourself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make performance a test, not a vibe. After this incident we added a benchmark mode to the app itself (&lt;code&gt;BISHARE_BENCH=1&lt;/code&gt;) so throughput on a real device is one command away. If a regression like this ships again, we want a user-visible number to disagree with us immediately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delete work the cipher already does. While profiling, we also found our Dart layer computing an extra SHA-256 over data that had already passed through per-chunk &lt;em&gt;authenticated&lt;/em&gt; encryption. AES-GCM's tags already prove integrity, chunk by chunk — the checksum was proving it again, slower. We verified received bytes were identical with and without it, deleted it, and got a free speed bump. If you're layering integrity checks on top of an AEAD, ask what, exactly, the second check catches.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The part where this is a pitch (briefly)
&lt;/h2&gt;

&lt;p&gt;The app this happened to is open source and on the stores: LAN transfers with mDNS discovery, a browser receive path so the other side needs no app, QR-stream transfer for the no-network case, 13 languages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/BIShare-project/bishare-flutter" rel="noopener noreferrer"&gt;https://github.com/BIShare-project/bishare-flutter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The Rust protocol crate: &lt;a href="https://github.com/BIShare-project/bishare-protocol" rel="noopener noreferrer"&gt;https://github.com/BIShare-project/bishare-protocol&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;App/downloads: &lt;a href="https://bishare.app" rel="noopener noreferrer"&gt;https://bishare.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've hit your own cursed FRB or cargokit build behavior, I'd genuinely like to hear it — and if you speak a language we don't ship yet, translations are a 15-minute PR.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>rust</category>
      <category>opensource</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
