]> granicus.if.org Git - php/commitdiff
hash: Implement secret support for xxh3 and xxh128
authorAnatol Belski <ab@php.net>
Sat, 20 Feb 2021 22:26:59 +0000 (23:26 +0100)
committerAnatol Belski <ab@php.net>
Sat, 20 Feb 2021 22:26:59 +0000 (23:26 +0100)
A secret can be passed through the options array. The length is
currently in the range of 136 to 256 bytes. The concerned algos are
already marked as non serializable.

Signed-off-by: Anatol Belski <ab@php.net>
ext/hash/hash_xxhash.c
ext/hash/php_hash_xxhash.h
ext/hash/tests/xxhash_secret.phpt [new file with mode: 0644]

index 8ce5fcfc7fa9ebe843d3e0d42062aa4b9185d8bf..9a49c7691acf48ad07f13601bd09b74116be1ff8 100644 (file)
@@ -165,21 +165,51 @@ const php_hash_ops php_hash_xxh3_64_ops = {
        0
 };
 
-PHP_HASH_API void PHP_XXH3_64_Init(PHP_XXH3_64_CTX *ctx, HashTable *args)
+typedef XXH_errorcode (*xxh3_reset_with_secret_func_t)(XXH3_state_t*, const void*, size_t);
+typedef XXH_errorcode (*xxh3_reset_with_seed_func_t)(XXH3_state_t*, XXH64_hash_t);
+
+zend_always_inline static void _PHP_XXH3_Init(PHP_XXH3_64_CTX *ctx, HashTable *args,
+               xxh3_reset_with_seed_func_t func_init_seed, xxh3_reset_with_secret_func_t func_init_secret, const char* algo_name)
 {
-       /* TODO integrate also XXH3_64bits_reset_withSecret(). */
-       XXH64_hash_t seed = 0;
+       memset(&ctx->s, 0, sizeof ctx->s);
 
        if (args) {
                zval *_seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
-               /* This might be a bit too restrictive, but thinking that a seed might be set
-                       once and for all, it should be done a clean way. */
+               zval *_secret = zend_hash_str_find_deref(args, "secret", sizeof("secret") - 1);
+
+               if (_seed && _secret) {
+                       zend_throw_error(NULL, "%s: Only one of seed or secret is to be passed for initialization", algo_name);
+                       return;
+               }
+
                if (_seed && IS_LONG == Z_TYPE_P(_seed)) {
-                       seed = (XXH64_hash_t)Z_LVAL_P(_seed);
+                       /* This might be a bit too restrictive, but thinking that a seed might be set
+                               once and for all, it should be done a clean way. */
+                       func_init_seed(&ctx->s, (XXH64_hash_t)Z_LVAL_P(_seed));
+                       return;
+               } else if (_secret) {
+                       convert_to_string(_secret);
+                       size_t len = Z_STRLEN_P(_secret);
+                       if (len < PHP_XXH3_SECRET_SIZE_MIN) {
+                               zend_throw_error(NULL, "%s: Secret length must be >= %u bytes, %zu bytes passed", algo_name, XXH3_SECRET_SIZE_MIN, len);
+                               return;
+                       }
+                       if (len > sizeof(ctx->secret)) {
+                               len = sizeof(ctx->secret);
+                               php_error_docref(NULL, E_WARNING, "%s: Secret content exceeding %zu bytes discarded", algo_name, sizeof(ctx->secret));
+                       }
+                       memcpy((unsigned char *)ctx->secret, Z_STRVAL_P(_secret), len);
+                       func_init_secret(&ctx->s, ctx->secret, len);
+                       return;
                }
        }
 
-       XXH3_64bits_reset_withSeed(&ctx->s, seed);
+       func_init_seed(&ctx->s, 0);
+}
+
+PHP_HASH_API void PHP_XXH3_64_Init(PHP_XXH3_64_CTX *ctx, HashTable *args)
+{
+       _PHP_XXH3_Init(ctx, args, XXH3_64bits_reset_withSeed, XXH3_64bits_reset_withSecret, "xxh3");
 }
 
 PHP_HASH_API void PHP_XXH3_64_Update(PHP_XXH3_64_CTX *ctx, const unsigned char *in, size_t len)
