<?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: Alex</title>
    <description>The latest articles on DEV Community by Alex (@alexiaxaxa).</description>
    <link>https://dev.to/alexiaxaxa</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%2F4141365%2Fcf2e14be-eadd-47bc-b1a1-7476fc2393e5.png</url>
      <title>DEV Community: Alex</title>
      <link>https://dev.to/alexiaxaxa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexiaxaxa"/>
    <language>en</language>
    <item>
      <title>Post-Quantum Crypto Meets a Non-Extractable TPM Key</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Fri, 25 Sep 2026 13:14:02 +0000</pubDate>
      <link>https://dev.to/alexiaxaxa/post-quantum-crypto-meets-anon-extractable-tpm-key-5g1i</link>
      <guid>https://dev.to/alexiaxaxa/post-quantum-crypto-meets-anon-extractable-tpm-key-5g1i</guid>
      <description>&lt;h1&gt;
  
  
  Post-Quantum Crypto Meets a Non-Extractable TPM Key
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;An engineering story about combining two useful properties without pretending either one solves the whole problem.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I ran into an interesting design problem while working on device-bound encryption.&lt;/p&gt;

&lt;p&gt;I wanted two things at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a key-establishment path designed to withstand future quantum-capable attackers;&lt;/li&gt;
&lt;li&gt;a device identity whose private P-256 key stays inside the machine's TPM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each goal has a familiar tool. Getting both into one key-establishment flow is where the details start to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part the TPM already did well
&lt;/h2&gt;

&lt;p&gt;On Windows, the Microsoft Platform Crypto Provider can use a TPM for cryptographic operations. With a suitable key configuration, an application can ask the provider to perform an operation without exporting the private key material into the application.&lt;/p&gt;

&lt;p&gt;For an ECDH P-256 key, the rough idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Windows application
                         |
                  request ECDH
                         |
                         v
                 Platform Crypto Provider
                         |
                         v
                 TPM-held P-256 key
                         |
                         v
                  shared secret bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application works with a key handle and an operation result. It does not need to copy the private scalar out of the TPM.&lt;/p&gt;

&lt;p&gt;That is useful for device binding. Copying the application's files to another machine does not, by itself, copy the TPM-held private key too.&lt;/p&gt;

&lt;p&gt;But P-256 is classical elliptic-curve cryptography. A sufficiently capable quantum computer would change the assumptions behind it. I wanted a post-quantum component in the design as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  ML-KEM brought a different strength
&lt;/h2&gt;

&lt;p&gt;NIST standardized ML-KEM in FIPS 203. ML-KEM-768 lets one party encapsulate a shared secret to a public key; the other party decapsulates it with the corresponding private key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sender                                      Recipient
------                                      ---------
ML-KEM public key                           ML-KEM private key
       |                                           |
 encapsulate                                  decapsulate
       |                                           |
       +-- ciphertext ----------------------------+
       +-- shared secret --------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the design a post-quantum component. It does not make the ML-KEM private key hardware-bound: in this design, that private component is still managed by software and can be copied if the machine or process exposes it.&lt;/p&gt;

&lt;p&gt;So the two mechanisms had complementary properties:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Intended contribution&lt;/th&gt;
&lt;th&gt;Where the private component lives&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ML-KEM-768&lt;/td&gt;
&lt;td&gt;Post-quantum key establishment&lt;/td&gt;
&lt;td&gt;Software-managed key material&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P-256 ECDH&lt;/td&gt;
&lt;td&gt;Device-bound classical contribution&lt;/td&gt;
&lt;td&gt;TPM-backed key handle and operation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzq9aam43l5og079yx06w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzq9aam43l5og079yx06w.png" alt=" " width="760" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted both contributions to matter to the final shared secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why two independent recovery slots are not a hybrid
&lt;/h2&gt;

&lt;p&gt;A tempting design is to wrap the same content key twice: once for ML-KEM and once for P-256, then let either path recover it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         +-- ML-KEM path --+
Encrypted content key --|                 |--&amp;gt; recovered key
                         +-- P-256 path ---+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is two alternative ways in. If either path is enough on its own, the overall result can be limited by whichever path is easier to defeat. It does not require an attacker to break both components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp2nwifzac6ibhiot7fut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp2nwifzac6ibhiot7fut.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The design I wanted instead has both components feed one hybrid key-establishment construction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ML-KEM decapsulation ---- shared secret A --+
                                             +--&amp;gt; specified hybrid KDF --&amp;gt; final secret
