Skip to content

Commit 6c1d5d6

Browse files
authored
Merge pull request #42 from mpretty-cyro/feature/xchacha20-functions
Added general xchacha20 encrypt/decrypt functions, doc tweaks
2 parents 199d68f + 80d894b commit 6c1d5d6

File tree

6 files changed

+203
-3
lines changed

6 files changed

+203
-3
lines changed

include/session/config/groups/info.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class Info : public ConfigBase {
124124
///
125125
/// Returns the group description, or std::nullopt if there is no group description set.
126126
///
127-
/// If given a description longer than `Info::DESCRIPTION_MAX_LENGTH` (2000) bytes it will be
127+
/// If given a description longer than `Info::DESCRIPTION_MAX_LENGTH` bytes it will be
128128
/// truncated.
129129
///
130130
/// Inputs: None

include/session/random.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ extern "C" {
1616
/// - `size` -- [in] number of bytes to be generated.
1717
///
1818
/// Outputs:
19-
/// - `unsigned char*` -- pointer to random bytes of `size` bytes.
19+
/// - `unsigned char*` -- pointer to random bytes of `size` bytes. The caller is responsible for
20+
/// freeing the data when done!
2021
LIBSESSION_EXPORT unsigned char* session_random(size_t size);
2122

2223
#ifdef __cplusplus
2324
}
24-
#endif
25+
#endif

include/session/session_encrypt.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,54 @@ LIBSESSION_EXPORT bool session_decrypt_push_notification(
229229
LIBSESSION_EXPORT bool session_compute_message_hash(
230230
const char* pubkey_hex_in, int16_t ns, const char* base64_data_in, char* hash_out);
231231

232+
/// API: crypto/session_encrypt_xchacha20
233+
///
234+
/// Encrypts a value with a given key using xchacha20.
235+
///
236+
/// Inputs:
237+
/// - `plaintext_in` -- [in] the data to encrypt.
238+
/// - `plaintext_len` -- [in] the length of `plaintext_in`.
239+
/// - `enc_key_in` -- [in] the key to use for encryption (32 bytes).
240+
/// - `ciphertext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the
241+
/// encrypted data written to it, and then the pointer to that buffer is stored here.
242+
/// This buffer must be `free()`d by the caller when done with it *unless* the function returns
243+
/// false, in which case the buffer pointer will not be set.
244+
/// - `ciphertext_len` -- [out] Pointer to a size_t where the length of `ciphertext_out` is stored.
245+
/// Not touched if the function returns false.
246+
///
247+
/// Outputs:
248+
/// - `bool` -- True if the encryption was successful, false if encryption failed.
249+
LIBSESSION_EXPORT bool session_encrypt_xchacha20(
250+
const unsigned char* plaintext_in,
251+
size_t plaintext_len,
252+
const unsigned char* enc_key_in, /* 32 bytes */
253+
unsigned char** ciphertext_out,
254+
size_t* ciphertext_len);
255+
256+
/// API: crypto/session_decrypt_xchacha20
257+
///
258+
/// Decrypts a value that was encrypted with the `encrypt_xchacha20` function.
259+
///
260+
/// Inputs:
261+
/// - `ciphertext_in` -- [in] the data to decrypt.
262+
/// - `ciphertext_len` -- [in] the length of `ciphertext_in`.
263+
/// - `enc_key_in` -- [in] the key to use for decryption (32 bytes).
264+
/// - `plaintext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the
265+
/// decrypted data written to it, and then the pointer to that buffer is stored here.
266+
/// This buffer must be `free()`d by the caller when done with it *unless* the function returns
267+
/// false, in which case the buffer pointer will not be set.
268+
/// - `plaintext_len` -- [out] Pointer to a size_t where the length of `plaintext_out` is stored.
269+
/// Not touched if the function returns false.
270+
///
271+
/// Outputs:
272+
/// - `bool` -- True if the decryption was successful, false if decryption failed.
273+
LIBSESSION_EXPORT bool session_decrypt_xchacha20(
274+
const unsigned char* ciphertext_in,
275+
size_t ciphertext_len,
276+
const unsigned char* enc_key_in, /* 32 bytes */
277+
unsigned char** plaintext_out,
278+
size_t* plaintext_len);
279+
232280
#ifdef __cplusplus
233281
}
234282
#endif

include/session/session_encrypt.hpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,30 @@ std::vector<unsigned char> decrypt_push_notification(
295295
std::string compute_message_hash(
296296
const std::string_view pubkey_hex, int16_t ns, std::string_view data);
297297

298+
/// API: crypto/encrypt_xchacha20
299+
///
300+
/// Encrypts a value with a given key using xchacha20.
301+
///
302+
/// Inputs:
303+
/// - `plaintext` -- the data to encrypt.
304+
/// - `enc_key` -- the key to use for encryption (32 bytes).
305+
///
306+
/// Outputs:
307+
/// - `std::vector<unsigned char>` -- the resulting ciphertext.
308+
std::vector<unsigned char> encrypt_xchacha20(
309+
std::span<const unsigned char> plaintext, std::span<const unsigned char> enc_key);
310+
311+
/// API: crypto/decrypt_xchacha20
312+
///
313+
/// Decrypts a value that was encrypted with the `encrypt_xchacha20` function.
314+
///
315+
/// Inputs:
316+
/// - `ciphertext` -- the data to decrypt.
317+
/// - `enc_key` -- the key to use for decryption (32 bytes).
318+
///
319+
/// Outputs:
320+
/// - `std::vector<unsigned char>` -- the resulting plaintext.
321+
std::vector<unsigned char> decrypt_xchacha20(
322+
std::span<const unsigned char> ciphertext, std::span<const unsigned char> enc_key);
323+
298324
} // namespace session

src/session_encrypt.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,8 +745,75 @@ std::string compute_message_hash(
745745
decoded_data);
746746
}
747747