@@ -238,19 +268,7 @@ const php_hash_ops php_hash_xxh3_128_ops = {
 
 PHP_HASH_API void PHP_XXH3_128_Init(PHP_XXH3_128_CTX *ctx, HashTable *args)
 {
-       /* TODO integrate also XXH3_128__64bits_reset_withSecret(). */
-       XXH64_hash_t seed = 0;
-
-       if (args) {
-               zval *_seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
-               /* This might be a bit too restrictive, but thinking that a seed might be set
-                       once and for all, it should be done a clean way. */
-               if (_seed && IS_LONG == Z_TYPE_P(_seed)) {
-                       seed = (XXH64_hash_t)Z_LVAL_P(_seed);
-               }
-       }
-
-       XXH3_128bits_reset_withSeed(&ctx->s, seed);
+       _PHP_XXH3_Init(ctx, args, XXH3_128bits_reset_withSeed, XXH3_128bits_reset_withSecret, "xxh128");
 }
 
 PHP_HASH_API void PHP_XXH3_128_Update(PHP_XXH3_128_CTX *ctx, const unsigned char *in, size_t len)
index d6a50cb2707b39ee78e35fbf384556711cc35e66..1a66e742f3f8fffc0190b1ca7adb32589595fb28 100644 (file)
@@ -40,18 +40,29 @@ PHP_HASH_API void PHP_XXH64Update(PHP_XXH64_CTX *ctx, const unsigned char *in, s
 PHP_HASH_API void PHP_XXH64Final(unsigned char digest[8], PHP_XXH64_CTX *ctx);
 PHP_HASH_API int PHP_XXH64Copy(const php_hash_ops *ops, PHP_XXH64_CTX *orig_context, PHP_XXH64_CTX *copy_context);
 
+#define PHP_XXH3_SECRET_SIZE_MIN XXH3_SECRET_SIZE_MIN
+#define PHP_XXH3_SECRET_SIZE_MAX 256
+
 typedef struct {
        XXH3_state_t s;
-} PHP_XXH3_64_CTX; 
+       /* The value must survive the whole streaming cycle from init to final.
+
+          A more flexible mechanism would be to carry zend_string* passed through
+          the options. However, that will require to introduce a destructor
+          handler for ctx, so then it wolud be automatically called from the
+          object destructor. Until that is given, the viable way is to use a
+          plausible max secret length. */
+       const unsigned char secret[PHP_XXH3_SECRET_SIZE_MAX];
+} PHP_XXH3_CTX;
+
+typedef PHP_XXH3_CTX PHP_XXH3_64_CTX;
 
 PHP_HASH_API void PHP_XXH3_64_Init(PHP_XXH3_64_CTX *ctx, HashTable *args);
 PHP_HASH_API void PHP_XXH3_64_Update(PHP_XXH3_64_CTX *ctx, const unsigned char *in, size_t len);
 PHP_HASH_API void PHP_XXH3_64_Final(unsigned char digest[8], PHP_XXH3_64_CTX *ctx);
 PHP_HASH_API int PHP_XXH3_64_Copy(const php_hash_ops *ops, PHP_XXH3_64_CTX *orig_context, PHP_XXH3_64_CTX *copy_context);
 
-typedef struct {
-       XXH3_state_t s;
-} PHP_XXH3_128_CTX; 
+typedef PHP_XXH3_CTX PHP_XXH3_128_CTX;
 
 PHP_HASH_API void PHP_XXH3_128_Init(PHP_XXH3_128_CTX *ctx, HashTable *args);
 PHP_HASH_API void PHP_XXH3_128_Update(PHP_XXH3_128_CTX *ctx, const unsigned char *in, size_t len);
diff --git a/ext/hash/tests/xxhash_secret.phpt b/ext/hash/tests/xxhash_secret.phpt
new file mode 100644 (file)
index 0000000..91e7d92
--- /dev/null
@@ -0,0 +1,42 @@
+--TEST--
+Hash: xxHash secret
+--FILE--
+<?php
+
+foreach (["xxh3", "xxh128"] as $a) {
+
+       //$secret = random_bytes(256);
+       $secret = str_repeat('a', 256);
+
+       try {
+               $ctx = hash_init($a, options: ["seed" => 24, "secret" => $secret]);
+       } catch (Throwable $e) {
+               var_dump($e->getMessage());
+       }
+
+       try {
+               $ctx = hash_init($a, options: ["secret" => str_repeat('a', 17)]);
+       } catch (Throwable $e) {
+               var_dump($e->getMessage());
+       }
+
+       $ctx = hash_init($a, options: ["secret" => $secret]);
+       hash_update($ctx, "Lorem");
+       hash_update($ctx, " ipsum dolor");
+       hash_update($ctx, " sit amet,");
+       hash_update($ctx, " consectetur adipiscing elit.");
+       $h0 = hash_final($ctx);
+
+       $h1 = hash($a, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", options: ["secret" => $secret]);
+       echo $h0 , " == ", $h1, " == ", (($h0 == $h1) ? "true" : "false"), "\n";
+
+}
+
+?>
+--EXPECT--
+string(67) "xxh3: Only one of seed or secret is to be passed for initialization"
+string(57) "xxh3: Secret length must be >= 136 bytes, 17 bytes passed"
+8028aa834c03557a == 8028aa834c03557a == true
+string(69) "xxh128: Only one of seed or secret is to be passed for initialization"
+string(59) "xxh128: Secret length must be >= 136 bytes, 17 bytes passed"
+54279097795e7218093a05d4d781cbb9 == 54279097795e7218093a05d4d781cbb9 == true