Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Histórico de Alterações do libzupt

## Não lançado

### Segurança
- Chaves não são mais gravadas em arquivos temporários previsíveis em `/tmp`
durante a criptografia/descriptografia em memória. As funções de
criptografia/descriptografia agora derivam as chaves diretamente do buffer
em memória (`zupt_hybrid_encrypt_init_mem` / `zupt_hybrid_decrypt_init_mem`),
eliminando um vetor de ataque por symlink e o vazamento da chave privada em
disco (que anulava a proteção `mlock`).
- Arquivos de chave privada gerados agora são criados com permissões `0600`
(somente o dono), tanto na API C (`zupt_hybrid_keygen`) quanto na API C++
(`KeyGenerator::saveKeyPair`), em vez de herdar o modo padrão do `umask`.

### Correções
- Corrigida extensão de sinal/comportamento indefinido na leitura do tamanho
do bloco durante a descriptografia: cada byte é convertido para `size_t`
antes do deslocamento, evitando um `block_len` inválido (enorme).
- Removidas as funções mortas e incorretas `zupt_hybrid_derive_keys` (que
ignorava o segredo X25519, produzindo chaves que o lado de descriptografia
nunca reproduziria) e `zupt_hybrid_decrypt_derive_keys`, ambas sem uso.

## v1.5.0 (2026-03-29)

### Novidades
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The **libzupt** is an SDK designed to simplify the implementation of encryption

The main goal of libzupt is to enable current applications to be protected against emerging threats from quantum computing, even when running on classical computers. By combining traditional cryptographic techniques with quantum-resistant mechanisms, the library provides an additional layer of security that anticipates future scenarios where classical algorithms may be broken. This allows developers to build more resilient systems, ensuring long-term data confidentiality and integrity.

The name **zupt** is atribute to the original project created by [**Cristian Cezar Moisés**](https://github.com/cristiancmoises), acknowledging his fundamental contribution to the conceptual and technical foundation of this solution. This inspiration reinforces the library’s commitment to innovation, security, and the evolution of ideas that drive modern applied cryptography forward.
The name **zupt** is a tribute to the original project created by [**Cristian Cezar Moisés**](https://github.com/cristiancmoises), acknowledging his fundamental contribution to the conceptual and technical foundation of this solution. This inspiration reinforces the library’s commitment to innovation, security, and the evolution of ideas that drive modern applied cryptography forward.

<div align="center">
<img width="480" height="487" alt="image" src="https://github.com/user-attachments/assets/df6a4a01-ca23-4b34-a5bf-d38a4c31d48c" /><br>
Expand Down
8 changes: 8 additions & 0 deletions include/zupt.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,14 @@ int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
uint8_t *enc_hdr, size_t *enc_hdr_len);
int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
const uint8_t *enc_hdr, size_t enc_hdr_len);
/* In-memory variants: take the key material directly from a buffer instead of
* a file path, so callers never have to stage (private) keys on disk. */
int zupt_hybrid_encrypt_init_mem(zupt_keyring_t *kr,
const uint8_t *pubkey, size_t pubkey_len,
uint8_t *enc_hdr, size_t *enc_hdr_len);
int zupt_hybrid_decrypt_init_mem(zupt_keyring_t *kr,
const uint8_t *privkey, size_t privkey_len,
const uint8_t *enc_hdr, size_t enc_hdr_len);

