diff options
author | Anatol Belski <ab@php.net> | 2021-02-20 23:26:59 +0100 |
---|---|---|
committer | Anatol Belski <ab@php.net> | 2021-02-20 23:26:59 +0100 |
commit | 20501c72c838e2adfbc8591e270ebbd67b22f400 (patch) | |
tree | ba732ace869c18c47a9f6461872855d71f82bddc | |
parent | 3b29f5164dab76392477d67b515baf696d14d29b (diff) | |
download | php-git-20501c72c838e2adfbc8591e270ebbd67b22f400.tar.gz |
hash: Implement secret support for xxh3 and xxh128
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>
-rw-r--r-- | ext/hash/hash_xxhash.c | 58 | ||||
-rw-r--r-- | ext/hash/php_hash_xxhash.h | 19 | ||||
-rw-r--r-- | ext/hash/tests/xxhash_secret.phpt | 42 |
3 files changed, 95 insertions, 24 deletions
diff --git a/ext/hash/hash_xxhash.c b/ext/hash/hash_xxhash.c index 8ce5fcfc7f..9a49c7691a 100644 --- a/ext/hash/hash_xxhash.c +++ b/ext/hash/hash_xxhash.c @@ -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) diff --git a/ext/hash/php_hash_xxhash.h b/ext/hash/php_hash_xxhash.h index d6a50cb270..1a66e742f3 100644 --- a/ext/hash/php_hash_xxhash.h +++ b/ext/hash/php_hash_xxhash.h @@ -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 index 0000000000..91e7d929d3 --- /dev/null +++ b/ext/hash/tests/xxhash_secret.phpt @@ -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 |