<?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: Rajath R</title>
    <description>The latest articles on DEV Community by Rajath R (@rajathtuesday).</description>
    <link>https://dev.to/rajathtuesday</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%2F4015238%2Fb2d84288-fab9-452e-868b-49aee6e4ad65.png</url>
      <title>DEV Community: Rajath R</title>
      <link>https://dev.to/rajathtuesday</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rajathtuesday"/>
    <language>en</language>
    <item>
      <title>How I built a zero-knowledge encrypted cloud storage app: the complete crypto architecture</title>
      <dc:creator>Rajath R</dc:creator>
      <pubDate>Sat, 04 Jul 2026 16:05:25 +0000</pubDate>
      <link>https://dev.to/rajathtuesday/how-i-built-a-zero-knowledge-encrypted-cloud-storage-app-the-complete-crypto-architecture-1e0l</link>
      <guid>https://dev.to/rajathtuesday/how-i-built-a-zero-knowledge-encrypted-cloud-storage-app-the-complete-crypto-architecture-1e0l</guid>
      <description>&lt;p&gt;For the past two years I've been building Silvora - a zero-knowledge &lt;br&gt;
encrypted cloud storage app - entirely solo, alongside a full-time job &lt;br&gt;
as a programming instructor.&lt;/p&gt;

&lt;p&gt;This post is about the cryptographic architecture. Not the marketing &lt;br&gt;
version. The actual decisions, why I made them, and what they mean for &lt;br&gt;
the security guarantees.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core guarantee
&lt;/h2&gt;

&lt;p&gt;The server stores only ciphertext. It has zero decryption code. &lt;br&gt;
Mathematically, even if someone compromised the server completely, &lt;br&gt;
they could not read a single user's file.&lt;/p&gt;

&lt;p&gt;Here is how that guarantee is actually implemented.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key hierarchy
&lt;/h2&gt;

&lt;p&gt;Everything starts with your password. But your password never travels &lt;br&gt;
to the server - not even as a hash.&lt;/p&gt;

&lt;p&gt;Instead, on your device, Argon2id derives your master key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory: 64MB&lt;/li&gt;
&lt;li&gt;Iterations: 3
&lt;/li&gt;
&lt;li&gt;Parallelism: 2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These parameters are above OWASP minimums. The server enforces floors &lt;br&gt;
server-side - if a client tries to submit with weaker parameters, &lt;br&gt;
the request is rejected.&lt;/p&gt;

&lt;p&gt;The master key lives in memory only. Never touches disk. Never touches &lt;br&gt;
logs. When you lock the vault or background the app, it is zeroed with &lt;br&gt;
fillRange(0) before being released.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-file key derivation
&lt;/h2&gt;

&lt;p&gt;One master key encrypting everything would be a design failure. If the &lt;br&gt;
master key was ever exposed, every file would be compromised simultaneously.&lt;/p&gt;

&lt;p&gt;Instead, each file gets its own key derived via HKDF-SHA256:&lt;br&gt;
per_file_key = HKDF(&lt;br&gt;
master_key,&lt;br&gt;
salt=file_id,&lt;br&gt;
info="silvora-file-encryption-v1",&lt;br&gt;
length=32&lt;br&gt;
)&lt;br&gt;
The &lt;code&gt;info&lt;/code&gt; parameter is domain separation - it ensures a key derived &lt;br&gt;
for file encryption cannot be used for any other purpose, even with &lt;br&gt;
the same inputs.&lt;/p&gt;

&lt;p&gt;Per-chunk keys are derived the same way with their own domain separator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encryption
&lt;/h2&gt;

&lt;p&gt;XChaCha20-Poly1305 AEAD encrypts every chunk.&lt;/p&gt;

&lt;p&gt;Why XChaCha20 over AES?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;192-bit nonce - nonce reuse is catastrophically unlikely even at scale&lt;/li&gt;
&lt;li&gt;No hardware acceleration dependency - constant-time on all devices&lt;/li&gt;
&lt;li&gt;No padding oracle attacks (stream cipher, not block cipher)&lt;/li&gt;
&lt;li&gt;Poly1305 provides authentication - tampering is detectable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every encryption call uses a fresh random nonce. Nonce reuse is &lt;br&gt;
checked server-side as an additional layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovery without server knowledge
&lt;/h2&gt;

&lt;p&gt;If you forget your password, a traditional system resets it server-side &lt;br&gt;
because the server knows something.&lt;/p&gt;

&lt;p&gt;Silvora can't do that. The server knows nothing.&lt;/p&gt;

