@@ -101,10 +101,13 @@ Throughout this document, the following terms and conventions apply:
101101* XOF: Extendable-Output Function, a cryptographic function that can
102102 produce output of arbitrary length.
103103
104- * SIV: Synthetic Initialization Vector, a 16-byte value derived
105- from the accumulated state of all previous components, used for
104+ * SIV: Synthetic Initialization Vector, a value derived from the
105+ accumulated state of all previous components, used for
106106 authentication and as input to keystream generation.
107107
108+ * SIVLEN: The length of the Synthetic Initialization Vector in bytes,
109+ defined as 16 bytes (128 bits) for this specification.
110+
108111* Domain Separation: The practice of using distinct inputs to
109112 cryptographic functions to ensure outputs for different purposes
110113 are not compatible.
@@ -287,7 +290,7 @@ The chained encryption model creates cryptographic dependencies between componen
287290 | Plaintext absorbed into components_xof
288291 v
289292 +-------------------+
290- | SIV1 generation |------> SIV1 (16 bytes)
293+ | SIV1 generation |------> SIV1 (SIVLEN bytes)
291294 +-------------------+ |
292295 |
293296 v
@@ -431,7 +434,7 @@ For each component, the encryption process follows a precise sequence
431434that ensures both confidentiality and authenticity :
432435
4334361. Update `components_xof` with the component plaintext
434- 2. Squeeze the SIV from `components_xof` (16 bytes). This requires cloning `components_xof` before reading, as reading may finalize the XOF.
437+ 2. Squeeze the SIV from `components_xof` (SIVLEN bytes). This requires cloning `components_xof` before reading, as reading may finalize the XOF.
4354383. Create `keystream_xof` by cloning `base_keystream_xof` and updating it with SIV
4364394. Calculate padding needed for base64 encoding
4374405. Generate a keystream of length `(component_length + padding)`
@@ -443,7 +446,7 @@ base64 encoding works with groups of 3 bytes (producing 4 characters), we pad ea
443446`(SIV || encrypted_component)` pair to have a length that's a multiple of 3 :
444447
445448~~~
446- total_bytes = 16 (SIV) + component_len
449+ total_bytes = SIVLEN (SIV) + component_len
447450padding_len = (3 - total_bytes % 3) % 3
448451~~~
449452
@@ -460,7 +463,7 @@ all previous components, thus enabling the prefix-preserving property.
460463
461464For each encrypted component, the decryption process is :
462465
463- 1. Read SIV from input (16 bytes)
466+ 1. Read SIV from input (SIVLEN bytes)
4644672. Create `keystream_xof` by cloning `base_keystream_xof` and updating it with SIV
4654683. Decrypt bytes incrementally to determine component boundaries :
466469 - Generate keystream bytes one at a time from the XOF
@@ -493,11 +496,11 @@ encrypted component pair `(SIV || ciphertext)` is padded to be a multiple of 3 b
493496This is necessary because base64 encoding processes 3 bytes at a time to produce
4944974 characters of output.
495498
496- The padding calculation `(3 - (16 + component_len) % 3) % 3` ensures the following :
499+ The padding calculation `(3 - (SIVLEN + component_len) % 3) % 3` ensures the following :
497500
498- - If `(16 + component_len) % 3 = 0` : no padding needed (already aligned)
499- - If `(16 + component_len) % 3 = 1` : add 2 bytes of padding
500- - If `(16 + component_len) % 3 = 2` : add 1 byte of padding
501+ - If `(SIVLEN + component_len) % 3 = 0` : no padding needed (already aligned)
502+ - If `(SIVLEN + component_len) % 3 = 1` : add 2 bytes of padding
503+ - If `(SIVLEN + component_len) % 3 = 2` : add 1 byte of padding
501504
502505The final output is encoded using URL-safe base64 {{!RFC4648}}, with '-' replacing
503506' +' and '_' replacing '/' for URI compatibility.
@@ -549,10 +552,10 @@ Steps:
5495523. `encrypted_output = empty byte array`
5505534. For each component :
551554 - Update `components_xof` with `component`.
552- - ` SIV = components_xof.clone().read(16 )` .
555+ - ` SIV = components_xof.clone().read(SIVLEN )` .
553556 - ` keystream_xof = base_keystream_xof.clone()` .
554557 - ` keystream_xof.update(SIV)` .
555- - ` padding_len = (3 - (16 + len(component)) % 3) % 3` .
558+ - ` padding_len = (3 - (SIVLEN + len(component)) % 3) % 3` .
556559 - ` keystream = keystream_xof.read(len(component) + padding_len)` .
557560 - ` padded_component = component concatenated with zeros(padding_len)` .
558561 - ` encrypted_part = padded_component XOR keystream` .
@@ -581,22 +584,22 @@ Steps:
5815844. `decrypted_components = empty list`
5825855. `position = 0`
5835866. While `position < len(decoded)` :
584- - ` SIV = decoded[position:position+16 ]` . If not enough bytes, return `error`.
587+ - ` SIV = decoded[position:position+SIVLEN ]` . If not enough bytes, return `error`.
585588 - ` keystream_xof = base_keystream_xof.clone().update(SIV)` .
586- - ` component_start = position + 16 `
589+ - ` component_start = position + SIVLEN `
587590 - ` component = empty byte array`
588- - ` position = position + 16 `
591+ - ` position = position + SIVLEN `
589592 - While `position < len(decoded)` :
590593 - ` decrypted_byte = decoded[position] XOR keystream_xof.read(1)`
591594 - ` position = position + 1`
592595 - If `decrypted_byte == 0x00` : continue (skip padding)
593596 - ` component.append(decrypted_byte)`
594597 - If `decrypted_byte` is '/', '?', or '#':
595598 - ` total_len = position - component_start`
596- - ` position = position + ((3 - ((16 + total_len) % 3)) % 3)`
599+ - ` position = position + ((3 - ((SIVLEN + total_len) % 3)) % 3)`
597600 - Break inner loop
598601 - Update `components_xof` with `component`.
599- - ` expected_SIV = components_xof.clone().read(16 )` .
602+ - ` expected_SIV = components_xof.clone().read(SIVLEN )` .
600603 - If `constant_time_compare(SIV, expected_SIV) == false`, return `error`.
601604 - ` decrypted_components.append(component)` .
602605
@@ -620,7 +623,7 @@ properties needed for this construction.
620623
621624# # Key and Context Handling
622625
623- The secret key MUST be at least 16 bytes long. Keys shorter than 16
626+ The secret key MUST be at least SIVLEN bytes long. Keys shorter than SIVLEN
624627bytes MUST be rejected. Implementations SHOULD validate that the key
625628does not consist of repeated patterns (e.g., identical first and
626629second halves) as a best practice.
@@ -718,7 +721,7 @@ Key Recovery: TurboSHAKE128's security properties ensure that observing cipherte
718721
719722The security of URICrypt is bounded by the following :
720723
721- - Key strength : Minimum 128-bit security with 16 -byte keys
724+ - Key strength : Minimum 128-bit security with SIVLEN -byte keys
722725- Collision resistance : 2<sup>64</sup> birthday bound for SIV collisions
723726- Authentication security : 2<sup>-128</sup> probability of successful forgery
724727- Computational security : Based on TurboSHAKE128's proven security as an XOF
@@ -866,8 +869,8 @@ function uricrypt_encrypt(secret_key, context, uri_string):
866869 // Update components XOF for SIV computation
867870 components_xof.update(component)
868871
869- // Generate 16 -byte Synthetic Initialization Vector (SIV)
870- siv = components_xof.squeeze(16 )
872+ // Generate SIVLEN -byte Synthetic Initialization Vector (SIV)
873+ siv = components_xof.squeeze(SIVLEN )
871874
872875 // Create keystream XOF for this component
873876 keystream_xof = base_keystream_xof.clone()
@@ -877,7 +880,7 @@ function uricrypt_encrypt(secret_key, context, uri_string):
877880 // The total bytes (SIV + component) must be a multiple of 3
878881 // to produce clean base64 output without padding characters
879882 component_len = len(component)
880- padding_len = (3 - (16 + component_len) % 3) % 3
883+ padding_len = (3 - (SIVLEN + component_len) % 3) % 3
881884
882885 // Generate keystream
883886 keystream = keystream_xof.squeeze(component_len + padding_len)
@@ -933,8 +936,8 @@ function uricrypt_decrypt(secret_key, context, encrypted_uri):
933936 // Process each component
934937 while not input_stream.empty() :
935938 // Read SIV
936- siv = input_stream.read(16 )
937- if len(siv) != 16 :
939+ siv = input_stream.read(SIVLEN )
940+ if len(siv) != SIVLEN :
938941 return error("Decryption failed")
939942
940943 // Create keystream XOF
@@ -949,7 +952,7 @@ function uricrypt_decrypt(secret_key, context, encrypted_uri):
949952 // Find valid component length by checking padding alignment
950953 component_data = None
951954 for possible_len in range(1, remaining + 1) :
952- total_len = 16 + possible_len
955+ total_len = SIVLEN + possible_len
953956 padding_len = (3 - total_len % 3) % 3
954957 if possible_len >= padding_len :
955958 component_data = input_stream.peek(possible_len)
@@ -966,14 +969,14 @@ function uricrypt_decrypt(secret_key, context, encrypted_uri):
966969 padded_plaintext = xor_bytes(encrypted_part, keystream)
967970
968971 // Remove padding bytes added for base64 alignment
969- padding_len = (3 - (16 + len(encrypted_part)) % 3) % 3
972+ padding_len = (3 - (SIVLEN + len(encrypted_part)) % 3) % 3
970973 component = padded_plaintext[:-padding_len] if padding_len > 0 else padded_plaintext
971974
972975 // Update XOF with plaintext
973976 components_xof.update(component)
974977
975978 // Generate expected SIV
976- expected_siv = components_xof.squeeze(16 )
979+ expected_siv = components_xof.squeeze(SIVLEN )
977980
978981 // Authenticate using constant-time comparison to prevent timing attacks
979982 if not constant_time_equal(siv, expected_siv) :
@@ -996,9 +999,9 @@ function uricrypt_decrypt(secret_key, context, encrypted_uri):
996999~~~
9971000function calculate_padding(component_len) :
9981001 // Calculate padding needed for base64 encoding alignment
999- // The combined SIV (16 bytes) + component must be divisible by 3
1002+ // The combined SIV (SIVLEN bytes) + component must be divisible by 3
10001003 // for clean base64 encoding without '=' padding characters
1001- total_len = 16 + component_len
1004+ total_len = SIVLEN + component_len
10021005 return (3 - total_len % 3) % 3
10031006
10041007function base64_urlsafe_no_pad_encode(data) :
0 commit comments