Skip to content
This repository was archived by the owner on Mar 10, 2026. It is now read-only.

Commit 3bc0399

Browse files
committed
refactor: Improve plausible deniability and add SMP encryption
1 parent 5e0ca8a commit 3bc0399

File tree

7 files changed

+279
-196
lines changed

7 files changed

+279
-196
lines changed

core/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# crypto parameters (bytes)
1313
CHALLENGE_LEN = 11264
1414

15-
AES_GCM_NONCE_LEN = 12
15+
CHACHA20POLY1305_NONCE_LEN = 12
1616

1717
OTP_PAD_SIZE = 11264
1818
OTP_PADDING_LENGTH = 2

core/crypto.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ def generate_kem_keys(algorithm: str):
148148
private_key = kem.export_secret_key()
149149
return private_key, public_key
150150

151+
def encap_shared_secret(public_key: bytes, algorithm: str):
152+
with oqs.KeyEncapsulation(algorithm) as kem:
153+
return kem.encap_secret(public_key[:ALGOS_BUFFER_LIMITS[algorithm]["PK_LEN"]])
154+
155+
def decap_shared_secret(ciphertext: bytes, private_key: bytes, algorithm: str):
156+
with oqs.KeyEncapsulation(algorithm, secret_key = private_key[:ALGOS_BUFFER_LIMITS[algorithm]["SK_LEN"]]) as kem:
157+
return kem.decap_secret(ciphertext[:ALGOS_BUFFER_LIMITS[algorithm]["CT_LEN"]])
158+
151159
def decrypt_shared_secrets(ciphertext_blob: bytes, private_key: bytes, algorithm: str = None, otp_pad_size: int = OTP_PAD_SIZE):
152160
"""
153161
Decrypts concatenated KEM ciphertexts to derive shared one-time pad.

core/trad_crypto.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
These functions rely on the cryptography library and are intended for use within Coldwire's higher-level protocol logic.
99
"""
1010

11-
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
11+
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
1212
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
13+
from cryptography.hazmat.primitives import hashes
14+
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
1315
from core.constants import (
1416
OTP_PAD_SIZE,
15-
AES_GCM_NONCE_LEN,
17+
CHACHA20POLY1305_NONCE_LEN,
1618
ARGON2_ITERS,
1719
ARGON2_MEMORY,
1820
ARGON2_LANES,
@@ -39,6 +41,14 @@ def sha3_512(data: bytes) -> bytes:
3941
return h.digest()
4042

4143

44+
def hkdf(key: bytes, length: int = 32, salt: bytes = None, info: bytes = None) -> bytes:
45+
return HKDF(
46+
algorithm = hashes.SHA3_256(),
47+
length = length,
48+
salt = salt,
49+
info = info,
50+
).derive(key)
51+
4252
def derive_key_argon2id(password: bytes, salt: bytes = None, salt_length: int = ARGON2_SALT_LEN, output_length: int = ARGON2_OUTPUT_LEN) -> tuple[bytes, bytes]:
4353
"""
4454
Derive a symmetric key from a password using Argon2id.
@@ -70,42 +80,49 @@ def derive_key_argon2id(password: bytes, salt: bytes = None, salt_length: int =
7080
return derived_key, salt
7181

7282

73-
def encrypt_aes_gcm(key: bytes, plaintext: bytes) -> tuple[bytes, bytes]:
83+
def encrypt_chacha20poly1305(key: bytes, plaintext: bytes, counter: int = None, counter_safety: int = 2 ** 32) -> tuple[bytes, bytes]:
7484
"""
75-
Encrypt plaintext using AES-256 in GCM mode.
85+
Encrypt plaintext using ChaCha20Poly1305.
7686
7787
A random nonce is generated for each encryption.
7888
7989
Args:
80-
key: A 32-byte AES key.
90+
key: A 32-byte ChaCha20Poly1305 key.
8191
plaintext: Data to encrypt.
92+
counter: an (optional) number to add to nonce
8293
8394
Returns:
8495
A tuple (nonce, ciphertext) where:
8596
- nonce: The randomly generated AES-GCM nonce.
8697
- ciphertext: The encrypted data including the authentication tag.
8798
"""
88-
nonce = secrets.token_bytes(AES_GCM_NONCE_LEN)
89-
aes_gcm = AESGCM(key)
90-
ciphertext = aes_gcm.encrypt(nonce, plaintext, None)
99+
nonce = secrets.token_bytes(CHACHA20POLY1305_NONCE_LEN)
100+
if counter is not None:
101+
if counter > counter_safety:
102+
raise ValueError("ChaCha counter has overflowen")
103+
104+
nonce = nonce[:CHACHA20POLY1305_NONCE_LEN - 4] + counter.to_bytes(4, "big")
105+
106+
chacha = ChaCha20Poly1305(key)
107+
ciphertext = chacha.encrypt(nonce, plaintext, None)
91108
return nonce, ciphertext
92109

93110

94-
def decrypt_aes_gcm(key: bytes, nonce: bytes, ciphertext: bytes) -> bytes:
111+
def decrypt_chacha20poly1305(key: bytes, nonce: bytes, ciphertext: bytes) -> bytes:
95112
"""
96-
Decrypt ciphertext using AES-256 in GCM mode.
113+
Decrypt ciphertext using ChaCha20Poly1305.
97114
98115
Raises an exception if authentication fails.
99116
100117
Args:
101-
key: The 32-byte AES key used for encryption.
118+
key: The 32-byte ChaCha20Poly1305 key used for encryption.
102119
nonce: The nonce used during encryption.
103120
ciphertext: The encrypted data including the authentication tag.
104121
105122
Returns:
106123
The decrypted plaintext bytes.
107124
"""
108-
aes_gcm = AESGCM(key)
109-
return aes_gcm.decrypt(nonce, ciphertext, None)
125+
chacha = ChaCha20Poly1305(key)
126+
return chacha.decrypt(nonce, ciphertext, None)
110127

111128

logic/contacts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def save_contact(user_data: dict, user_data_lock, contact_id: str) -> None:
6060
"contact_nonce": None,
6161
"smp_step": None,
6262
"tmp_proof": None,
63+
"tmp_key": None,
6364
"contact_kem_public_key": None,
6465
"our_kem_keys": {
6566
"private_key": None,

0 commit comments

Comments
 (0)