&lt;p&gt;Instead, during registration, your device generates a BIP39 24-word &lt;br&gt;
recovery phrase (256 bits of entropy). The master key is encrypted &lt;br&gt;
under a key derived from this phrase and stored - still encrypted - &lt;br&gt;
on the server.&lt;/p&gt;

&lt;p&gt;Recovery works entirely client-side. The server hands back the &lt;br&gt;
encrypted blob. Your phrase decrypts it locally. The server never &lt;br&gt;
sees the phrase or the master key.&lt;/p&gt;

&lt;p&gt;During onboarding, you must correctly identify 3 random words from &lt;br&gt;
your phrase before continuing. You cannot skip this. For a &lt;br&gt;
zero-knowledge app, a user who loses their phrase loses their data &lt;br&gt;
permanently. That consequence deserved a real confirmation step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrity verification
&lt;/h2&gt;

&lt;p&gt;Each file upload generates a manifest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SHA-256 hash of every plaintext chunk&lt;/li&gt;
&lt;li&gt;Total chunk count (detects truncation)&lt;/li&gt;
&lt;li&gt;The manifest itself encrypted with XChaCha20-Poly1305 under an 
HKDF-derived per-file integrity key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On every download, the manifest is fetched, decrypted, and every &lt;br&gt;
chunk hash is verified before the file is returned to the user.&lt;/p&gt;

&lt;p&gt;The server cannot tamper with file contents without detection. It &lt;br&gt;
cannot reorder chunks. It cannot truncate files silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the server has
&lt;/h2&gt;

&lt;p&gt;The server stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encrypted file chunks (ciphertext)&lt;/li&gt;
&lt;li&gt;Encrypted filenames (ciphertext)&lt;/li&gt;
&lt;li&gt;Encrypted integrity manifests (ciphertext)&lt;/li&gt;
&lt;li&gt;Argon2id parameters (public - needed for key derivation)&lt;/li&gt;
&lt;li&gt;File metadata: size, upload timestamp, chunk count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is all it has ever had.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backend
&lt;/h2&gt;

&lt;p&gt;Django REST Framework on Render, PostgreSQL on Neon, file storage &lt;br&gt;
on Cloudflare R2.&lt;/p&gt;

&lt;p&gt;Key security decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every file endpoint scoped by owner AND tenant - double isolation&lt;/li&gt;
&lt;li&gt;Rate limiting on all auth and file endpoints&lt;/li&gt;
&lt;li&gt;SELECT FOR UPDATE on upload commits - idempotent, race-condition safe&lt;/li&gt;
&lt;li&gt;Quota reserved at upload start, not commit - parallel uploads 
cannot blow past limits&lt;/li&gt;
&lt;li&gt;Soft-deleted files cannot accept new chunk writes&lt;/li&gt;
&lt;li&gt;No raw SQL anywhere - full ORM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;103 tests passing across backend and Flutter client.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flutter client
&lt;/h2&gt;

&lt;p&gt;On-device Argon2 key derivation. Encrypted chunk streaming. JWT &lt;br&gt;
stored in FlutterSecureStorage - never in SharedPreferences.&lt;/p&gt;

&lt;p&gt;Additional hardening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Root detection blocks vault access on rooted devices&lt;/li&gt;
&lt;li&gt;Developer mode / USB debugging gate - vault inaccessible while 
debugging active&lt;/li&gt;
&lt;li&gt;FLAG_SECURE - blocks screenshots, screen recording, and 
recent-apps thumbnail on release builds&lt;/li&gt;
&lt;li&gt;Clipboard auto-clear - recovery phrase wiped from clipboard 
after 60 seconds&lt;/li&gt;
&lt;li&gt;1-minute auto-lock on background&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Current status
&lt;/h2&gt;

&lt;p&gt;Silvora is in Google Play closed testing. I'm looking for Android &lt;br&gt;
users who care about privacy to test it.&lt;/p&gt;

&lt;p&gt;If you want early access: leave a comment or email me at &lt;br&gt;
&lt;a href="mailto:rajathramesh2002@gmail.com"&gt;rajathramesh2002@gmail.com&lt;/a&gt; with your Gmail address and I'll &lt;br&gt;
add you as a tester.&lt;/p&gt;

&lt;p&gt;If you want to understand the full architecture in more depth - &lt;br&gt;
I've been writing a technical book using Silvora as the running &lt;br&gt;
example, covering the cryptography from first principles. &lt;br&gt;
Happy to share chapters with anyone interested.&lt;/p&gt;

&lt;p&gt;Live at: silvora.cloud&lt;/p&gt;

&lt;p&gt;Ask me anything about the implementation.&lt;/p&gt;

</description>
      <category>security</category>
      <category>programming</category>
      <category>android</category>
      <category>cryptocurrency</category>
    </item>
  </channel>
</rss>
