<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<!-- generated by https://github.com/cabo/kramdown-rfc version 1.7.29 (Ruby 3.4.4) -->
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" ipr="trust200902" docName="draft-denis-uricrypt-01" category="info" submissionType="independent" tocInclude="true" sortRefs="true" symRefs="true" version="3">
  <!-- xml2rfc v2v3 conversion 3.30.2 -->
  <front>
    <title abbrev="URICrypt">Prefix-Preserving Encryption for URIs</title>
    <seriesInfo name="Internet-Draft" value="draft-denis-uricrypt-01"/>
    <author initials="F." surname="Denis" fullname="Frank Denis">
      <organization>Fastly Inc.</organization>
      <address>
        <email>fde@00f.net</email>
      </address>
    </author>
    <date year="2025"/>
    <keyword>Internet-Draft</keyword>
    <abstract>
      <?line 19?>

<t>This document specifies URICrypt, a deterministic, prefix-preserving
encryption scheme for Uniform Resource Identifiers (URIs). URICrypt
encrypts URI paths while preserving their hierarchical structure,
enabling systems that rely on URI prefix relationships to continue
functioning with encrypted URIs. The scheme provides authenticated
encryption for each URI path component, preventing tampering,
reordering, or mixing of encrypted segments.</t>
    </abstract>
    <note removeInRFC="true">
      <name>Discussion Venues</name>
      <t>Source for this draft and an issue tracker can be found at
    <eref target="https://github.com/jedisct1/draft-denis-uricrypt"/>.</t>
    </note>
  </front>
  <middle>
    <?line 29?>

<section anchor="introduction">
      <name>Introduction</name>
      <t>This document specifies URICrypt, a method for encrypting Uniform
Resource Identifiers (URIs) while preserving their hierarchical
structure. The primary motivation is to enable systems that rely on
URI prefix relationships for routing, filtering, or access control to
continue functioning with encrypted URIs.</t>
      <t>URICrypt achieves prefix preservation through a chained encryption
model where the encryption of each URI component depends
cryptographically on all preceding components. This ensures that URIs
sharing common prefixes produce ciphertexts that also share common
encrypted prefixes.</t>
      <t>The scheme uses an extendable-output function (XOF) as its cryptographic primitive
and provides authenticated encryption for each component, preventing
tampering, reordering, or mixing of encrypted segments. URICrypt is a
reversible encryption scheme: encrypted URIs can be fully decrypted to
recover the original URIs, but only with possession of the secret key.</t>
      <section anchor="use-cases-and-motivations">
        <name>Use Cases and Motivations</name>
        <t>The main motivations include:</t>
        <ul spacing="normal">
          <li>
            <t>Access Control in CDNs: Content Delivery Networks often use URI
prefixes for routing and access control. URICrypt allows encryption of
resource paths while preserving the prefix structure needed for
CDN operations.</t>
          </li>
          <li>
            <t>Privacy-Preserving Logging: Systems can log encrypted URIs
without exposing sensitive path information, while still enabling
analysis based on URI structure.</t>
          </li>
          <li>
            <t>Confidential Data Sharing: When sharing links to sensitive
resources, URICrypt prevents the path structure itself from
revealing confidential information.</t>
          </li>
          <li>
            <t>Token-Based Access Systems: Systems that issue time-limited
access tokens can use URICrypt to obfuscate the underlying
resource location while maintaining routability.</t>
          </li>
          <li>
            <t>Multi-tenant Systems: In systems where multiple tenants share
infrastructure, URICrypt can isolate tenant data while allowing
shared components to be processed efficiently.</t>
          </li>
          <li>
            <t>Privacy-preserving Analytics: URICrypt can complement IPCrypt
<xref target="I-D.draft-denis-ipcrypt"/>. Together, they enable systems to perform
analytics on encrypted network flows and resource access patterns
without exposing sensitive information about either the network
endpoints or the specific resources being accessed.</t>
          </li>
        </ul>
      </section>
    </section>
    <section anchor="terminology">
      <name>Terminology</name>
      <t>The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”,
“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and
“OPTIONAL” in this document are to be interpreted as described in BCP
14 <xref target="RFC2119"/> <xref target="RFC8174"/> when, and only when, they appear in all capitals, as
shown here.</t>
      <t>Throughout this document, the following terms and conventions apply:</t>
      <ul spacing="normal">
        <li>
          <t>URI: Uniform Resource Identifier as defined in <xref target="RFC3986"/>.</t>
        </li>
        <li>
          <t>URI Component: A segment of a URI path, terminated by ‘/’, ‘?’, or
