Chepuhagram/srv/app/services/crypto_service.py

51 lines
2.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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')