51 lines
2.6 KiB
Python
51 lines
2.6 KiB
Python
import base64
|
||
import os
|
||
from cryptography.hazmat.primitives.asymmetric import x25519
|
||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||
from cryptography.hazmat.primitives import serialization
|
||
from app.core.config import config
|
||
|
||
def encrypt_system_message(text: str, user_public_key_b64: str) -> str:
|
||
"""
|
||
Шифрует системное сообщение от id: 0 для конкретного пользователя.
|
||
Универсальное решение: автоматически преобразует любые форматы ключей в Raw 32-bytes.
|
||
"""
|
||
# 1. Загружаем приватный ключ сервера (id: 0)
|
||
server_priv_bytes = base64.b64decode(config.SYSTEM_CHAT_PRIVATE_KEY)
|
||
server_private_key = x25519.X25519PrivateKey.from_private_bytes(server_priv_bytes)
|
||
|
||
# 2. Безопасно загружаем публичный ключ получателя
|
||
user_pub_bytes = base64.b64decode(user_public_key_b64)
|
||
|
||
# ФИКС: Если длина ключа 42 байта (формат SubjectPublicKeyInfo / DER от OpenSSL),
|
||
# мы извлекаем из него чистые 32 байта, которые понимает Flutter.
|
||
if len(user_pub_bytes) == 42:
|
||
user_public_key = serialization.load_der_public_key(user_pub_bytes)
|
||
elif len(user_pub_bytes) == 32:
|
||
user_public_key = x25519.X25519PublicKey.from_public_bytes(user_pub_bytes)
|
||
else:
|
||
raise ValueError(f"Неподдерживаемая длина публичного ключа: {len(user_pub_bytes)} байт")
|
||
|
||
# 3. Вычисляем чистый, сырой Shared Secret (32 байта)
|
||
# В Dart методе `sharedSecretKey` для X25519 это и есть финальный AES-ключ
|
||
shared_secret = server_private_key.exchange(user_public_key)
|
||
|
||
# 4. Инициализируем AESGCM с чистым общим секретом
|
||
aesgcm = AESGCM(shared_secret)
|
||
|
||
# 5. Генерируем случайный Nonce (12 байт)
|
||
nonce = os.urandom(12)
|
||
|
||
# 6. Шифруем plain text
|
||
plaintext_bytes = text.encode('utf-8')
|
||
encrypted_data = aesgcm.encrypt(nonce, plaintext_bytes, None)
|
||
|
||
# Разделяем результат на ciphertext и 16-байтовый MAC
|
||
ciphertext = encrypted_data[:-16]
|
||
mac = encrypted_data[-16:]
|
||
|
||
# 7. Склеиваем под формат Dart: nonce (12) + mac (16) + ciphertext
|
||
final_payload = nonce + mac + ciphertext
|
||
|
||
return base64.b64encode(final_payload).decode('utf-8')
|