‘#’ characters. For encryption purposes, components include the
trailing terminator except for the final component.</t>
        </li>
        <li>
          <t>Scheme: The URI scheme (e.g., “https://”) which is preserved in
plaintext.</t>
        </li>
        <li>
          <t>XOF: Extendable-Output Function, a cryptographic function that can
produce output of arbitrary length.</t>
        </li>
        <li>
          <t>SIV: Synthetic Initialization Vector, a 16-byte value derived
from the accumulated state of all previous components, used for
authentication and as input to keystream generation.</t>
        </li>
        <li>
          <t>Domain Separation: The practice of using distinct inputs to
cryptographic functions to ensure outputs for different purposes
are not compatible.</t>
        </li>
        <li>
          <t>Prefix-preserving Encryption: An encryption scheme where, if two
plaintexts share a common prefix, their corresponding ciphertexts
also share a common (encrypted) prefix.</t>
        </li>
        <li>
          <t>Chained Encryption: A mode where encryption of each component
depends cryptographically on all preceding components.</t>
        </li>
      </ul>
    </section>
    <section anchor="uri-processing">
      <name>URI Processing</name>
      <t>This section describes how URIs are processed for encryption and
decryption.</t>
      <t>The overall encryption flow transforms a plaintext URI into an encrypted
URI while preserving its hierarchical structure:</t>
      <artwork><![CDATA[
+-------------------------------------------------------------+
|                         Input URI                           |
|          "https://example.com/path/to/resource"             |
+-------------------------------------------------------------+
                              |
                              v
+-------------------------------------------------------------+
|                    URI Decomposition                        |
+-------------------------------------------------------------+
|  Scheme: "https://"                                         |
|  Components: ["example.com/", "path/", "to/", "resource"]   |
+-------------------------------------------------------------+
                              |
                              v
+-------------------------------------------------------------+
|                 Chained Encryption Process                  |
+-------------------------------------------------------------+
|  For each component in sequence:                            |
|    1. Update state with plaintext                           |
|    2. Generate SIV from accumulated state                   |
|    3. Derive keystream using SIV                            |
|    4. Encrypt component with keystream                      |
|    5. Output: SIV || encrypted_component                    |
+-------------------------------------------------------------+
                              |
                              v
+-------------------------------------------------------------+
|                    Encoding & Assembly                      |
+-------------------------------------------------------------+
|  1. Concatenate all (SIV || encrypted_component) pairs      |
|  2. Apply base64url encoding                                |
|  3. Prepend original scheme                                 |
+-------------------------------------------------------------+
                              |
                              v
+-------------------------------------------------------------+
|                       Encrypted URI                         |
|          "https://HOGo9vauZ3b3xsPNPQng5apS..."              |
+-------------------------------------------------------------+
]]></artwork>
      <section anchor="uri-component-extraction">
        <name>URI Component Extraction</name>
        <t>Before encryption, a URI must be split into its scheme and path
components. The path is further divided into individual components for
chained encryption. Components are terminated by ‘/’, ‘?’, or ‘#’
characters, which allows proper handling of query strings and fragments.</t>
        <section anchor="full-uris">
          <name>Full URIs</name>
          <t>For a full URI including a scheme:</t>
          <artwork><![CDATA[
Input:  "https://example.com/a/b/c"

Components:

- Scheme: "https://"
- Component 1: "example.com/"
- Component 2: "a/"
- Component 3: "b/"
- Component 4: "c"
]]></artwork>
          <t>For a URI with query parameters:</t>
          <artwork><![CDATA[
Input:  "https://example.com/path?foo=bar&baz=qux"

Components:

- Scheme: "https://"
- Component 1: "example.com/"
- Component 2: "path?"
- Component 3: "foo=bar&baz=qux"
]]></artwork>
          <t>For a URI with a fragment:</t>
          <artwork><![CDATA[
Input:  "https://example.com/path#section"

Components:

- Scheme: "https://"
- Component 1: "example.com/"
- Component 2: "path#"
- Component 3: "section"
]]></artwork>
          <t>Note that all components except the last include their trailing terminator
character (‘/’, ‘?’, or ‘#’). This ensures proper reconstruction during decryption.</t>
        </section>
        <section anchor="path-only-uris">
          <name>Path-Only URIs</name>
          <t>For absolute paths (URIs starting with ‘/’ but without a scheme), the
leading ‘/’ is treated as the first component:</t>
          <artwork><![CDATA[
Input:  "/a/b/c"

Components:

- Scheme: "" (empty)
- Component 1: "/"
- Component 2: "a/"
- Component 3: "b/"
- Component 4: "c"
]]></artwork>
          <t>For a path with query parameters:</t>
          <artwork><![CDATA[
Input:  "/path/to/file?param=value"

Components:

- Scheme: "" (empty)
- Component 1: "/"
- Component 2: "path/"
- Component 3: "to/"
- Component 4: "file?"
- Component 5: "param=value"
]]></artwork>
          <t>The leading ‘/’ is explicitly encrypted as a component to maintain
consistency and enable proper prefix preservation for absolute paths.</t>
        </section>
      </section>
      <section anchor="component-reconstruction">
        <name>Component Reconstruction</name>
        <t>During decryption, components are joined to reconstruct the original
path:</t>
        <artwork><![CDATA[
Components: ["example.com/", "a/", "b/", "c"]
Reconstructed Path: "example.com/a/b/c"

When combined with the scheme: "https://example.com/a/b/c"
]]></artwork>
        <t>For absolute paths without a scheme:</t>
        <artwork><![CDATA[
Components: ["/", "a/", "b/", "c"]
Reconstructed Path: "/a/b/c"
]]></artwork>
      </section>
    </section>
    <section anchor="cryptographic-operations">
      <name>Cryptographic Operations</name>
      <t>The chained encryption model creates cryptographic dependencies between components, ensuring prefix preservation.</t>
      <artwork><![CDATA[
  URI: "https://example.com/path/to/resource"

  +-------------------+
  |   Component 1:    |
  |  "example.com/"   |
  +-------------------+
            |
            | Plaintext absorbed into components_xof
            v
  +-------------------+
  | SIV1 generation   |------> SIV1 (16 bytes)
  +-------------------+         |
                                |
                                v
                      Encrypt("example.com/")
                                |
                                v
                      Output1 = SIV1 || Ciphertext1
            |
            | State carries forward
            v
  +-------------------+
  |   Component 2:    |
  |     "path/"       |
  +-------------------+
            |
            | Plaintext absorbed (includes Component 1 state)
            v
  +-------------------+
  | SIV2 generation   |------> SIV2 (depends on 1)
  +-------------------+         |
                                |
                                v
                      Encrypt("path/")
                                |
                                v
                      Output2 = SIV2 || Ciphertext2
            |
            | State carries forward
            v
  +-------------------+
  |   Component 3:    |
  |      "to/"        |
  +-------------------+
            |
            | Plaintext absorbed (includes 1 + 2 state)
            v
  +-------------------+
  | SIV3 generation   |------> SIV3 (depends on 1, 2)
  +-------------------+         |
                                |
                                v
                      Encrypt("to/")
                                |
                                v
                      Output3 = SIV3 || Ciphertext3
            |
            | State carries forward
            v
  +-------------------+
  |   Component 4:    |
  |    "resource"     |
  +-------------------+
            |
            | Plaintext absorbed (includes 1 + 2 + 3 state)
            v
  +-------------------+
  | SIV4 generation   |------> SIV4 (depends on 1, 2, 3)
  +-------------------+         |
                                |
                                v
                      Encrypt("resource")
                                |
                                v
                      Output4 = SIV4 || Ciphertext4

  Final Output: Output1 || Output2 || Output3 || Output4
]]></artwork>
      <t>If URIs share a common prefix <tt>example.com/path/</tt>, their <tt>Output1</tt> and <tt>Output2</tt> will be identical.</t>
      <section anchor="xof-init">
        <name>XOF Initialization</name>
        <t>The base XOF is initialized with the secret key and context
parameters using length-prefixed encoding to prevent ambiguities.</t>
        <t>Two XOF instances are derived from the base XOF:</t>
        <ol spacing="normal" type="1"><li>
            <t>Components XOF: Updated with each component’s plaintext to
generate SIVs</t>
          </li>
          <li>
            <t>Base Keystream XOF: Used as the starting point for generating
keystream for each component</t>
          </li>
        </ol>
        <artwork><![CDATA[
  Input: len(key) || key || len(context) || context

  +------------------------------------------------------+
  | base_xof = TurboSHAKE128(domain_sep=0x1F)           |
  | base_xof.update(len(secret_key))                    |
  | base_xof.update(secret_key)                         |
  | base_xof.update(len(context))                       |
  | base_xof.update(context)                            |
  +------------------------------------------------------+
                            |
                            v
               +------------------------+
               |   Clone Base State     |
               +------------------------+
                            |
           +----------------+----------------+
           v                                 v
  +--------------------+          +--------------------+
  |  Components XOF    |          | Base Keystream XOF |
  +--------------------+          +--------------------+
  |   update("IV")     |          |   update("KS")     |
  +--------------------+          +--------------------+
           |                                 |
           |                                 |
           v                                 v
   For SIV Generation              For Keystream Base
   (Updated with each              (Cloned for each
    component plaintext)            component's keystream)
]]></artwork>
        <t>The initialization process is:</t>
        <artwork><![CDATA[
base_xof = TurboSHAKE128()
base_xof.update(len(secret_key))
base_xof.update(secret_key)
base_xof.update(len(context))
base_xof.update(context)

components_xof = base_xof.clone()
components_xof.update("IV")

base_keystream_xof = base_xof.clone()
base_keystream_xof.update("KS")
]]></artwork>
        <t>Note on XOF cloning: The <tt>.clone()</tt> operation creates a new XOF instance with
an identical internal state, preserving all previously absorbed data. After
cloning, the original and cloned XOFs can be updated and read from
independently. This allows the components_xof to maintain a running state
across all components while base_keystream_xof remains unchanged for creating
per-component keystreams.</t>
      </section>
      <section anchor="component-encryption">
        <name>Component Encryption</name>
        <t>For each component, the encryption process follows a precise sequence
that ensures both confidentiality and authenticity:</t>
        <ol spacing="normal" type="1"><li>
            <t>Update <tt>components_xof</tt> with the component plaintext</t>
          </li>
          <li>
            <t>Squeeze the SIV from <tt>components_xof</tt> (16 bytes). This requires cloning <tt>components_xof</tt> before reading, as reading may finalize the XOF.</t>
          </li>
          <li>
            <t>Create <tt>keystream_xof</tt> by cloning <tt>base_keystream_xof</tt> and updating it with SIV</t>
          </li>
          <li>
            <t>Calculate padding needed for base64 encoding</t>
          </li>
          <li>
            <t>Generate a keystream of length <tt>(component_length + padding)</tt></t>
          </li>
          <li>
            <t>XOR the padded component with the keystream</t>
          </li>
          <li>
            <t>Output SIV concatenated with <tt>encrypted_component</tt></t>
          </li>
        </ol>
        <t>The padding ensures clean base64url encoding without padding characters. Since
base64 encoding works with groups of 3 bytes (producing 4 characters), we pad each
<tt>(SIV || encrypted_component)</tt> pair to have a length that’s a multiple of 3:</t>
        <artwork><![CDATA[
total_bytes = 16 (SIV) + component_len
padding_len = (3 - total_bytes % 3) % 3
]]></artwork>
        <t>This formula calculates:
- How many bytes are needed to reach the next multiple of 3
- The outer modulo handles the case where <tt>total_bytes</tt> is already a multiple of 3</t>
        <t>Important: The <tt>components_xof</tt> maintains state across all components. After
generating the SIV for component N, the XOF can be updated with component N+1’s
plaintext. This chaining ensures that each component’s encryption depends on
all previous components, thus enabling the prefix-preserving property.</t>
      </section>
      <section anchor="component-decryption">
        <name>Component Decryption</name>
        <t>For each encrypted component, the decryption process is:</t>
        <ol spacing="normal" type="1"><li>
            <t>Read SIV from input (16 bytes)</t>
          </li>
          <li>
            <t>Create <tt>keystream_xof</tt> by cloning <tt>base_keystream_xof</tt> and updating it with SIV</t>
          </li>
          <li>
            <t>Decrypt bytes incrementally to determine component boundaries:
            </t>
            <ul spacing="normal">
              <li>
                <t>Generate keystream bytes one at a time from the XOF</t>
              </li>
              <li>
                <t>XOR each encrypted byte with its corresponding keystream byte</t>
              </li>
              <li>
                <t>Check each decrypted byte for component terminators (‘/’, ‘?’, ‘#’)</t>
              </li>
              <li>
                <t>When a terminator is found, the component is complete.</t>
              </li>
              <li>
                <t>Skip any padding bytes (null bytes) after the component</t>
              </li>
            </ul>
          </li>
          <li>
            <t>Update <tt>components_xof</tt> with the complete plaintext component (including terminator)</t>
          </li>
          <li>
            <t>Generate the expected SIV from <tt>components_xof</tt></t>
          </li>
          <li>
            <t>Compare the expected SIV with the received SIV (constant-time)</t>
          </li>
          <li>
            <t>If mismatch, return <tt>error</tt></t>
          </li>
        </ol>
        <section anchor="component-boundary-detection">
          <name>Component Boundary Detection</name>
          <t>During decryption, component boundaries are discovered dynamically by examining the decrypted plaintext:</t>
          <ul spacing="normal">
            <li>
              <t>Each component (except possibly the last) ends with a terminator character (‘/’, ‘?’, or ‘#’)</t>
            </li>
            <li>
              <t>When a terminator is encountered, we know the component is complete</t>
            </li>
            <li>
              <t>After finding the terminator, we skip padding bytes to align to the next 3-byte boundary.</t>
            </li>
            <li>
              <t>The padding length can be calculated: <tt>padding = (3 - ((SIV_size + bytes_read) % 3)) % 3</tt></t>
            </li>
          </ul>
          <t>This approach eliminates the need for explicit length encoding, as the component structure itself provides the necessary boundary information.</t>
          <t>Any tampering with the encrypted data will cause the SIV comparison to fail.</t>
        </section>
      </section>
      <section anchor="padding-and-encoding">
        <name>Padding and Encoding</name>
        <t>To enable clean base64url encoding without padding characters (‘=’), each
encrypted component pair <tt>(SIV || ciphertext)</tt> is padded to be a multiple of 3 bytes.
This is necessary because base64 encoding processes 3 bytes at a time to produce
4 characters of output.</t>
        <t>The padding calculation <tt>(3 - (16 + component_len) % 3) % 3</tt> ensures the following:</t>
        <ul spacing="normal">
          <li>
            <t>If <tt>(16 + component_len) % 3 = 0</tt>: no padding needed (already aligned)</t>
          </li>
          <li>
            <t>If <tt>(16 + component_len) % 3 = 1</tt>: add 2 bytes of padding</t>
          </li>
          <li>
            <t>If <tt>(16 + component_len) % 3 = 2</tt>: add 1 byte of padding</t>
          </li>
        </ul>
        <t>The final output is encoded using URL-safe base64 <xref target="RFC4648"/>, with ‘-‘ replacing
‘+’ and ‘_’ replacing ‘/’ for URI compatibility.</t>
      </section>
    </section>
    <section anchor="algorithm-specification">
      <name>Algorithm Specification</name>
      <t>This section provides the complete algorithms for encryption and
decryption. The following functions and operations are used throughout
the algorithms:</t>
      <ul spacing="normal">
        <li>
          <t><tt>TurboSHAKE128()</tt>: Creates a new TurboSHAKE128 XOF instance with domain separation parameter 0x1F. This function produces an extensible output function (XOF) that can generate arbitrary-length outputs.</t>
        </li>
        <li>
          <t><tt>.update(data)</tt>: Absorbs the provided data into the XOF state. Data is processed sequentially and updates the internal state of the XOF.</t>
        </li>
        <li>
          <t><tt>.read(length)</tt>: Squeezes the specified number of bytes from the XOF’s output. Each call continues from where the previous read left off, producing a continuous stream of pseudorandom bytes.</t>
        </li>
        <li>
          <t><tt>.clone()</tt>: Creates a new XOF instance with an identical internal state to the original. This enables multiple independent computation paths from the same initial state.</t>
        </li>
        <li>
          <t>XOR operation: The bitwise exclusive OR operation between two byte sequences of equal length. This operation is used to combine plaintext with keystream for encryption, and ciphertext with keystream for decryption.</t>
        </li>
        <li>
          <t><tt>base64url_encode(data)</tt>: Converts binary data to a base64 string using URL-safe encoding (replacing ‘+’ with ‘-‘ and ‘/’ with ‘_’) and omitting padding characters.</t>
        </li>
        <li>
          <t><tt>base64url_decode(string)</tt>: Converts a URL-safe base64 string back to binary data, automatically handling the absence of padding characters.</t>
        </li>
        <li>
          <t><tt>Stream(data)</tt>: Creates a sequential reader for binary data, enabling byte-by-byte or block-based access to the contents.</t>
        </li>
        <li>
          <t><tt>constant_time_compare(a, b)</tt>: Compares two byte sequences in constant time, regardless of their contents. This prevents timing attacks by ensuring the comparison duration does not depend on which bytes differ.</t>
        </li>
        <li>
          <t><tt>len(data)</tt>: Returns the length of the provided data in bytes.</t>
        </li>
        <li>
          <t>Concatenation: The operation of joining two byte sequences end-to-end to form a single sequence.</t>
        </li>
        <li>
          <t><tt>zeros(count)</tt>: Generates a sequence of zero-valued bytes of the specified length, used for padding.</t>
        </li>
        <li>
          <t><tt>remove_padding(data)</tt>: Removes trailing zero bytes from a byte sequence to recover the original data length.</t>
        </li>
        <li>
          <t><tt>join(components)</tt>: Combines multiple path components into a single path string, preserving the terminator characters (‘/’, ‘?’, ‘#’) that are included in each component.</t>
        </li>
      </ul>
      <section anchor="encryption-algorithm">
        <name>Encryption Algorithm</name>
        <t>Input: secret_key, context, uri_string</t>
        <t>Output: encrypted_uri</t>
        <t>Steps:</t>
        <ol spacing="normal" type="1"><li>
            <t>Split URI into scheme and components</t>
          </li>
          <li>
            <t>Initialize XOF instances as described in <xref target="xof-init"/></t>
          </li>
          <li>
            <t><tt>encrypted_output = empty byte array</tt></t>
          </li>
          <li>
            <t>For each component:
            </t>
            <ul spacing="normal">
              <li>
                <t>Update <tt>components_xof</tt> with <tt>component</tt>.</t>
              </li>
              <li>
                <t><tt>SIV = components_xof.clone().read(16)</tt>.</t>
              </li>
              <li>
                <t><tt>keystream_xof = base_keystream_xof.clone()</tt>.</t>
              </li>
              <li>
                <t><tt>keystream_xof.update(SIV)</tt>.</t>
              </li>
              <li>
                <t><tt>padding_len = (3 - (16 + len(component)) % 3) % 3</tt>.</t>
              </li>
              <li>
                <t><tt>keystream = keystream_xof.read(len(component) + padding_len)</tt>.</t>
              </li>
              <li>
                <t><tt>padded_component = component concatenated with zeros(padding_len)</tt>.</t>
              </li>
              <li>
                <t><tt>encrypted_part = padded_component XOR keystream</tt>.</t>
              </li>
              <li>
                <t><tt>encrypted_output = encrypted_output concatenated with SIV concatenated with encrypted_part</tt>.</t>
              </li>
            </ul>
          </li>
          <li>
            <t><tt>base64_output = base64url_encode(encrypted_output)</tt>.</t>
          </li>
          <li>
            <t>If scheme is not empty: Return <tt>scheme + base64_output</tt></t>
          </li>
          <li>
            <t>Else if original URI started with ‘/’: Return <tt>'/' + base64_output</tt></t>
          </li>
          <li>
            <t>Else: Return <tt>base64_output</tt></t>
          </li>
        </ol>
      </section>
      <section anchor="decryption-algorithm">
        <name>Decryption Algorithm</name>
        <t>Input: secret_key, context, encrypted_uri</t>
        <t>Output: encrypted_uri</t>
        <t>Note: For path-only URIs (those starting with ‘/’), the output format is:
- ‘/’ followed by the base64url-encoded encrypted components
- This preserves the absolute path indicator in the encrypted form or error</t>
        <t>Steps:</t>
        <ol spacing="normal" type="1"><li>
            <t>Split encrypted URI into scheme and base64 part</t>
          </li>
          <li>
            <t><tt>decoded = base64url_decode(base64_part)</tt>. If decoding fails, return <tt>error</tt>.</t>
          </li>
          <li>
            <t>Initialize XOF instances as described in <xref target="xof-init"/></t>
          </li>
          <li>
            <t><tt>decrypted_components = empty list</tt></t>
          </li>
          <li>
            <t><tt>position = 0</tt></t>
          </li>
          <li>
            <t>While <tt>position &lt; len(decoded)</tt>:
            </t>
            <ul spacing="normal">
              <li>
                <t><tt>SIV = decoded[position:position+16]</tt>. If not enough bytes, return <tt>error</tt>.</t>
              </li>
              <li>
                <t><tt>keystream_xof = base_keystream_xof.clone().update(SIV)</tt>.</t>
              </li>
              <li>
                <t><tt>component_start = position + 16</tt></t>
              </li>
              <li>
                <t><tt>component = empty byte array</tt></t>
              </li>
              <li>
                <t><tt>position = position + 16</tt></t>
              </li>
              <li>
                <t>While <tt>position &lt; len(decoded)</tt>:
                </t>
                <ul spacing="normal">
                  <li>
                    <t><tt>decrypted_byte = decoded[position] XOR keystream_xof.read(1)</tt></t>
                  </li>
                  <li>
                    <t><tt>position = position + 1</tt></t>
                  </li>
                  <li>
                    <t>If <tt>decrypted_byte == 0x00</tt>: continue (skip padding)</t>
                  </li>
                  <li>
                    <t><tt>component.append(decrypted_byte)</tt></t>
                  </li>
                  <li>
                    <t>If <tt>decrypted_byte</tt> is ‘/’, ‘?’, or ‘#’:
                    </t>
                    <ul spacing="normal">
                      <li>
                        <t><tt>total_len = position - component_start</tt></t>
                      </li>
                      <li>
                        <t><tt>position = position + ((3 - ((16 + total_len) % 3)) % 3)</tt></t>
                      </li>
                      <li>
                        <t>Break inner loop</t>
                      </li>
                    </ul>
                  </li>
                </ul>
              </li>
              <li>
                <t>Update <tt>components_xof</tt> with <tt>component</tt>.</t>
              </li>
              <li>
                <t><tt>expected_SIV = components_xof.clone().read(16)</tt>.</t>
              </li>
              <li>
                <t>If <tt>constant_time_compare(SIV, expected_SIV) == false</tt>, return <tt>error</tt>.</t>
              </li>
              <li>
                <t><tt>decrypted_components.append(component)</tt>.</t>
              </li>
            </ul>
          </li>
          <li>
            <t><tt>decrypted_path = join(decrypted_components)</tt>.</t>
          </li>
          <li>
            <t>Return <tt>scheme + decrypted_path</tt></t>
          </li>
        </ol>
      </section>
    </section>
    <section anchor="implementation-details">
      <name>Implementation Details</name>
      <section anchor="turboshake128-usage">
        <name>TurboSHAKE128 Usage</name>
        <t>Implementations MUST use TurboSHAKE128 with a domain separation
parameter of <tt>0x1F</tt> for all operations. The TurboSHAKE128 XOF is used
for:</t>
        <ul spacing="normal">
          <li>
            <t>Generating SIVs from the components XOF</t>
          </li>
          <li>
            <t>Generating keystream for encryption/decryption</t>
          </li>
          <li>
            <t>All XOF operations in the initialization</t>
          </li>
        </ul>
        <t>TurboSHAKE128 is specified in <xref target="I-D.draft-irtf-cfrg-kangarootwelve"/> and provides the security
properties needed for this construction.</t>
      </section>
      <section anchor="key-and-context-handling">
        <name>Key and Context Handling</name>
        <t>The secret key MUST be at least 16 bytes long. Keys shorter than 16
bytes MUST be rejected. Implementations SHOULD validate that the key
does not consist of repeated patterns (e.g., identical first and
second halves) as a best practice.</t>
        <t>The context parameter is a string that provides domain separation.
Different applications SHOULD use different context strings to prevent
cross-application attacks. The context string MAY be empty.</t>
        <t>Both key and context are length-prefixed when absorbed into the base
XOF:</t>
        <artwork><![CDATA[
base_xof.update(len(secret_key) as uint8)
base_xof.update(secret_key)
base_xof.update(len(context) as uint8)
base_xof.update(context)
]]></artwork>
        <t>The length is encoded as a single byte, limiting keys and contexts to
255 bytes. This is sufficient for all practical use cases.</t>
      </section>
      <section anchor="error-handling">
        <name>Error Handling</name>
        <t>Implementations MUST NOT reveal the cause of decryption failures. All
error conditions (invalid base64, incorrect padding, SIV mismatch,
insufficient data) MUST result in identical, generic error messages.</t>
        <t>SIV comparison MUST be performed in constant-time to prevent timing
attacks.</t>
      </section>
    </section>
    <section anchor="security-guarantees">
      <name>Security Guarantees</name>
      <t>URICrypt provides the following cryptographic security guarantees:</t>
      <section anchor="confidentiality">
        <name>Confidentiality</name>
        <t>URICrypt achieves semantic security for URI path components through its use of TurboSHAKE128 as a pseudorandom function. Each component is encrypted using a unique keystream derived from the following:</t>
        <ul spacing="normal">
          <li>
            <t>The secret key</t>
          </li>
          <li>
            <t>The application context</t>
          </li>
          <li>
            <t>A synthetic initialization vector (SIV) that depends on all preceding components</t>
          </li>
        </ul>
        <t>This construction ensures that:</t>
        <ul spacing="normal">
          <li>
            <t>An attacker without the secret key cannot recover plaintext components from ciphertexts.</t>
          </li>
          <li>
            <t>The keystream generation is computationally indistinguishable from random for each unique (key, context, path-prefix) tuple.</t>
          </li>
          <li>
            <t>Components are protected by at least 128 bits of security against brute-force attacks.</t>
          </li>
        </ul>
      </section>
      <section anchor="authenticity-and-integrity">
        <name>Authenticity and Integrity</name>
        <t>Each URI component is authenticated through the SIV mechanism:</t>
        <ul spacing="normal">
          <li>
            <t>The SIV acts as a Message Authentication Code (MAC) computed over the component and all preceding components.</t>
          </li>
          <li>
            <t>Any modification to a component will cause the SIV verification to fail during decryption.</t>
          </li>
          <li>
            <t>The chained construction ensures that reordering, insertion, or deletion of components is detected.</t>
          </li>
          <li>
            <t>Authentication provides 128-bit security against forgery attempts.</t>
          </li>
        </ul>
      </section>
      <section anchor="prefix-preserving-property">
        <name>Prefix-Preserving Property</name>
        <t>URICrypt maintains a controlled information leakage pattern:</t>
        <ul spacing="normal">
          <li>
            <t>URIs sharing a common prefix will produce ciphertexts with the same encrypted prefix.</t>
          </li>
          <li>
            <t>This property is deterministic and intentional, enabling systems to perform prefix-based operations.</t>
          </li>
          <li>
            <t>The leakage is limited to prefix structure only—no information about non-matching suffixes is revealed.</t>
          </li>
        </ul>
      </section>
      <section anchor="domain-separation">
        <name>Domain Separation</name>
        <t>The context parameter provides cryptographic domain separation:</t>
        <ul spacing="normal">
          <li>
            <t>Different contexts with the same key produce completely independent ciphertexts.</t>
          </li>
          <li>
            <t>This prevents cross-context attacks where ciphertexts from one application could be used in another.</t>
          </li>
          <li>
            <t>Context binding is cryptographically enforced through the XOF initialization.</t>
          </li>
        </ul>
      </section>
      <section anchor="key-commitment">
        <name>Key Commitment</name>
        <t>URICrypt provides full key-commitment security.</t>
        <t>The scheme is fully key-committing, meaning that a ciphertext can only be decrypted with the exact key that was used to encrypt it. It is computationally infeasible to find two different keys that successfully decrypt the same ciphertext to valid plaintexts.</t>
      </section>
      <section anchor="resistance-to-common-attacks">
        <name>Resistance to Common Attacks</name>
        <t>URICrypt resists several categories of attacks:</t>
        <t>Chosen-plaintext Attacks (CPA): While deterministic, URICrypt is CPA-secure for unique inputs. The determinism is a design requirement for prefix preservation.</t>
        <t>Tampering Detection: Any bit flip, truncation, or modification in the ciphertext will be detected with overwhelming probability (1 - 2<sup>-128</sup>).</t>
        <t>Length-extension Attacks: The use of length-prefixed encoding and domain separation prevents length-extension attacks.</t>
        <t>Replay Attacks: Within a single (key, context) pair, replay is possible due to determinism. Applications requiring replay protection should incorporate timestamps or nonces into the context.</t>
        <t>Key Recovery: TurboSHAKE128’s security properties ensure that observing ciphertexts does not leak information about the secret key.</t>
      </section>
      <section anchor="security-bounds">
        <name>Security Bounds</name>
        <t>The security of URICrypt is bounded by the following:</t>
        <ul spacing="normal">
          <li>
            <t>Key strength: Minimum 128-bit security with 16-byte keys</t>
          </li>
          <li>
            <t>Collision resistance: 2<sup>64</sup> birthday bound for SIV collisions</t>
          </li>
          <li>
            <t>Authentication security: 2<sup>-128</sup> probability of successful forgery</t>
          </li>
          <li>
            <t>Computational security: Based on TurboSHAKE128’s proven security as an XOF</t>
          </li>
        </ul>
      </section>
      <section anchor="limitations-and-trade-offs">
        <name>Limitations and Trade-offs</name>
        <t>URICrypt makes specific security trade-offs for functionality, including the following:</t>
        <ul spacing="normal">
          <li>
            <t>Deterministic encryption: Same inputs produce same outputs, enabling certain traffic analysis</t>
          </li>
          <li>
            <t>Length preservation: Component lengths are not hidden, potentially revealing information patterns</t>
          </li>
          <li>
            <t>Prefix structure leakage: The hierarchical structure of URIs is preserved by design</t>
          </li>
        </ul>
        <t>These trade-offs are intentional and necessary for the prefix-preserving functionality. Applications requiring stronger privacy guarantees should evaluate whether URICrypt’s properties align with their threat model.</t>
      </section>
    </section>
    <section anchor="security-considerations">
      <name>Security Considerations</name>
      <t>URICrypt provides confidentiality and integrity for URI paths while
preserving prefix relationships. The encryption is fully reversible:
encrypted URIs can be decrypted to recover the original plaintext URIs,
but only with knowledge of the secret key. The security properties depend on:</t>
      <ul spacing="normal">
        <li>
          <t>Key Secrecy: The security of URICrypt depends entirely on the
secrecy of the secret key. Keys MUST be generated using a
cryptographically secure random number generator <xref target="RFC4086"/> and
stored securely.</t>
        </li>
        <li>
          <t>Deterministic Encryption: URICrypt is deterministic - identical
inputs produce identical outputs. This allows observers to detect
when the same URI is encrypted multiple times. Applications
requiring unlinkability SHOULD incorporate additional entropy (e.g.,
via the context parameter).</t>
        </li>
        <li>
          <t>Prefix Preservation: While essential for functionality, prefix
preservation leaks information about URI structure. Systems where
this information is sensitive SHOULD consider alternative
approaches.</t>
        </li>
        <li>
          <t>Context Separation: The context parameter prevents cross-context
attacks. Applications MUST use distinct contexts for different
purposes, even when sharing keys.</t>
        </li>
        <li>
          <t>Component Authentication: Each component is authenticated via
the SIV mechanism. Any modification, reordering, or truncation of
components will be detected during decryption.</t>
        </li>
        <li>
          <t>Length Leakage: The length of each component is preserved in the
encrypted output. Applications sensitive to length information
SHOULD consider padding components to fixed lengths.</t>
        </li>
        <li>
          <t>Key Reuse: Using the same key with different contexts is safe, but
using the same (key, context) pair for different applications is
NOT RECOMMENDED.</t>
        </li>
      </ul>
    </section>
    <section numbered="false" anchor="iana-considerations">
      <name>IANA Considerations</name>
      <t>This document has no actions for IANA.</t>
    </section>
    <section numbered="false" anchor="acknowledgments">
      <name>Acknowledgments</name>
      <t>The author would like to thank Maciej Soltysiak for highlighting the importance of properly supporting query parameters and fragments in URI encryption.</t>
    </section>
  </middle>
  <back>
    <references anchor="sec-normative-references">
      <name>Normative References</name>
      <reference anchor="I-D.draft-denis-ipcrypt">
        <front>
          <title>Methods for IP Address Encryption and Obfuscation</title>
          <author fullname="Frank Denis" initials="F." surname="Denis">
            <organization>Fastly Inc.</organization>
          </author>
          <date day="19" month="September" year="2025"/>
          <abstract>
            <t>   This document specifies secure, efficient methods for encrypting IP
   addresses for privacy-preserving storage, logging, and analytics.
   Unlike truncation, which destroys data irreversibly, these methods
   are reversible with the encryption key while providing strong privacy
   guarantees.

   Four modes are defined: ipcrypt-deterministic (format-preserving,
   16-byte output), ipcrypt-pfx (prefix-preserving, native address
   size), ipcrypt-nd and ipcrypt-ndx (non-deterministic with random
   tweaks).  All support high-performance processing at network speeds
   and produce interoperable results across implementations.

            </t>
          </abstract>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-denis-ipcrypt-12"/>
      </reference>
      <reference anchor="RFC2119">
        <front>
          <title>Key words for use in RFCs to Indicate Requirement Levels</title>
          <author fullname="S. Bradner" initials="S." surname="Bradner"/>
          <date month="March" year="1997"/>
          <abstract>
            <t>In many standards track documents several words are used to signify the requirements in the specification. These words are often capitalized. This document defines these words as they should be interpreted in IETF documents. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</t>
          </abstract>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="2119"/>
        <seriesInfo name="DOI" value="10.17487/RFC2119"/>
      </reference>
      <reference anchor="RFC8174">
        <front>
          <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
          <author fullname="B. Leiba" initials="B." surname="Leiba"/>
          <date month="May" year="2017"/>
          <abstract>
            <t>RFC 2119 specifies common key words that may be used in protocol specifications. This document aims to reduce the ambiguity by clarifying that only UPPERCASE usage of the key words have the defined special meanings.</t>
          </abstract>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="8174"/>
        <seriesInfo name="DOI" value="10.17487/RFC8174"/>
      </reference>
      <reference anchor="RFC3986">
        <front>
          <title>Uniform Resource Identifier (URI): Generic Syntax</title>
          <author fullname="T. Berners-Lee" initials="T." surname="Berners-Lee"/>
          <author fullname="R. Fielding" initials="R." surname="Fielding"/>
          <author fullname="L. Masinter" initials="L." surname="Masinter"/>
          <date month="January" year="2005"/>
          <abstract>
            <t>A Uniform Resource Identifier (URI) is a compact sequence of characters that identifies an abstract or physical resource. This specification defines the generic URI syntax and a process for resolving URI references that might be in relative form, along with guidelines and security considerations for the use of URIs on the Internet. The URI syntax defines a grammar that is a superset of all valid URIs, allowing an implementation to parse the common components of a URI reference without knowing the scheme-specific requirements of every possible identifier. This specification does not define a generative grammar for URIs; that task is performed by the individual specifications of each URI scheme. [STANDARDS-TRACK]</t>
          </abstract>
        </front>
        <seriesInfo name="STD" value="66"/>
        <seriesInfo name="RFC" value="3986"/>
        <seriesInfo name="DOI" value="10.17487/RFC3986"/>
      </reference>
      <reference anchor="RFC4648">
        <front>
          <title>The Base16, Base32, and Base64 Data Encodings</title>
          <author fullname="S. Josefsson" initials="S." surname="Josefsson"/>
          <date month="October" year="2006"/>
          <abstract>
            <t>This document describes the commonly used base 64, base 32, and base 16 encoding schemes. It also discusses the use of line-feeds in encoded data, use of padding in encoded data, use of non-alphabet characters in encoded data, use of different encoding alphabets, and canonical encodings. [STANDARDS-TRACK]</t>
          </abstract>
        </front>
        <seriesInfo name="RFC" value="4648"/>
        <seriesInfo name="DOI" value="10.17487/RFC4648"/>
      </reference>
      <reference anchor="I-D.draft-irtf-cfrg-kangarootwelve">
        <front>
          <title>KangarooTwelve and TurboSHAKE</title>
          <author fullname="Benoît Viguier" initials="B." surname="Viguier">
            <organization>ABN AMRO Bank</organization>
          </author>
          <author fullname="David Wong" initials="D." surname="Wong">
            <organization>zkSecurity</organization>
          </author>
          <author fullname="Gilles Van Assche" initials="G." surname="Van Assche">
            <organization>STMicroelectronics</organization>
          </author>
          <author fullname="Quynh Dang" initials="Q." surname="Dang">
            <organization>National Institute of Standards and Technology</organization>
          </author>
          <author fullname="Joan Daemen" initials="J." surname="Daemen">
            <organization>Radboud University</organization>
          </author>
          <date day="21" month="February" year="2025"/>
          <abstract>
            <t>   This document defines four eXtendable Output Functions (XOF), hash
   functions with output of arbitrary length, named TurboSHAKE128,
   TurboSHAKE256, KT128 and KT256.

   All four functions provide efficient and secure hashing primitives,
   and the last two are able to exploit the parallelism of the
   implementation in a scalable way.

   This document is a product of the Crypto Forum Research Group.  It
   builds up on the definitions of the permutations and of the sponge
   construction in [FIPS 202], and is meant to serve as a stable
   reference and an implementation guide.

            </t>
          </abstract>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-irtf-cfrg-kangarootwelve-17"/>
      </reference>
      <reference anchor="RFC4086">
        <front>
          <title>Randomness Requirements for Security</title>
          <author fullname="D. Eastlake 3rd" initials="D." surname="Eastlake 3rd"/>
          <author fullname="J. Schiller" initials="J." surname="Schiller"/>
          <author fullname="S. Crocker" initials="S." surname="Crocker"/>
          <date month="June" year="2005"/>
          <abstract>
            <t>Security systems are built on strong cryptographic algorithms that foil pattern analysis attempts. However, the security of these systems is dependent on generating secret quantities for passwords, cryptographic keys, and similar quantities. The use of pseudo-random processes to generate secret quantities can result in pseudo-security. A sophisticated attacker may find it easier to reproduce the environment that produced the secret quantities and to search the resulting small set of possibilities than to locate the quantities in the whole of the potential number space.</t>
            <t>Choosing random quantities to foil a resourceful and motivated adversary is surprisingly difficult. This document points out many pitfalls in using poor entropy sources or traditional pseudo-random number generation techniques for generating such quantities. It recommends the use of truly random hardware techniques and shows that the existing hardware on many systems can be used for this purpose. It provides suggestions to ameliorate the problem when a hardware solution is not available, and it gives examples of how large such quantities need to be for some applications. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</t>
          </abstract>
        </front>
        <seriesInfo name="BCP" value="106"/>
        <seriesInfo name="RFC" value="4086"/>
        <seriesInfo name="DOI" value="10.17487/RFC4086"/>
      </reference>
    </references>
    <?line 785?>

<section anchor="pseudocode">
      <name>Pseudocode</name>
      <section anchor="uri-component-extraction-1">
        <name>URI Component Extraction</name>
        <artwork><![CDATA[
function extract_components(uri_string):
  if uri_string contains "://":
     scheme = substring up to and including "://"
     path = substring after "://"
  else:
     scheme = ""
     path = uri_string

  components = []

  // For absolute paths, treat leading "/" as first component
  if path starts with "/":
     components.append("/")
     path = substring after first "/"

  while path is not empty:
     terminator_pos = find_next_terminator(path)
     if terminator_pos found:
        component = substring(path, 0, terminator_pos + 1)
        path = substring(path, terminator_pos + 1)
        components.append(component)
     else:
        components.append(path)
        path = ""

  return (scheme, components)

function find_next_terminator(path):
  for i from 0 to length(path) - 1:
     if path[i] == '/' or path[i] == '?' or path[i] == '#':
        return i
  return not_found
]]></artwork>
      </section>
      <section anchor="xof-initialization">
        <name>XOF Initialization</name>
        <artwork><![CDATA[
function initialize_xofs(secret_key, context):
  // Initialize base XOF
  base_xof = TurboSHAKE128(0x1F)

  // Absorb key and context with length prefixes
  base_xof.update(uint8(len(secret_key)))
  base_xof.update(secret_key)
  base_xof.update(uint8(len(context)))
  base_xof.update(context)

  // Create components XOF
  components_xof = base_xof.clone()
  components_xof.update("IV")

  // Create base keystream XOF
  base_keystream_xof = base_xof.clone()
  base_keystream_xof.update("KS")

  return (components_xof, base_keystream_xof)
]]></artwork>
      </section>
      <section anchor="encryption-algorithm-1">
        <name>Encryption Algorithm</name>
        <artwork><![CDATA[
function uricrypt_encrypt(secret_key, context, uri_string):
  // Extract components
  (scheme, components) = extract_components(uri_string)

  // Initialize XOF instances with secret key and context
  (components_xof, base_keystream_xof) =
      initialize_xofs(secret_key, context)
  if error: return error

  encrypted_output = byte_array()

  // Process each component
  for component in components:
     // Update components XOF for SIV computation
     components_xof.update(component)

     // Generate 16-byte Synthetic Initialization Vector (SIV)
     siv = components_xof.squeeze(16)

     // Create keystream XOF for this component
     keystream_xof = base_keystream_xof.clone()
     keystream_xof.update(siv)

     // Calculate padding for base64 encoding alignment
     // The total bytes (SIV + component) must be a multiple of 3
     // to produce clean base64 output without padding characters
     component_len = len(component)
     padding_len = (3 - (16 + component_len) % 3) % 3

     // Generate keystream
     keystream = keystream_xof.squeeze(component_len + padding_len)

     // Pad component to align with base64 encoding requirements
     padded_component = component + byte_array(padding_len)

     // Encrypt using XOR with keystream
     encrypted_part = xor_bytes(padded_component, keystream)

     // Append to output
     encrypted_output.extend(siv)
     encrypted_output.extend(encrypted_part)

  // Base64 encode with URL-safe characters and no padding
  base64_output = base64_urlsafe_no_pad_encode(encrypted_output)

  // Return with appropriate prefix
  if scheme != "":
     return scheme + base64_output
  else if uri_string starts with "/":
     return "/" + base64_output
  else:
     return base64_output
]]></artwork>
      </section>
      <section anchor="decryption-algorithm-1">
        <name>Decryption Algorithm</name>
        <artwork><![CDATA[
function uricrypt_decrypt(secret_key, context, encrypted_uri):
  // Split scheme and base64
  if encrypted_uri contains "://":
     scheme = substring up to and including "://"
     base64_part = substring after "://"
  else:
     scheme = ""
     base64_part = encrypted_uri

  // Decode base64
  try:
     decoded = base64_urlsafe_no_pad_decode(base64_part)
  catch:
     return error("Decryption failed")

  // Initialize XOF instances with secret key and context
  (components_xof, base_keystream_xof) =
      initialize_xofs(secret_key, context)
  if error: return error

  decrypted_components = []
  input_stream = ByteStream(decoded)

  // Process each component
  while not input_stream.empty():
     // Read SIV
     siv = input_stream.read(16)
     if len(siv) != 16:
        return error("Decryption failed")

     // Create keystream XOF
     keystream_xof = base_keystream_xof.clone()
     keystream_xof.update(siv)

     // Determine component length by checking padding constraints
     remaining = input_stream.remaining()
     if remaining == 0:
        return error("Decryption failed")

     // Find valid component length by checking padding alignment
     component_data = None
     for possible_len in range(1, remaining + 1):
        total_len = 16 + possible_len
        padding_len = (3 - total_len % 3) % 3
        if possible_len >= padding_len:
           component_data = input_stream.peek(possible_len)
           break

     if component_data is None:
        return error("Decryption failed")

     // Read encrypted data
     encrypted_part = input_stream.read(len(component_data))

     // Generate keystream and decrypt
     keystream = keystream_xof.squeeze(len(encrypted_part))
     padded_plaintext = xor_bytes(encrypted_part, keystream)

     // Remove padding bytes added for base64 alignment
     padding_len = (3 - (16 + len(encrypted_part)) % 3) % 3
     component = padded_plaintext[:-padding_len] if padding_len > 0 else padded_plaintext

     // Update XOF with plaintext
     components_xof.update(component)

     // Generate expected SIV
     expected_siv = components_xof.squeeze(16)

     // Authenticate using constant-time comparison to prevent timing attacks
     if not constant_time_equal(siv, expected_siv):
        return error("Decryption failed")

     decrypted_components.append(component)

  // Reconstruct URI
  if scheme and decrypted_components:
     path = "".join(decrypted_components)
     return scheme + path
  elif decrypted_components:
     return "/" + "".join(decrypted_components)
  else:
     return ""
]]></artwork>
      </section>
      <section anchor="padding-and-encoding-1">
        <name>Padding and Encoding</name>
        <artwork><![CDATA[
function calculate_padding(component_len):
  // Calculate padding needed for base64 encoding alignment
  // The combined SIV (16 bytes) + component must be divisible by 3
  // for clean base64 encoding without '=' padding characters
  total_len = 16 + component_len
  return (3 - total_len % 3) % 3

function base64_urlsafe_no_pad_encode(data):
  // Use standard base64 encoding
  encoded = standard_base64_encode(data)
  // Make URL-safe and remove padding for URI compatibility
  encoded = encoded.replace('+', '-')
                   .replace('/', '_')
                   .rstrip('=')
  return encoded

function base64_urlsafe_no_pad_decode(encoded):
  // Add padding if needed for standard decoder
  padding = (4 - len(encoded) % 4) % 4
  if padding > 0:
     encoded = encoded + ('=' * padding)
  // Make standard base64
  encoded = encoded.replace('-', '+')
                   .replace('_', '/')
  // Decode
  return standard_base64_decode(encoded)
]]></artwork>
      </section>
    </section>
    <section anchor="test-vectors">
      <name>Test Vectors</name>
      <t>These test vectors were generated using the reference Rust implementation
of URICrypt with TurboSHAKE128.</t>
      <artwork><![CDATA[
Test Configuration:
secret_key (hex): 0102030405060708090a0b0c0d0e0f10
context: "test-context"
]]></artwork>
      <section anchor="test-vector-1-full-uri">
        <name>Test Vector 1: Full URI</name>
        <artwork><![CDATA[
Input: "https://example.com/a/b/c"
Output: "https://HOGo9vauZ3b3xsPNPQng5apSzL5V7QW94C7USgN8mHZJ337AKSWOu
         cUwMuD-uUfF95SsSHCNgBkXUnH1uGll_YtBltXSqKEHNcYJJwbdFdhfWz19"
]]></artwork>
      </section>
      <section anchor="test-vector-2-path-only-uri">
        <name>Test Vector 2: Path-Only URI</name>
        <artwork><![CDATA[
Input: "/a/b/c"
Output: "/b9bCOhqZsvU9XxGOMk6d8QFQhTIdI_xYKpds2lWXpZCms5-az9wtfUft3rec
         3d9YkUo0N7VcxO5MXfxE5UobvgTJX8UpRdNN"
]]></artwork>
      </section>
      <section anchor="test-vector-3-multi-component-path">
        <name>Test Vector 3: Multi-Component Path</name>
        <artwork><![CDATA[
Input: "https://cdn.example.com/videos/2025/03/file.mp4"
Output: "https://hxUM2N3txwYjGxjvCpWn30SznxR0v0fDbkSQgCTXCUu7Rq8iSbWP4
         0OvYxKs9zC3kw1JNzAc4Wuj7RZvRd0VUprJWLs5KJPnWsA9Kguxa_J7XviTS3G
         Tqf-XZdPxYyq1Y1MXVE9_4ojHwm6jBDUkVthAkuNe5Cqk_h6d"
]]></artwork>
      </section>
      <section anchor="test-vector-4-root-with-scheme">
        <name>Test Vector 4: Root with Scheme</name>
        <artwork><![CDATA[
Input: "https://example.com/"
Output: "https://HOGo9vauZ3b3xsPNPQng5apSzL5V7QW94C7USgN8"
]]></artwork>
      </section>
      <section anchor="test-vector-5-simple-path">
        <name>Test Vector 5: Simple Path</name>
        <artwork><![CDATA[
Input: "/path/to/resource"
Output: "/b9bCOhqZsvU9XxGOMk6d8QFQPTuMlsQKDBhAbc77JvsdRj0kxiFipunATQmm
         CkNhAe0BPP2EqQoxORElY_ukfUYSrr9mIMfiO9joa3Kn5RS7eSKr"
]]></artwork>
      </section>
      <section anchor="test-vector-6-uri-with-query-parameters">
        <name>Test Vector 6: URI with Query Parameters</name>
        <artwork><![CDATA[
Input: "https://example.com/search?q=test&limit=10"
Output: "https://HOGo9vauZ3b3xsPNPQng5apSzL5V7QW94C7USgN8cl2BBtuWmxTsI
         Ij59ka3KeDsaqXFGnKgW9aLLR36YvUf9ORkMnVE5PTR_3DiO43hL9WjdSu7L9
         FN"
]]></artwork>
      </section>
      <section anchor="test-vector-7-uri-with-fragment">
        <name>Test Vector 7: URI with Fragment</name>
        <artwork><![CDATA[
Input: "https://docs.example.com/guide#installation"
Output: "https://ypHTiw0JUMcr4bUjQH9Dxo8wGWHyfFlLq8VrOE-zX6IbgLFxYX_Jm
         2hzivywvrpIBWa-9Jl6nSZLq2pd35QwkDsc1-_Kao2BvyBB19ndu1PpwQv1wy
         uA"
]]></artwork>
      </section>
      <section anchor="test-vector-8-uri-with-query-and-fragment">
        <name>Test Vector 8: URI with Query and Fragment</name>
        <artwork><![CDATA[
Input: "/api/v2/users?id=123#profile"
Output: "/b9bCOhqZsvU9XxGOMk6d8QFQwcP2C3bJVNVZDge7zfub_ai4x6LaUlXp-XjZ
         XOgZlLloIbasK-JKlbeKeKV2rctq5bX9zQh1KogN2zaggTMZioUb4kwGIKp8Z
         y744xQwGDG64n6GhN56XEM8LvBfJuEj6ZgsjeLbTPIMbCmO0pJhzVSh"
]]></artwork>
      </section>
    </section>
  </back>
  <!-- ##markdown-source:
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
0YlUFasWl/LLx8JZ4ZJcTC8iUJXCodMcjDN21G6BD5ipUD+ozPNMzmMeKz2A
bMG4eB/KFkosYA5nwCBsl64h8rhWoup9RCMfJ1lQLe5pyqiotDzIrUH+45a0
89DG4qI9lT5FWGgp07LGmZCR/8dqspIpbcUUPlA1YZ9dV3HJX0JiDTkJ+vbq
uJ3TNKzC96ROhdREjO+SNbGBQ0RSO5OowWeBfpB26AsyKkFwZWVHI8f+/hgr
XZN7KQeYMs1OozAD+iDy190R0PFAdP2BQsTmi38Q55FnAylYZpvym5/VgK76
5fHyxi98VrodMfVKJMlcPe0f4KzzGKjxZxARIr9RR3ksljcua56slTOGGRoQ
zZnpgSCj2QxiaLEq/H5x2aHh3svNS3uiOduyn0EnT3k9wPNtB31Muh1mw3a/
Nu0ljI6BfabiQcOdrHn3WuTRK3uVu1ZaT0uF+ljW6VO0RAmHl+6g+pM3pGeX
hKWe13LpNt1pngNQr+DygL4qoiSZ/hkFQkUaLn6vJoFQq7deYKYlYc/bRNwN
feD4l3fcn7p7r7BnhZpB+j112QQxyy2yCRp1k+CYZxTqK4k3d4pL6kGrWtix
qbEb5MjjSCC5Hq7zzB8FFMe1ns8ENXFDj677tAxfVJxgJq0XjYpLdIRdcm1g
FNk9I8kCqnGxsYPBG2LnZtD8X5lAMKbbGm+IxUkxCOg8Oc8H8cQ4GLAjJ2wI
l7Rch1IquflgnuduEz2a2k7ivmqmH2CY5sNWf5iOWld+DMZrkuQ3QXQdfP8u
nNaqMi8a5Hc+82RMGANRVu4H9YazKybZuHgr06h3WEkQr6VzQLaDNbnWhLge
hU2jAAuRVQwYbhgYcpR8J7JxknIk0Y/hAY8fUEPT4AuRfFuUqUK26AMjMhz4
qhBaZo542q6WVaSCcpqmXCasmheqTm3Ge8aVw+j1zbDKcSDGPkAua3Kdai/I
ct1nTAYEpKJkOV5DsnVzafb7uYF4hVLb3q5uMIZN9aTTWp8Nad60IFNLqRYE
Jifdo0SGljWFckIwkbsjxcH2JwQtyTg4xvOE3WB2ajwZmeVs+BsK/zlFiUo5
8zi33U5VnJOPiKAsYOyzP56YeMcUOkPRqkIm94gVlSBcSpMciW1JUPtSdW1t
MFDTuJX1delFESrYlBWqo6dmLJIugIgQa5i/IrPhXiBPtu5ILXfDlpLcn1Wm
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
gU89WWzadoqBYpkmStBSb9M7w7oz9mtlOoUHP+NaDTAxMOu3nFzBzS0pBNkP
xAneuNApPfLs5A4SRI4LXb45jxanwplRoRKcjd0hGuPgttkVneXOSme1s9ZZ
72x0nnaedTY7fqfX6XcGnaAzXO540jzBF0vChCrUbviXdUZ8q556la7zMsy7
3nOoGhPc+8bkb/vr75+++7C5tvP0/HR0+Gzy+vOb1dWn229PPxwVBpn985uD
YrdVnA9fbq6fZqevdw5Hz68+nsevl4tXUXTxKX8e5R9Pv7598fqw/+nNm5ve
4OVgPPzwbXmz/lArXfddqO7JKsd40tvs7RyNv37Ors83P96+Ojq42hg8e/fy
3fhsb7B3cfvp7XSQrUQfPk4/70yy9Zb/bfMmH54P89U06JtjrA42P12dJ53D
p+/7t0frBx+Hty/Wz5Pe9ejszcdn59OTweFh/X5Xu+IAXYUtE2bF/dcjpD+I
2zZSMM0qyZ6sdFbWn3RW6dWl7cl0rQZN49vzg5XD1fz25tOXV7dfrnemH+LV
zum3+Pakc90Z7vauTt+Nds4+7pwXT0++PgtPex+O18z5OkfXn27fZpvfdlav
bpbfHH7b7q99KL48Pfl8fTLovD+fpm8+7Gfrb98cxx+y7c23o+LWv3jz9ON1
eHa6+srMc/Z12Pr4eXB8+2n2dfnT8sHH9y82L9aSL69vJhtfnu+eX73Px9tX
xWGwvvP16mK8MagH2lpXnCSJaktMysD9FPwniLd+F+tdcUpXvQZjNW+7vJ/m
js+Kgyh793b3+Xi713/69M11Njj50rm6DV+G0yLePns3mRhY7lwdjreDzvPj
45UXX98lt0cnL6JPF8XV8PzTaZpuTvYOhuHR5pfEX30br5+cPg1O36b1B9no
mtczv6P8gWOdP3A/XLMAEyR/+rqFHOdvVEaytdz5E9DuRyvPn+fFh8ntWbZn
zrv3ZX3zCg4T7Gb+148vX8VvRx82/f39k9WNT9fnw82jk6uD+P2L9eOzk4vV
3fBobXW8v/nhy+C0eLq/aaZ5OeciPrWA8FLmS9QffpD0M+cajgq4h4/IFRVx
tmLN6WfT12fhTefN+UE/Xeudf3n3enP3Nnl28+rD69nwZbT/9dn79OhF69vH
jb3eaP/l7aePF28sbK+Mv4XXs5vrdLr3/IPf2nwTbcSnn/e/rkwHq+vvbq52
s/5y6+Ktn6w8v549f768GQ+K5ePpzbvr5ZuZmabYrj/9swoJoGJUD4cn/jR8
cr3ypMiAPn4KB1vLK6uPpmmC7OchZH7TP17ZWe29eX/4/vPuKHj6bVj0Lvxw
7XZj3z+PPk5bH798Njv+eDT6HO1HyR4I77etN2+jXvA2ePt+Je3nX9d7Hze/
vRsvv01Ghyvf/NHo7OBzmJz31q5uXu29nT6zppk9XVu7fXfzavfVxlq88Wp8
uL7x8cXBs/3r58M3xYsvG59H2Zdgv3d2vHfQ25kcdaZvxt/en44ZWv8NjgqA
DxqbAAA=

-->

</rfc>
