Skip to content

Commit 86254fd

Browse files
Add crypto_stream_xchacha20 to ext/sodium
Paragon Initiative Enterprises is aware of PHP applications that use sodium_compat's ParagonIE\Sodium\Core\XChaCha20 class directly for stream encryption. Greater performance and security assurance is offered by exposing libsodium's crypto_stream_xchacha20 API to PHP users. It's acceptable to only include this change in PHP 8.1+; the offending applications are more than welcome to either install ext/sodium from PECL or upgrade to 8.1 when it comes out later this year. Ref: jedisct1/libsodium-php#211
1 parent 14fd14d commit 86254fd

File tree

5 files changed

+172
-0
lines changed

5 files changed

+172
-0
lines changed

ext/sodium/libsodium.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,13 @@ PHP_MINIT_FUNCTION(sodium)
335335
crypto_stream_NONCEBYTES, CONST_CS | CONST_PERSISTENT);
336336
REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_STREAM_KEYBYTES",
337337
crypto_stream_KEYBYTES, CONST_CS | CONST_PERSISTENT);
338+
339+
#ifdef crypto_stream_xchacha20_KEYBYTES
340+
REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES",
341+
crypto_stream_xchacha20_NONCEBYTES, CONST_CS | CONST_PERSISTENT);
342+
REGISTER_LONG_CONSTANT("SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES",
343+
crypto_stream_xchacha20_KEYBYTES, CONST_CS | CONST_PERSISTENT);
344+
#endif
338345
#ifdef sodium_base64_VARIANT_ORIGINAL
339346
REGISTER_LONG_CONSTANT("SODIUM_BASE64_VARIANT_ORIGINAL",
340347
sodium_base64_VARIANT_ORIGINAL, CONST_CS | CONST_PERSISTENT);
@@ -1465,6 +1472,83 @@ PHP_FUNCTION(sodium_crypto_stream_xor)
14651472
RETURN_NEW_STR(ciphertext);
14661473
}
14671474

1475+
#ifdef crypto_stream_xchacha20_KEYBYTES
1476+
PHP_FUNCTION(sodium_crypto_stream_xchacha20)
1477+
{
1478+
zend_string *ciphertext;
1479+
unsigned char *key;
1480+
unsigned char *nonce;
1481+
zend_long ciphertext_len;
1482+
size_t key_len;
1483+
size_t nonce_len;
1484+
1485+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "lss",
1486+
&ciphertext_len,
1487+
&nonce, &nonce_len,
1488+
&key, &key_len) == FAILURE) {
1489+
return;
1490+
}
1491+
if (ciphertext_len <= 0 || ciphertext_len >= SIZE_MAX) {
1492+
zend_throw_exception(sodium_exception_ce, "ciphertext length must be greater than 0", 0);
1493+
return;
1494+
}
1495+
if (nonce_len != crypto_stream_xchacha20_NONCEBYTES) {
1496+
zend_throw_exception(sodium_exception_ce, "nonce should be SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES bytes", 0);
1497+
return;
1498+
}
1499+
if (key_len != crypto_stream_xchacha20_KEYBYTES) {
1500+
zend_throw_exception(sodium_exception_ce, "key should be SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES bytes", 0);
1501+
return;
1502+
}
1503+
ciphertext = zend_string_checked_alloc((size_t) ciphertext_len, 0);
1504+
if (crypto_stream_xchacha20((unsigned char *) ZSTR_VAL(ciphertext),
1505+
(unsigned long long) ciphertext_len, nonce, key) != 0) {
1506+
zend_string_free(ciphertext);
1507+
zend_throw_exception(sodium_exception_ce, "internal error", 0);
1508+
return;
1509+
}
1510+
1511+
RETURN_STR(ciphertext);
1512+
}
1513+
1514+
PHP_FUNCTION(sodium_crypto_stream_xchacha20_xor)
1515+
{
1516+
zend_string *ciphertext;
1517+
unsigned char *key;
1518+
unsigned char *msg;
1519+
unsigned char *nonce;
1520+
size_t ciphertext_len;
1521+
size_t key_len;
1522+
size_t msg_len;
1523+
size_t nonce_len;
1524+
1525+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss",
1526+
&msg, &msg_len,
1527+
&nonce, &nonce_len,
1528+
&key, &key_len) == FAILURE) {
1529+
return;
1530+
}
1531+
if (nonce_len != crypto_stream_xchacha20_NONCEBYTES) {
1532+
zend_throw_exception(sodium_exception_ce, "nonce should be SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES bytes", 0);
1533+
return;
1534+
}
1535+
if (key_len != crypto_stream_xchacha20_KEYBYTES) {
1536+
zend_throw_exception(sodium_exception_ce, "key should be SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES bytes", 0);
1537+
return;
1538+
}
1539+
ciphertext_len = msg_len;
1540+
ciphertext = zend_string_checked_alloc((size_t) ciphertext_len, 0);
1541+
if (crypto_stream_xchacha20_xor((unsigned char *) ZSTR_VAL(ciphertext), msg,
1542+
(unsigned long long) msg_len, nonce, key) != 0) {
1543+
zend_string_free(ciphertext);
1544+
zend_throw_exception(sodium_exception_ce, "internal error", 0);
1545+
return;
1546+
}
1547+
1548+
RETURN_STR(ciphertext);
1549+
}
1550+
#endif
1551+
14681552
#ifdef crypto_pwhash_SALTBYTES
14691553
PHP_FUNCTION(sodium_crypto_pwhash)
14701554
{
@@ -2894,6 +2978,18 @@ PHP_FUNCTION(sodium_crypto_stream_keygen)
28942978
randombytes_buf(key, sizeof key);
28952979
RETURN_STRINGL((const char *) key, sizeof key);
28962980
}
2981+
#ifdef crypto_stream_xchacha20_KEYBYTES
2982+
PHP_FUNCTION(sodium_crypto_stream_xchacha20_keygen)
2983+
{
2984+
unsigned char key[crypto_stream_xchacha20_KEYBYTES];
2985+
2986+
if (zend_parse_parameters_none() == FAILURE) {
2987+
return;
2988+
}
2989+
randombytes_buf(key, sizeof key);
2990+
RETURN_STRINGL((const char *) key, sizeof key);
2991+
}
2992+
#endif
28972993