const char *zupt_strerror(zupt_error_t e);
const char *zupt_codec_name(uint16_t id);
Expand Down
20 changes: 0 additions & 20 deletions include/zupt_cxx.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,6 @@
extern "C" {
#endif

/* HYBRID KEY DERIVATION C API */

/* Maximum key derivation input size */
#define ZUPT_KDF_INPUT_MAX (32 + 1088 + 32 + 15)

/* Derive archive keys from hybrid shared secret and transcript
* Returns 64 bytes: enc_key[32] + mac_key[32]
* Returns 0 on success, -1 on error
*/
ZUPT_API int zupt_hybrid_derive_keys(const uint8_t* ml_ss, const uint8_t* ml_ct,
const uint8_t* eph_pk, const uint8_t* ml_pk,
uint8_t* archive_key);

/* Derive archive keys from private key and encryption header
* Returns 0 on success, -1 on error
*/
ZUPT_API int zupt_hybrid_decrypt_derive_keys(const uint8_t* priv_key, size_t priv_key_len,
const uint8_t* enc_header, size_t enc_header_len,
uint8_t* archive_key);

/* KEY GENERATION C API */

/* Generate a hybrid key pair
Expand Down
99 changes: 86 additions & 13 deletions src/zupt_crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
#include <sys/syscall.h>
#include <unistd.h>
#endif
#if !defined(_WIN32)
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

/* ═══════════════════════════════════════════════════════════════════
* RANDOM BYTES (OS-native CSPRNG — NO FALLBACK)
Expand Down Expand Up @@ -452,9 +457,19 @@ int zupt_hybrid_keygen(const char *keyfile) {
zupt_random_bytes(x_sk, 32);
zupt_x25519_base(x_pk, x_sk);

/* Write private key file */
/* Write private key file. The file holds the X25519 and ML-KEM secret
* keys in the clear, so it must never be world/group readable. Create it
* with 0600 atomically (via open) rather than fopen + chmod, which would
* briefly expose the file with the umask-default mode. */
#if !defined(_WIN32)
int fd = open(keyfile, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0) return -1;
FILE *f = fdopen(fd, "wb");
if (!f) { close(fd); return -1; }
#else
FILE *f = fopen(keyfile, "wb");
if (!f) return -1;
#endif

size_t total = ZKEY_PRIV_SIZE;
uint8_t *buf = (uint8_t *)calloc(total + 8, 1); /* +8 for checksum */
Expand Down Expand Up @@ -552,6 +567,29 @@ static int read_privkey(const char *path, uint8_t ml_pk[1184], uint8_t x_pk[32],
return 0;
}

/* Parse a public key directly from an in-memory buffer (no temp files). */
static int parse_pubkey_buf(const uint8_t *buf, size_t len,
uint8_t ml_pk[1184], uint8_t x_pk[32]) {
if (!buf || len < (size_t)(8 + 1184 + 32)) return -1;
if (memcmp(buf, ZKEY_MAGIC, 4) != 0) return -1;
memcpy(ml_pk, buf + 8, 1184);
memcpy(x_pk, buf + 8 + 1184, 32);
return 0;
}

/* Parse a private key directly from an in-memory buffer (no temp files). */
static int parse_privkey_buf(const uint8_t *buf, size_t len,
uint8_t ml_pk[1184], uint8_t x_pk[32],
uint8_t ml_sk[2400], uint8_t x_sk[32]) {
if (!buf || len < (size_t)(8 + 1184 + 32 + 2400 + 32)) return -1;
if (memcmp(buf, ZKEY_MAGIC, 4) != 0 || !(buf[5] & ZKEY_FLAG_PRIVATE)) return -1;
memcpy(ml_pk, buf + 8, 1184);
memcpy(x_pk, buf + 8 + 1184, 32);
memcpy(ml_sk, buf + 8 + 1184 + 32, 2400);
memcpy(x_sk, buf + 8 + 1184 + 32 + 2400, 32);
return 0;
}

/*
* HYBRID ENCRYPT INIT: Encapsulate with ML-KEM + X25519, derive archive keys.
*
Expand All @@ -576,11 +614,11 @@ static int read_privkey(const char *path, uint8_t ml_pk[1184], uint8_t x_pk[32],
@ ensures \result == 0 ==> kr->active == 1;
@ ensures \result == 0 ==> *enc_hdr_len == 1137;
*/
int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
uint8_t *enc_hdr, size_t *enc_hdr_len) {
uint8_t ml_pk[1184], x_pk[32];
if (read_pubkey(pubkeyfile, ml_pk, x_pk) != 0) return -1;

/* Shared encapsulation + KDF core, used by both the file- and memory-based
* encrypt-init entry points. Takes the already-parsed recipient public keys. */
static int hybrid_encrypt_core(zupt_keyring_t *kr,
const uint8_t ml_pk[1184], const uint8_t x_pk[32],
uint8_t *enc_hdr, size_t *enc_hdr_len) {
/* ML-KEM-768 encapsulation */
uint8_t ml_ct[1088], ml_ss[32];
if (zupt_mlkem768_encaps(ml_ct, ml_ss, ml_pk) != 0) return -1;
Expand Down Expand Up @@ -637,6 +675,21 @@ int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
return 0;
}

int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
uint8_t *enc_hdr, size_t *enc_hdr_len) {
uint8_t ml_pk[1184], x_pk[32];
if (read_pubkey(pubkeyfile, ml_pk, x_pk) != 0) return -1;
return hybrid_encrypt_core(kr, ml_pk, x_pk, enc_hdr, enc_hdr_len);
}

int zupt_hybrid_encrypt_init_mem(zupt_keyring_t *kr,
const uint8_t *pubkey, size_t pubkey_len,
uint8_t *enc_hdr, size_t *enc_hdr_len) {
uint8_t ml_pk[1184], x_pk[32];
if (parse_pubkey_buf(pubkey, pubkey_len, ml_pk, x_pk) != 0) return -1;
return hybrid_encrypt_core(kr, ml_pk, x_pk, enc_hdr, enc_hdr_len);
}

