Skip to content

Commit 4669976

Browse files
committed
feat: improved xchacha padding and traffic obfsucation
1 parent d218743 commit 4669976

File tree

5 files changed

+45
-19
lines changed

5 files changed

+45
-19
lines changed

core/constants.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@
1919
# crypto parameters (bytes)
2020
CHALLENGE_LEN = 11264
2121

22-
XCHACHA20POLY1305_NONCE_LEN = 24
23-
2422
OTP_PAD_SIZE = 11264
2523
OTP_MAX_BUCKET = 64
2624
OTP_MAX_RANDOM_PAD = 16
2725
OTP_SIZE_LENGTH = 2
2826
OTP_MAX_MESSAGE_LEN = OTP_PAD_SIZE - OTP_SIZE_LENGTH
2927

28+
XCHACHA20POLY1305_NONCE_LEN = 24
29+
XCHACHA20POLY1305_SIZE_LEN = 3
30+
XCHACHA20POLY1305_MAX_RANODM_PAD = OTP_PAD_SIZE
31+
32+
33+
3034
SMP_NONCE_LENGTH = 64
3135
SMP_PROOF_LENGTH = 64
3236
SMP_ANSWER_OUTPUT_LEN = 64

core/requests.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
sha3_512
44
)
55
import urllib
6-
import uuid
76
import json
87
import logging
98
import string

core/trad_crypto.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
-------
44
Provides wrappers for cryptographic primitives:
55
- SHA3-512 hashing
6-
- Argon2id key derivation for AES-256 keys
7-
- AES-256-GCM encryption and decryption
6+
- Argon2id key derivation for XChaCha20Poly1305 keys
7+
- XChaCha20Poly1305 encryption and decryption
88
These functions rely on the cryptography library and are intended for use within Coldwire's higher-level protocol logic.
99
"""
1010

1111
from nacl import pwhash, bindings
1212
from core.constants import (
1313
OTP_PAD_SIZE,
1414
XCHACHA20POLY1305_NONCE_LEN,
15+
XCHACHA20POLY1305_SIZE_LEN,
16+
XCHACHA20POLY1305_MAX_RANODM_PAD,
1517
ARGON2_ITERS,
1618
ARGON2_MEMORY,
1719
ARGON2_LANES,
@@ -67,20 +69,22 @@ def derive_key_argon2id(password: bytes, salt: bytes = None, output_length: int
6769
), salt
6870

6971

70-
def encrypt_xchacha20poly1305(key: bytes, plaintext: bytes, nonce: bytes = None, counter: int = None, counter_safety: int = 255) -> tuple[bytes, bytes]:
72+
def encrypt_xchacha20poly1305(key: bytes, plaintext: bytes, nonce: bytes = None, counter: int = None, counter_safety: int = 255, max_padding: int = XCHACHA20POLY1305_MAX_RANODM_PAD) -> tuple[bytes, bytes]:
7173
"""
72-
Encrypt plaintext using ChaCha20Poly1305.
74+
Encrypt plaintext using XChaCha20Poly1305.
7375
74-
A random nonce is generated for each encryption.
76+
A random nonce is generated for each encryption unless you specify one.
7577
7678
Args:
77-
key: A 32-byte ChaCha20Poly1305 key.
79+
key: A 32-byte XChaCha20Poly1305 key.
7880
plaintext: Data to encrypt.
81+
nonce: An (optional) nonce to be used.
7982
counter: an (optional) number to add to nonce
80-
83+
counter_safety: an (optional) max counter number, to prevent counter overflow.
84+
max_padding: an (optional) maximum padding limit number to message. Cannot be larger than what `XCHACHA20POLY1305_MAX_RANODM_PAD` could store. Set to 0 for no padding.
8185
Returns:
8286
A tuple (nonce, ciphertext) where:
83-
- nonce: The randomly generated AES-GCM nonce.
87+
- nonce: The randomly generated nonce or the same given nonce.
8488
- ciphertext: The encrypted data including the authentication tag.
8589
"""
8690
if nonce is None:
@@ -92,26 +96,46 @@ def encrypt_xchacha20poly1305(key: bytes, plaintext: bytes, nonce: bytes = None,
9296

9397
nonce = nonce[:XCHACHA20POLY1305_NONCE_LEN - 1] + counter.to_bytes(1, "big")
9498

95-
ciphertext = bindings.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, None, nonce, key)
99+
if max_padding < 0:
100+
raise ValueError(f"Max_padding is less than 0! ({max_padding})")
101+
102+
if max_padding > 2 ** (XCHACHA20POLY1305_SIZE_LEN * 8) - 1:
103+
raise ValueError(f"Max_padding is more than ``XCHACHA20POLY1305_SIZE_LEN`! ({max_padding})")
104+
105+
padding = secrets.token_bytes(secrets.randbelow(max_padding + 1))
106+
padding_length_bytes = len(padding).to_bytes(XCHACHA20POLY1305_SIZE_LEN, "big")
107+
108+
padded_plaintext = padding_length_bytes + plaintext + padding
109+
110+
111+
ciphertext = bindings.crypto_aead_xchacha20poly1305_ietf_encrypt(padded_plaintext, None, nonce, key)
96112

97113
return nonce, ciphertext
98114

99115

100116
def decrypt_xchacha20poly1305(key: bytes, nonce: bytes, ciphertext: bytes) -> bytes:
101117
"""
102-
Decrypt ciphertext using ChaCha20Poly1305.
118+
Decrypt ciphertext using XChaCha20Poly1305.
103119
104120
Raises an exception if authentication fails.
105121
106122
Args:
107-
key: The 32-byte ChaCha20Poly1305 key used for encryption.
123+
key: The 32-byte XChaCha20Poly1305 key used for encryption.
108124
nonce: The nonce used during encryption.
109125
ciphertext: The encrypted data including the authentication tag.
110126
111127
Returns:
112-
The decrypted plaintext bytes.
128+
The decrypted plaintext bytes with no padding.
113129
"""
114130

115-
return bindings.crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, None, nonce, key)
131+
padded_plaintext = bindings.crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, None, nonce, key)
132+
133+
padding_length = int.from_bytes(padded_plaintext[:XCHACHA20POLY1305_SIZE_LEN], "big")
134+
135+
if padding_length < 0:
136+
raise ValueError(f"Negative padding length ({padding_length}), ciphertext likely corrupted, or key is invalid!")
137+
138+
return padded_plaintext[XCHACHA20POLY1305_SIZE_LEN : -padding_length]
139+
116140

117141

logic/background_worker.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@
1313
)
1414
from core.crypto import random_number_range
1515
from core.trad_crypto import (
16-
encrypt_xchacha20poly1305,
1716
decrypt_xchacha20poly1305
1817
)
1918
from base64 import b64decode
2019
import copy
2120
import logging
22-
import json
2321

2422
logger = logging.getLogger(__name__)
2523

logic/pfs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ def send_new_ephemeral_keys(user_data: dict, user_data_lock: threading.Lock, con
111111
_, ciphertext_blob = encrypt_xchacha20poly1305(
112112
our_strand_key,
113113
PFS_TYPE + our_new_strand_nonce + publickeys_hashchain_signature + publickeys_hashchain,
114-
nonce = our_next_strand_nonce
114+
nonce = our_next_strand_nonce,
115+
max_padding = 1024
115116
)
116117

117118
try:

0 commit comments

Comments
 (0)