28982994
PHP_FUNCTION(sodium_crypto_kdf_derive_from_key)
28992995
{

ext/sodium/libsodium.stub.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ function sodium_crypto_stream_keygen(): string {}
160160

161161
function sodium_crypto_stream_xor(string $message, string $nonce, string $key): string {}
162162

163+
function sodium_crypto_stream_xchacha20(int $length, string $nonce, string $key) : string {}
164+
165+
function sodium_crypto_stream_xchacha20_keygen() : string {}
166+
167+
function sodium_crypto_stream_xchacha20_xor(string $message, string $nonce, string $key) : string {}
168+
163169
function sodium_add(string &$string1, string $string2): void {}
164170

165171
function sodium_compare(string $string1, string $string2): int {}

ext/sodium/libsodium_arginfo.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,11 @@ ZEND_END_ARG_INFO()
348348

349349
#define arginfo_sodium_crypto_stream_xor arginfo_sodium_crypto_secretbox
350350

351+
#ifdef crypto_stream_xchacha20_KEYBYTES
352+
#define arginfo_sodium_crypto_stream_xchacha20_xor arginfo_sodium_crypto_stream_xor
353+
#define arginfo_sodium_crypto_stream_xchacha20 arginfo_sodium_crypto_stream
354+
#endif
355+
351356
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sodium_add, 0, 2, IS_VOID, 0)
352357
ZEND_ARG_TYPE_INFO(1, string1, IS_STRING, 0)
353358
ZEND_ARG_TYPE_INFO(0, string2, IS_STRING, 0)
@@ -514,6 +519,11 @@ ZEND_FUNCTION(sodium_crypto_sign_verify_detached);
514519
ZEND_FUNCTION(sodium_crypto_stream);
515520
ZEND_FUNCTION(sodium_crypto_stream_keygen);
516521
ZEND_FUNCTION(sodium_crypto_stream_xor);
522+
#if defined(crypto_stream_xchacha20_KEYBYTES)
523+
ZEND_FUNCTION(sodium_crypto_stream_xchacha20);
524+
ZEND_FUNCTION(sodium_crypto_stream_xchacha20_keygen);
525+
ZEND_FUNCTION(sodium_crypto_stream_xchacha20_xor);
526+
#endif
517527
ZEND_FUNCTION(sodium_add);
518528
ZEND_FUNCTION(sodium_compare);
519529
ZEND_FUNCTION(sodium_increment);
@@ -643,6 +653,11 @@ static const zend_function_entry ext_functions[] = {
643653
ZEND_FE(sodium_crypto_stream, arginfo_sodium_crypto_stream)
644654
ZEND_FE(sodium_crypto_stream_keygen, arginfo_sodium_crypto_stream_keygen)
645655
ZEND_FE(sodium_crypto_stream_xor, arginfo_sodium_crypto_stream_xor)
656+
#if defined(crypto_stream_xchacha20_KEYBYTES)
657+
ZEND_FE(sodium_crypto_stream_xchacha20, arginfo_sodium_crypto_xchacha20_stream)
658+
ZEND_FE(sodium_crypto_stream_xchacha20_keygen, arginfo_sodium_crypto_xchacha20_stream_keygen)
659+
ZEND_FE(sodium_crypto_stream_xchacha20_xor, arginfo_sodium_crypto_xchacha20_stream_xor)
660+
#endif
646661
ZEND_FE(sodium_add, arginfo_sodium_add)
647662
ZEND_FE(sodium_compare, arginfo_sodium_compare)
648663
ZEND_FE(sodium_increment, arginfo_sodium_increment)

ext/sodium/php_libsodium.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ PHP_FUNCTION(sodium_crypto_sign_verify_detached);
112112
PHP_FUNCTION(sodium_crypto_stream);
113113
PHP_FUNCTION(sodium_crypto_stream_keygen);
114114
PHP_FUNCTION(sodium_crypto_stream_xor);
115+
PHP_FUNCTION(sodium_crypto_stream_xchacha20);
116+
PHP_FUNCTION(sodium_crypto_stream_xchacha20_keygen);
117+
PHP_FUNCTION(sodium_crypto_stream_xchacha20_xor);
115118
PHP_FUNCTION(sodium_hex2bin);
116119
PHP_FUNCTION(sodium_increment);
117120
PHP_FUNCTION(sodium_memcmp);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
Check for libsodium stream
3+
--SKIPIF--
4+
<?php if (!extension_loaded("sodium") || !defined('CRYPTO_STREAM_XCHACHA20_KEYBYTES')) print "skip"; ?>
5+
--FILE--
6+
<?php
7+
$nonce = random_bytes(SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES);
8+
$key = sodium_crypto_stream_xchacha20_keygen();
9+
10+
$len = 100;
11+
$stream = sodium_crypto_stream_xchacha20($len, $nonce, $key);
12+
var_dump(strlen($stream));
13+
14+
$stream2 = sodium_crypto_stream_xchacha20($len, $nonce, $key);
15+
16+
$nonce = random_bytes(SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES);
17+
$stream3 = sodium_crypto_stream_xchacha20($len, $nonce, $key);
18+
19+
$key = sodium_crypto_stream_keygen();
20+
$stream4 = sodium_crypto_stream_xchacha20($len, $nonce, $key);
21+
22+
var_dump($stream === $stream2);
23+
var_dump($stream !== $stream3);
24+
var_dump($stream !== $stream4);
25+
var_dump($stream2 !== $stream3);
26+
var_dump($stream2 !== $stream4);
27+
var_dump($stream3 !== $stream4);
28+
29+
$stream5 = sodium_crypto_stream_xchacha20_xor($stream, $nonce, $key);
30+
var_dump($stream5 !== $stream);
31+
$stream6 = sodium_crypto_stream_xchacha20_xor($stream5, $nonce, $key);
32+
33+
var_dump($stream6 === $stream);
34+
35+
try {
36+
sodium_crypto_stream_xchacha20($len, substr($nonce, 1), $key);
37+
} catch (SodiumException $ex) {
38+
var_dump(true);
39+
}
40+
41+
?>
42+
--EXPECT--
43+
int(100)
44+
bool(true)
45+
bool(true)
46+
bool(true)
47+
bool(true)
48+
bool(true)
49+
bool(true)
50+
bool(true)
51+
bool(true)
52+
bool(true)

0 commit comments

Comments
 (0)