TPM ECDH operation ------- shared secret B --+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the point where I did not want to invent a combiner from intuition. A line such as &lt;code&gt;hash(secret_a || secret_b)&lt;/code&gt; looks simple, but a secure construction also has to define input ordering, labels, transcript binding, failure behavior, and the security model.&lt;/p&gt;

&lt;p&gt;The concrete &lt;code&gt;MLKEM768-P256&lt;/code&gt; construction in the CFRG hybrid-KEM draft specifies how these pieces fit together. It uses ML-KEM-768 and P-256 with a defined combiner and domain-separation label. The draft's current wire sizes are 1,249 bytes for the encapsulation key and 1,153 bytes for the ciphertext.&lt;/p&gt;

&lt;p&gt;Those are larger than a plain P-256 exchange. For a file-encryption envelope, the extra bytes are usually less important than getting the composition right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqicgxztvt8q3mfy8a5oh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqicgxztvt8q3mfy8a5oh.png" alt=" " width="760" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the P-256 operation inside the TPM
&lt;/h2&gt;

&lt;p&gt;The implementation challenge was to preserve the TPM boundary while following the hybrid construction.&lt;/p&gt;

&lt;p&gt;Conceptually, the recipient side looks like this:&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="c1"&gt;// Simplified pseudocode, not a drop-in implementation.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pq_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mlkem768_decapsulate&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;mlkem_private_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;mlkem_ciphertext&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;p256_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;platform_provider&lt;/span&gt;&lt;span class="nf"&gt;.ecdh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tpm_key_handle&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;ephemeral_p256_public_key&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;final_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;specified_hybrid_kem_combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pq_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p256_secret&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;encapsulation_context&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact inputs and encoding must come from the selected specification and implementation. The architectural boundary is the useful part to see: software handles the ML-KEM operation; the platform provider asks the TPM to do ECDH; the hybrid construction consumes both results.&lt;/p&gt;

&lt;p&gt;There is no step that exports the TPM's P-256 private scalar. The TPM returns the result of an operation, not the key itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The keys need independent origins
&lt;/h2&gt;

&lt;p&gt;A hybrid design can quietly lose its point if one private component is derived from the other.&lt;/p&gt;

&lt;p&gt;For example, deriving an ML-KEM key from the P-256 secret might look convenient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P-256 secret --&amp;gt; KDF --&amp;gt; ML-KEM private key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But then a failure that exposes the classical secret could also expose the post-quantum key derived from it. Two algorithms would not mean two independent failure domains.&lt;/p&gt;

&lt;p&gt;The key material should be generated independently, with the right randomness and key-generation procedure for each component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;independent random input --&amp;gt; ML-KEM key pair
TPM key generation       --&amp;gt; P-256 key pair
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact lifecycle, backup, and recovery rules still need to be designed for the product. Independence at key generation is one necessary piece, not a complete lifecycle policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “hybrid” does and does not promise
&lt;/h2&gt;

&lt;p&gt;It is easy to summarize a hybrid as “if one algorithm breaks, we're safe.” That is too broad without assumptions.&lt;/p&gt;

&lt;p&gt;The intended benefit is that recovering the final secret should require defeating the specified hybrid construction, rather than merely finding either of two independent unwrap paths. The exact claim depends on the construction, correct implementation, key independence, and the attacker's access to both components over time.&lt;/p&gt;

&lt;p&gt;For example, if an attacker copies the software ML-KEM private key today and P-256 becomes breakable later, the two events can combine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F849uh1rylewbzm8gni3m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F849uh1rylewbzm8gni3m.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A TPM boundary raises the bar for copying the classical private key, but it does not erase every possible compromise path or make stored data invulnerable forever.&lt;/p&gt;

&lt;p&gt;And “TPM-backed” does not mean ML-KEM runs inside the TPM. In this design it does not. The software-held ML-KEM key remains a software secret; the hybrid makes the TPM-held operation another required component of the specified key-establishment flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the boundary, not just the math
&lt;/h2&gt;

&lt;p&gt;Cryptographic test vectors and a real TPM exercise answer different questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Known-answer tests can check that an implementation matches expected algorithm outputs.&lt;/li&gt;
&lt;li&gt;A Windows integration test can check that the configured provider and key handle actually perform the P-256 operation through the expected platform path on that machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second check matters. A build that references a TPM API is not proof that the operation reached a hardware-backed key. In one recorded live &lt;code&gt;kem 5&lt;/code&gt; check, the hybrid approval flow ran against a real TPM and passed 1/1 on a single machine. That is useful evidence that this path reached hardware in that setup.&lt;/p&gt;

