Skip to content

Commit 6ca9be6

Browse files
authored
Merge pull request #7 from Freedom-Club-Sec/feat/encrypt-smp
Feat/encrypt smp
2 parents 5e0ca8a + 206a626 commit 6ca9be6

File tree

10 files changed

+285
-226
lines changed

10 files changed

+285
-226
lines changed

core/constants.py

Lines changed: 2 additions & 2 deletions
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+
XCHACHA20POLY1305_NONCE_LEN = 24
1616

1717
OTP_PAD_SIZE = 11264
1818
OTP_PADDING_LENGTH = 2
@@ -71,5 +71,5 @@
7171
ARGON2_MEMORY = 256 * 1024 # MB
7272
ARGON2_ITERS = 3
7373
ARGON2_OUTPUT_LEN = 32 # bytes
74-
ARGON2_SALT_LEN = 32 # bytes
74+
ARGON2_SALT_LEN = 16 # bytes (Must be always 16 for interoperability with libsodium.)
7575
ARGON2_LANES = 4

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: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
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
12-
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
11+
from nacl import pwhash, bindings
1312
from core.constants import (
1413
OTP_PAD_SIZE,
15-
AES_GCM_NONCE_LEN,
14+
XCHACHA20POLY1305_NONCE_LEN,
1615
ARGON2_ITERS,
1716
ARGON2_MEMORY,
1817
ARGON2_LANES,
@@ -39,7 +38,7 @@ def sha3_512(data: bytes) -> bytes:
3938
return h.digest()
4039

4140

42-
def derive_key_argon2id(password: bytes, salt: bytes = None, salt_length: int = ARGON2_SALT_LEN, output_length: int = ARGON2_OUTPUT_LEN) -> tuple[bytes, bytes]:
41+
def derive_key_argon2id(password: bytes, salt: bytes = None, output_length: int = ARGON2_OUTPUT_LEN) -> tuple[bytes, bytes]:
4342
"""
4443
Derive a symmetric key from a password using Argon2id.
4544
@@ -57,55 +56,60 @@ def derive_key_argon2id(password: bytes, salt: bytes = None, salt_length: int =
5756
- salt: The salt used for derivation.
5857
"""
5958
if salt is None:
60-
salt = secrets.token_bytes(salt_length)
59+
salt = secrets.token_bytes(ARGON2_SALT_LEN)
6160

62-
kdf = Argon2id(
63-
salt=salt,
64-
iterations=ARGON2_ITERS,
65-
memory_cost=ARGON2_MEMORY,
66-
length=output_length,
67-
lanes=ARGON2_LANES
68-
)
69-
derived_key = kdf.derive(password)
70-
return derived_key, salt
61+
return pwhash.argon2id.kdf(
62+
output_length,
63+
password,
64+
salt,
65+
opslimit = ARGON2_ITERS,
66+
memlimit = ARGON2_MEMORY
67+
), salt
7168

7269

73-
def encrypt_aes_gcm(key: bytes, plaintext: bytes) -> tuple[bytes, bytes]:
70+
def encrypt_xchacha20poly1305(key: bytes, plaintext: bytes, counter: int = None, counter_safety: int = 2 ** 32) -> tuple[bytes, bytes]:
7471
"""
75-
Encrypt plaintext using AES-256 in GCM mode.
72+
Encrypt plaintext using ChaCha20Poly1305.
7673
7774
A random nonce is generated for each encryption.
7875
7976
Args:
80-
key: A 32-byte AES key.
77+
key: A 32-byte ChaCha20Poly1305 key.
8178
plaintext: Data to encrypt.
79+
counter: an (optional) number to add to nonce
8280
8381
Returns:
8482
A tuple (nonce, ciphertext) where:
8583
- nonce: The randomly generated AES-GCM nonce.
8684
- ciphertext: The encrypted data including the authentication tag.
8785
"""
88-
nonce = secrets.token_bytes(AES_GCM_NONCE_LEN)
89-
aes_gcm = AESGCM(key)
90-
ciphertext = aes_gcm.encrypt(nonce, plaintext, None)
86+
nonce = secrets.token_bytes(XCHACHA20POLY1305_NONCE_LEN)
87+
if counter is not None:
88+
if counter > counter_safety:
89+
raise ValueError("ChaCha counter has overflowen")
90+
91+
nonce = nonce[:XCHACHA20POLY1305_NONCE_LEN - 4] + counter.to_bytes(4, "big")
92+
93+
ciphertext = bindings.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, None, nonce, key)
94+
9195
return nonce, ciphertext
9296

9397

94-
def decrypt_aes_gcm(key: bytes, nonce: bytes, ciphertext: bytes) -> bytes:
98+
def decrypt_xchacha20poly1305(key: bytes, nonce: bytes, ciphertext: bytes) -> bytes:
9599
"""
96-
Decrypt ciphertext using AES-256 in GCM mode.
100+
Decrypt ciphertext using ChaCha20Poly1305.
97101
98102
Raises an exception if authentication fails.
99103
100104
Args:
101-
key: The 32-byte AES key used for encryption.
105+
key: The 32-byte ChaCha20Poly1305 key used for encryption.
102106
nonce: The nonce used during encryption.
103107
ciphertext: The encrypted data including the authentication tag.
104108
105109
Returns:
106110
The decrypted plaintext bytes.
107111
"""
108-
aes_gcm = AESGCM(key)
109-
return aes_gcm.decrypt(nonce, ciphertext, None)
112+
113+
return bindings.crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, None, nonce, key)
110114

111115

logic/background_worker.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ def background_worker(user_data, user_data_lock, ui_queue, stop_flag):
3232
# logger.debug("Data received: %s", json.dumps(response, indent = 2)[:2000])
3333

3434
for message in response["messages"]:
35-
try:
36-
logger.debug("Received data message: %s", json.dumps(message, indent = 2)[:5000])
37-
except:
38-
print("################# ", message)
35+
logger.debug("Received data message: %s", json.dumps(message, indent = 2)[:5000])
3936

4037
# Sanity check universal message fields
4138
if (not "sender" in message) or (not message["sender"].isdigit()) or (len(message["sender"]) != 16):

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)