748+
std::vector<unsigned char> encrypt_xchacha20(
749+
std::span<const unsigned char> plaintext, std::span<const unsigned char> enc_key) {
750+
if (enc_key.size() != 32)
751+
throw std::invalid_argument{"Invalid enc_key: expected 32 bytes"};
752+
753+
std::vector<unsigned char> ciphertext;
754+
ciphertext.resize(
755+
crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + plaintext.size() +
756+
crypto_aead_xchacha20poly1305_ietf_ABYTES);
757+
758+
// Generate random nonce, and stash it at the beginning of ciphertext:
759+
randombytes_buf(ciphertext.data(), crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
760+
761+
auto* c = reinterpret_cast<unsigned char*>(ciphertext.data()) +
762+
crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
763+
unsigned long long clen;
764+
765+
crypto_aead_xchacha20poly1305_ietf_encrypt(
766+
c,
767+
&clen,
768+
plaintext.data(),
769+
plaintext.size(),
770+
nullptr,
771+
0, // additional data
772+
nullptr, // nsec (always unused)
773+
reinterpret_cast<const unsigned char*>(ciphertext.data()),
774+
enc_key.data());
775+
assert(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + clen <= ciphertext.size());
776+
ciphertext.resize(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + clen);
777+
return ciphertext;
778+
}
779+
780+
std::vector<unsigned char> decrypt_xchacha20(
781+
std::span<const unsigned char> ciphertext, std::span<const unsigned char> enc_key) {
782+
if (ciphertext.size() <
783+
crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + crypto_aead_xchacha20poly1305_ietf_ABYTES)
784+
throw std::invalid_argument{
785+
"Invalid ciphertext: too short to contain valid encrypted data"};
786+
if (enc_key.size() != 32)
787+
throw std::invalid_argument{"Invalid enc_key: expected 32 bytes"};
788+
789+
// Extract nonce from the beginning of the ciphertext:
790+
auto nonce = ciphertext.subspan(0, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
791+
ciphertext = ciphertext.subspan(nonce.size());
792+
793+
std::vector<unsigned char> plaintext;
794+
plaintext.resize(ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES);
795+
auto* m = reinterpret_cast<unsigned char*>(plaintext.data());
796+
unsigned long long mlen;
797+
if (0 != crypto_aead_xchacha20poly1305_ietf_decrypt(
798+
m,
799+
&mlen,
800+
nullptr, // nsec (always unused)
801+
ciphertext.data(),
802+
ciphertext.size(),
803+
nullptr,
804+
0, // additional data
805+
nonce.data(),
806+
enc_key.data()))
807+
throw std::runtime_error{"Could not decrypt (XChaCha20-Poly1305)"};
808+
assert(mlen <= plaintext.size());
809+
plaintext.resize(mlen);
810+
return plaintext;
811+
}
812+
748813
} // namespace session
749814

815+
extern "C" {
816+
750817
using namespace session;
751818

752819
LIBSESSION_C_API bool session_encrypt_for_recipient_deterministic(
@@ -925,3 +992,45 @@ LIBSESSION_C_API bool session_compute_message_hash(
925992
return false;
926993
}
927994
}
995+
996+
LIBSESSION_C_API bool session_encrypt_xchacha20(
997+
const unsigned char* plaintext_in,
998+
size_t plaintext_len,
999+
const unsigned char* enc_key_in,
1000+
unsigned char** ciphertext_out,
1001+
size_t* ciphertext_len) {
1002+
try {
1003+
auto ciphertext = session::encrypt_xchacha20(
1004+
std::span<const unsigned char>{plaintext_in, plaintext_len},
1005+
std::span<const unsigned char>{enc_key_in, 32});
1006+
1007+
*ciphertext_out = static_cast<unsigned char*>(malloc(ciphertext.size()));
1008+
*ciphertext_len = ciphertext.size();
1009+
std::memcpy(*ciphertext_out, ciphertext.data(), ciphertext.size());
1010+
return true;
1011+
} catch (...) {
1012+
return false;
1013+
}
1014+
}
1015+
1016+
LIBSESSION_C_API bool session_decrypt_xchacha20(
1017+
const unsigned char* ciphertext_in,
1018+
size_t ciphertext_len,
1019+
const unsigned char* enc_key_in,
1020+
unsigned char** plaintext_out,
1021+
size_t* plaintext_len) {
1022+
try {
1023+
auto plaintext = session::decrypt_xchacha20(
1024+
std::span<const unsigned char>{ciphertext_in, ciphertext_len},
1025+
std::span<const unsigned char>{enc_key_in, 32});
1026+
1027+
*plaintext_out = static_cast<unsigned char*>(malloc(plaintext.size()));
1028+
*plaintext_len = plaintext.size();
1029+
std::memcpy(*plaintext_out, plaintext.data(), plaintext.size());
1030+
return true;
1031+
} catch (...) {
1032+
return false;
1033+
}
1034+
}
1035+
1036+
} // extern "C"

tests/test_session_encrypt.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,19 @@ TEST_CASE("Session message hash", "[session][message-hash]") {
500500
CHECK(compute_message_hash(pubkey_hex, ns, base64_data2) ==
501501
"apKu8OMjrbU+YeVWpMSyrr1wHq51K3uKD8WM0F4E1cE");
502502
}
503+
504+
TEST_CASE("xchacha20", "[session][xchacha20]") {
505+
using namespace session;
506+
507+
auto payload =
508+
"da74ac6e96afda1c5a07d5bde1b8b1e1c05be73cb3c84112f31f00369d67154d00ff029090b069b48c3cf603d838d4ef623d54"_hexbytes;
509+
auto enc_key = "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes;
510+
511+
CHECK(decrypt_xchacha20(payload, enc_key) == to_vector("TestMessage"));
512+
CHECK_THROWS(decrypt_xchacha20(to_span("invalid"), enc_key));
513+
CHECK_THROWS(decrypt_xchacha20(payload, to_span("invalid")));
514+
515+
auto ciphertext = encrypt_xchacha20(to_span("TestMessage"), enc_key);
516+
CHECK(decrypt_xchacha20(ciphertext, enc_key) == to_vector("TestMessage"));
517+
CHECK_THROWS(encrypt_xchacha20(payload, to_span("invalid")));
518+
}

0 commit comments

Comments
 (0)