&lt;p&gt;It is not hardware certification. Vendor EK attestation and multi-machine hardware behavior remain unverified, and one successful run does not establish that every TPM, firmware, provider configuration, or Windows release behaves identically. The claim should stay scoped to what was actually tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result: two failure domains, one specified construction
&lt;/h2&gt;

&lt;p&gt;The useful mental model is not “post-quantum TPM.” It is two independently managed components brought together by a defined hybrid KEM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;software-held ML-KEM component ----+
                                    +--&amp;gt; specified hybrid secret --&amp;gt; envelope key
TPM-held P-256 operation ----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ML-KEM contributes the post-quantum side. The TPM-held key contributes a device-bound operation. Neither label should be stretched into a claim that the other component has moved into that boundary.&lt;/p&gt;

&lt;p&gt;The lesson for me was less about picking a fashionable algorithm and more about preserving the properties we actually wanted: independent key material, a non-exportable device key, and a combiner designed and specified as a whole.&lt;/p&gt;

&lt;p&gt;When two primitives are meant to protect against different failures, the composition is the security design. The diagram is the easy part.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NIST, &lt;a href="https://csrc.nist.gov/pubs/fips/203/final" rel="noopener noreferrer"&gt;FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard&lt;/a&gt;. NIST has noted that an issue will be corrected in a future update; implementations should follow the publication and its current errata.&lt;/li&gt;
&lt;li&gt;IETF CFRG, &lt;a href="https://datatracker.ietf.org/doc/draft-irtf-cfrg-concrete-hybrid-kems/04/" rel="noopener noreferrer"&gt;Concrete Hybrid PQ/T Key Encapsulation Mechanisms, draft-irtf-cfrg-concrete-hybrid-kems-04&lt;/a&gt;. This is an Internet-Draft, not a final RFC.&lt;/li&gt;
&lt;li&gt;Microsoft Learn, &lt;a href="https://learn.microsoft.com/en-us/windows/win32/seccertenroll/cng-key-storage-providers" rel="noopener noreferrer"&gt;CNG Key Storage Providers&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/windows/security/hardware-security/tpm/how-windows-uses-the-tpm" rel="noopener noreferrer"&gt;How Windows uses the TPM&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#security&lt;/code&gt; &lt;code&gt;#windows&lt;/code&gt; &lt;code&gt;#rust&lt;/code&gt; &lt;code&gt;#systemsprogramming&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is an engineering narrative, not a claim of certification or a substitute for a review of a concrete implementation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>cybersecurity</category>
      <category>security</category>
    </item>
    <item>
      <title>We Tried to Keep Plaintext Only in Memory. Microsoft Word Said No.</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Thu, 24 Sep 2026 16:57:04 +0000</pubDate>
      <link>https://dev.to/alexiaxaxa/we-tried-to-keep-plaintext-only-in-memory-microsoft-word-said-no-l04</link>
      <guid>https://dev.to/alexiaxaxa/we-tried-to-keep-plaintext-only-in-memory-microsoft-word-said-no-l04</guid>
      <description>&lt;p&gt;I wanted to solve what sounded like a fairly simple problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open an encrypted &lt;code&gt;.docx&lt;/code&gt; in Microsoft Word without ever writing the decrypted document to disk.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ideal flow looked 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;encrypted file
     ↓
decrypt requested bytes
     ↓
Windows virtual file
     ↓
Microsoft Word
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plaintext would exist only in memory.&lt;/p&gt;

&lt;p&gt;No temporary &lt;code&gt;.docx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No cleanup problem.&lt;/p&gt;

&lt;p&gt;No plaintext sitting somewhere on disk waiting to be copied.&lt;/p&gt;

&lt;p&gt;Windows Cloud Files API seemed almost perfect for this.&lt;/p&gt;

&lt;p&gt;It lets you create placeholder files whose actual content is provided on demand. When an application asks for some bytes, your provider gets a callback and returns exactly that range.&lt;/p&gt;

&lt;p&gt;Conceptually:&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;fn&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&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;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nf"&gt;provide_to_windows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;offset&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;document&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I built a small test.&lt;/p&gt;

&lt;p&gt;The placeholder contained no document data. The provider kept the real bytes in memory and served them when Windows requested them.&lt;/p&gt;

&lt;p&gt;Then I opened the file in real Microsoft Word.&lt;/p&gt;

&lt;h2&gt;
  
  
  First attempt: full hydration
&lt;/h2&gt;

&lt;p&gt;In the normal hydration mode, everything worked.&lt;/p&gt;

