You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
72 lines
4.5 KiB
72 lines
4.5 KiB
/* |
|
* dm_crypto.h — криптография прямого чата (DM) |
|
* |
|
* Детерминированная идентичность и E2E-шифрование: |
|
* - conv_id / content_key выводятся обеими сторонами из своих ключей и |
|
* pubkey пира (известен из общей группы) — без переговоров. |
|
* - шифрование AES-256-CCM с детерминированным nonce из (dir, seq). |
|
*/ |
|
|
|
#ifndef DM_CRYPTO_H |
|
#define DM_CRYPTO_H |
|
|
|
#include <stdint.h> |
|
#include <stddef.h> |
|
#include <stdio.h> |
|
|
|
#ifdef __cplusplus |
|
extern "C" { |
|
#endif |
|
|
|
#define DM_CONTENT_KEY_SIZE 32 |
|
#define DM_NONCE_SIZE 13 /* AES-CCM требует ровно 13 байт */ |
|
#define DM_TAG_SIZE 16 |
|
#define DM_CCM_PLAIN_MAX 65535u |
|
#define DM_MEDIA_PLAIN_SIZE 32768u |
|
|
|
/* conv_id = SHA256("utun_dm_v1" || min(a,b) || max(a,b))[..8] & 0x7FFF..., |
|
* 63-битное число, одинаковое на обеих сторонах (одна ветка на пару). */ |
|
uint64_t dm_derive_conv_id(uint64_t a, uint64_t b); |
|
|
|
/* content_key = SHA256(X25519(my_priv, peer_pub) || "utun_dm_content"). |
|
* Возвращает 0 при успехе, <0 при ошибке. */ |
|
int dm_derive_content_key(const uint8_t my_priv[32], const uint8_t peer_pub[32], |
|
uint8_t out[DM_CONTENT_KEY_SIZE]); |
|
|
|
/* Детерминированный nonce, уникальный на каждое сообщение: |
|
* SHA256(conv_id || author || seq)[..13]. Одинаков на обеих сторонах, |
|
* не пересекается между направлениями (author различается). */ |
|
void dm_build_nonce(uint64_t conv_id, uint64_t author, uint64_t seq, uint8_t nonce[DM_NONCE_SIZE]); |
|
|
|
/* Nonce порции медиа; media_id неизменен на всех повторах одной отправки. */ |
|
void dm_build_media_nonce(uint64_t author, const uint8_t media_id[16], uint64_t part, uint8_t nonce[DM_NONCE_SIZE]); |
|
|
|
/* AES-256-CCM. ciphertext = ciphertext(plain_len) || tag(DM_TAG_SIZE). |
|
* out должен вмещать plain_len + DM_TAG_SIZE. 0=ок, <0=ошибка. */ |
|
int dm_encrypt(const uint8_t key[DM_CONTENT_KEY_SIZE], const uint8_t nonce[DM_NONCE_SIZE], |
|
const uint8_t* plain, size_t plain_len, uint8_t* out, size_t* out_len); |
|
|
|
/* Обратная операция. ct_len = plain_len + DM_TAG_SIZE. 0=ок, <0=ошибка. */ |
|
int dm_decrypt(const uint8_t key[DM_CONTENT_KEY_SIZE], const uint8_t nonce[DM_NONCE_SIZE], |
|
const uint8_t* ct, size_t ct_len, uint8_t* out, size_t* out_len); |
|
|
|
/* Подготовка неизменяемого ciphertext медиа в worker, без БД и сетевых вызовов. |
|
* Используется content_key target, отдельного ключа файла нет. |
|
* Формат: порции по DM_MEDIA_PLAIN_SIZE байт + tag каждой порции; последняя короче. |
|
* Пустой файл содержит один tag. Размер plaintext и SHA256 ciphertext входят в подписанные метаданные. |
|
* src/dst открывает и закрывает владелец; dst — временный файл, при ошибке его нужно отбросить. |
|
* Новый случайный media_id нужен для каждой подготовки, в том числе повторной после ошибки; |
|
* при повторе доставки передаётся уже подготовленный файл, шифрование не повторяется. |
|
* Возвращает 0 после fflush(dst), <0 при ошибке; длительную работу запускать через media_async. */ |
|
int dm_media_encrypt_stream(const uint8_t key[DM_CONTENT_KEY_SIZE], uint64_t author, const uint8_t media_id[16], |
|
FILE* src, FILE* dst, uint64_t* plain_size, uint8_t ciphertext_hash[32]); |
|
|
|
/* Расшифровать в временный dst, проверив все tags, точный размер и хеш ciphertext. |
|
* Публиковать файл разрешено только после успеха и durable сохранения владельцем. */ |
|
int dm_media_decrypt_stream(const uint8_t key[DM_CONTENT_KEY_SIZE], uint64_t author, const uint8_t media_id[16], |
|
uint64_t plain_size, const uint8_t ciphertext_hash[32], FILE* src, FILE* dst); |
|
|
|
#ifdef __cplusplus |
|
} |
|
#endif |
|
#endif /* DM_CRYPTO_H */
|
|
|