Skip to content

Commit 6f3f07c

Browse files
committed
Do not hardcode the SIV length
1 parent b26b769 commit 6f3f07c

File tree

1 file changed

+32
-29
lines changed

1 file changed

+32
-29
lines changed

draft-denis-uricrypt.md

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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
431434
that ensures both confidentiality and authenticity:
432435

433436
1. 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.
435438
3. Create `keystream_xof` by cloning `base_keystream_xof` and updating it with SIV
436439
4. Calculate padding needed for base64 encoding
437440
5. 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
447450
padding_len = (3 - total_bytes % 3) % 3
448451
~~~
449452

@@ -460,7 +463,7 @@ all previous components, thus enabling the prefix-preserving property.
460463

461464
For each encrypted component, the decryption process is:
462465

463-
1. Read SIV from input (16 bytes)
466+
1. Read SIV from input (SIVLEN bytes)
464467
2. Create `keystream_xof` by cloning `base_keystream_xof` and updating it with SIV
465468
3. 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
493496
This is necessary because base64 encoding processes 3 bytes at a time to produce
494497
4 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

502505
The final output is encoded using URL-safe base64 {{!RFC4648}}, with '-' replacing
503506
'+' and '_' replacing '/' for URI compatibility.
@@ -549,10 +552,10 @@ Steps:
549552
3. `encrypted_output = empty byte array`
550553
4. 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:
581584
4. `decrypted_components = empty list`
582585
5. `position = 0`
583586
6. 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
624627
bytes MUST be rejected. Implementations SHOULD validate that the key
625628
does not consist of repeated patterns (e.g., identical first and
626629
second halves) as a best practice.
@@ -718,7 +721,7 @@ Key Recovery: TurboSHAKE128's security properties ensure that observing cipherte
718721

719722
The 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
~~~
9971000
function 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

10041007
function base64_urlsafe_no_pad_encode(data):

0 commit comments

Comments
 (0)