&lt;p&gt;Word requested the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;offset: 0
length: 13400
process: WINWORD.EXE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provider returned the bytes.&lt;/p&gt;

&lt;p&gt;Word opened the document successfully.&lt;/p&gt;

&lt;p&gt;Great.&lt;/p&gt;

&lt;p&gt;Except Windows had now written the hydrated contents to disk.&lt;/p&gt;

&lt;p&gt;After stopping the provider entirely, the file was still readable as a normal &lt;code&gt;.docx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the architecture had effectively become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;encrypted file
     ↓
decrypt
     ↓
plaintext file on disk
     ↓
Word
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which was exactly what I was trying to avoid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second attempt: streaming
&lt;/h2&gt;

&lt;p&gt;Cloud Files also has a streaming mode.&lt;/p&gt;

&lt;p&gt;In this mode, the application can request data without permanently hydrating the placeholder.&lt;/p&gt;

&lt;p&gt;That sounded much better.&lt;/p&gt;

&lt;p&gt;A normal sequential reader worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReadAllBytes()
→ 13400 bytes
→ success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plaintext never became a normal file on disk.&lt;/p&gt;

&lt;p&gt;Then I tried Word.&lt;/p&gt;

&lt;p&gt;It failed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Word experienced an error trying to open the file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part was that Word &lt;strong&gt;did request the data&lt;/strong&gt;, and the provider successfully returned it.&lt;/p&gt;

&lt;p&gt;So the problem wasn't that Word couldn't read the bytes.&lt;/p&gt;

&lt;p&gt;The problem was how Word wanted to access them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Word doesn't just read the file
&lt;/h2&gt;

&lt;p&gt;A simple application might behave roughly 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;open()
read()
read()
read()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works perfectly with streaming placeholders.&lt;/p&gt;

&lt;p&gt;Word does something more complicated.&lt;/p&gt;

&lt;p&gt;It needs a representation of the file that can be backed by a Windows section object / memory mapping.&lt;/p&gt;

&lt;p&gt;And Windows cannot create that mapping over file contents that only exist as temporary streamed responses.&lt;/p&gt;

&lt;p&gt;The difference is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sequential read:

ReadFile()
   ↓
provider
   ↓
bytes
   ↓
works
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Word:

open file
   ↓
create mapped section
   ↓
requires locally backed data
   ↓
streaming placeholder
   ↓
fails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I reproduced the same behavior without Word.&lt;/p&gt;

&lt;p&gt;Sequential reading worked.&lt;/p&gt;

&lt;p&gt;Trying to create a mapped representation of the same file failed.&lt;/p&gt;

&lt;p&gt;At that point it was clear this wasn't just a Word quirk.&lt;/p&gt;

&lt;p&gt;The storage model itself was incompatible with what the application expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical solution
&lt;/h2&gt;

&lt;p&gt;The compromise was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Hydrate the plaintext file
2. Open it in Word
3. Keep it hydrated only for the active session
4. Dehydrate it when the session ends
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows provides &lt;code&gt;CfDehydratePlaceholder&lt;/code&gt;, which can return the file to its placeholder state.&lt;/p&gt;

&lt;p&gt;So the final flow became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;encrypted document
      ↓
temporary hydration
      ↓
Microsoft Word
      ↓
document closes
      ↓
dehydrate
      ↓
plaintext removed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked.&lt;/p&gt;

&lt;p&gt;But it changed the security model.&lt;/p&gt;

&lt;p&gt;The guarantee was no longer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;plaintext never touches disk&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;plaintext exists on disk only inside a controlled working session and is removed afterward.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that creates another problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crashes matter
&lt;/h2&gt;

&lt;p&gt;This is not enough:&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="nf"&gt;on_document_close&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;dehydrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because sometimes there is no clean close.&lt;/p&gt;

&lt;p&gt;The machine can lose power.&lt;/p&gt;

&lt;p&gt;The provider can crash.&lt;/p&gt;

&lt;p&gt;The process can be killed.&lt;/p&gt;

&lt;p&gt;So recovery has to be part of the design:&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;fn&lt;/span&gt; &lt;span class="nf"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;leftover_hydrated_files&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;dehydrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was probably the most useful realization from the experiment:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cleanup isn't just cleanup when plaintext is involved.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It becomes part of the security model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The larger lesson
&lt;/h2&gt;

&lt;p&gt;The original idea was much prettier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plaintext exists only in RAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, software you don't control may make assumptions your architecture cannot satisfy.&lt;/p&gt;

&lt;p&gt;And sometimes the right response isn't another workaround.&lt;/p&gt;