/*
* HYBRID DECRYPT INIT: Decapsulate with ML-KEM + X25519, derive archive keys.
*/
Expand All @@ -649,18 +702,19 @@ int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
@ kr->iterations, kr->active;
@ ensures \result == 0 ==> kr->active == 1;
*/
int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
const uint8_t *enc_hdr, size_t enc_hdr_len) {
/* Shared decapsulation + KDF core, used by both the file- and memory-based
* decrypt-init entry points. Takes the already-parsed recipient secret keys;
* the caller owns and wipes ml_sk/x_sk. */
static int hybrid_decrypt_core(zupt_keyring_t *kr,
const uint8_t ml_sk[2400], const uint8_t x_sk[32],
const uint8_t *enc_hdr, size_t enc_hdr_len) {
if (enc_hdr_len < 1 + 1088 + 32 + 16) return -1; /* enc_type + ct + eph_pk + nonce */
if (enc_hdr[0] != ZUPT_ENC_PQ_HYBRID) return -1;

const uint8_t *ml_ct = enc_hdr + 1;
const uint8_t *eph_pk = enc_hdr + 1 + 1088;
const uint8_t *nonce = enc_hdr + 1 + 1088 + 32;

uint8_t ml_pk[1184], x_pk[32], ml_sk[2400], x_sk[32];
if (read_privkey(privkeyfile, ml_pk, x_pk, ml_sk, x_sk) != 0) return -1;

/* ML-KEM-768 decapsulation */
uint8_t ml_ss[32];
zupt_mlkem768_decaps(ml_ss, ml_ct, ml_sk);
Expand Down Expand Up @@ -694,8 +748,6 @@ int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
zupt_mlock_keys(kr->enc_key, ZUPT_AES_KEY_SIZE);
zupt_mlock_keys(kr->mac_key, ZUPT_HMAC_SIZE);

zupt_secure_wipe(ml_sk, sizeof(ml_sk));
zupt_secure_wipe(x_sk, 32);
zupt_secure_wipe(ml_ss, 32);
zupt_secure_wipe(x_ss, 32);
zupt_secure_wipe(hybrid_ikm, 32);
Expand All @@ -704,3 +756,24 @@ int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,

return 0;
}

int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
const uint8_t *enc_hdr, size_t enc_hdr_len) {
uint8_t ml_pk[1184], x_pk[32], ml_sk[2400], x_sk[32];
if (read_privkey(privkeyfile, ml_pk, x_pk, ml_sk, x_sk) != 0) return -1;
int r = hybrid_decrypt_core(kr, ml_sk, x_sk, enc_hdr, enc_hdr_len);
zupt_secure_wipe(ml_sk, sizeof(ml_sk));
zupt_secure_wipe(x_sk, 32);
return r;
}

int zupt_hybrid_decrypt_init_mem(zupt_keyring_t *kr,
const uint8_t *privkey, size_t privkey_len,
const uint8_t *enc_hdr, size_t enc_hdr_len) {
uint8_t ml_pk[1184], x_pk[32], ml_sk[2400], x_sk[32];
if (parse_privkey_buf(privkey, privkey_len, ml_pk, x_pk, ml_sk, x_sk) != 0) return -1;
int r = hybrid_decrypt_core(kr, ml_sk, x_sk, enc_hdr, enc_hdr_len);
zupt_secure_wipe(ml_sk, sizeof(ml_sk));
zupt_secure_wipe(x_sk, 32);
return r;
}
20 changes: 19 additions & 1 deletion src/zupt_crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
#include <fstream>
#include <stdexcept>

#if !defined(_WIN32)
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

// Include C implementation headers
extern "C" {
#include "zupt.h"
Expand Down Expand Up @@ -183,7 +189,19 @@ void KeyGenerator::exportPublicKey(const std::string& privfile, const std::strin
}

void KeyGenerator::saveKeyPair(const KeyPair& kp, const std::string& filename) {
std::ofstream file(filename, std::ios::binary);
/* The key pair file contains the private key in the clear, so restrict it
* to owner-only (0600) before writing any secret bytes. ofstream offers no
* way to set the mode, so create/tighten the file first via open()+fchmod. */
#if !defined(_WIN32)
int fd = ::open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0) {
throw ZuptError(ErrorCode::ERR_IO, "Cannot create key file: " + filename);
}
::fchmod(fd, 0600); /* enforce 0600 even if the file already existed */
::close(fd);
#endif

std::ofstream file(filename, std::ios::binary | std::ios::trunc);
if (!file) {
throw ZuptError(ErrorCode::ERR_IO, "Cannot create key file: " + filename);
}
Expand Down
Loading
Loading