&lt;p&gt;It's changing the assumption.&lt;/p&gt;

&lt;p&gt;For applications that perform ordinary streaming reads, an in-memory delivery path can still work.&lt;/p&gt;

&lt;p&gt;For applications like Word, the safer practical model is a temporary protected working session with explicit cleanup and crash recovery.&lt;/p&gt;

&lt;p&gt;The experiment turned this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protect the file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protect the lifetime of the plaintext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not as elegant.&lt;/p&gt;

&lt;p&gt;But much more realistic.&lt;/p&gt;

</description>
      <category>security</category>
      <category>microsoft</category>
      <category>rust</category>
      <category>programming</category>
    </item>
    <item>
      <title>Can you call it end-to-end encrypted if the provider holds the keys?</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Thu, 24 Sep 2026 13:58:50 +0000</pubDate>
      <link>https://dev.to/alexiaxaxa/can-you-call-it-end-to-end-encrypted-if-the-provider-holds-the-keys-4ap1</link>
      <guid>https://dev.to/alexiaxaxa/can-you-call-it-end-to-end-encrypted-if-the-provider-holds-the-keys-4ap1</guid>
      <description>&lt;p&gt;'Encrypted' describes what happens to data. It does not tell you who can make that data readable again.&lt;/p&gt;

&lt;p&gt;Consider two designs. In one, a storage service encrypts a file on its servers and manages the keys. That protects stored disks, but the service can decrypt the file when serving an authorized request. In the other, encryption happens before upload and the storage service never receives what it needs to recover plaintext. &lt;a href="https://docs.aws.amazon.com/amazon-s3-encryption-client/latest/developerguide/client-server-side.html" rel="noopener noreferrer"&gt;AWS documents&lt;/a&gt; this distinction for its server-side and client-side encryption options.&lt;br&gt;
There is a harder case between those two: a server holds part of the material needed to open a file, but cannot open it alone. Does that count as end-to-end encryption? I think the useful questions are more specific. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can the operator decrypt without a recipient? &lt;br&gt;
Can it substitute a recipient? &lt;br&gt;
Can it change the author’s rules? What happens when a recipient’s device is compromised?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’m working through those questions in own project. Files carry &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;encrypted content,&lt;/li&gt;
&lt;li&gt;authenticated chunks &lt;/li&gt;
&lt;li&gt;a signed header &lt;/li&gt;
&lt;li&gt;recipient key slots&lt;/li&gt;
&lt;li&gt;access rules&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The server participates in granting access, but its share alone is insufficient to derive the file key. That is a narrower claim than “the provider cannot affect access”: the server can deny service, and a server cooperating with a recipient is outside this protection boundary.&lt;/p&gt;

&lt;p&gt;I also want access decisions to follow the file instead of relying on the folder or network where it happens to live. &lt;/p&gt;

&lt;p&gt;NIST’s zero-trust architecture frames access around individual resources and least privilege, without granting trust simply because a request comes from a particular network.&lt;/p&gt;

&lt;p&gt;Applying that idea to files raises practical questions about identity, short-lived access, offline use, and revocation. Revocation can restrict future access; it cannot erase plaintext someone has already received.&lt;/p&gt;

&lt;p&gt;Those questions become sharper with AI agents. A pipeline may need one agent to read a document, another to transform it, and a third to publish a result. Giving the whole pipeline one shared credential is convenient, but makes it difficult to limit the damage from a mistaken or compromised step. The direction I’m exploring is scoped grants for specific files and actions, with delegation that can narrow rights. It still cannot make a model “unsee” text it was allowed to read.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fip58duv9q41bskduui4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fip58duv9q41bskduui4v.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The part available to try today is OpenCrate, the Rust core behind Close Crate, and OpenCrateSDK, a simpler one-recipient API for JSON, messages, and file bytes. The SDK has Rust examples and experimental source-built interfaces for Python, Java, and C++. Hosting and agent workflows are still in development. The public libraries are an early, unaudited preview.&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;let&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_recipient&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;public&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recipient_public&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;recipient&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;purpose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"example.invoice.v1"&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"invoice:42"&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;sealed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;seal_bytes&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;public&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;br#"{"total":42}"#&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;opened&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open_bytes&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;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;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;sealed&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;I’m interested in where others draw the boundary: would you let a service participate in short-lived access to encrypted files if it could not decrypt them alone? What evidence would you need before trusting that design in an agent pipeline?&lt;/p&gt;

</description>
      <category>security</category>
      <category>opensource</category>
      <category>cybersecurity</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
