php-src/ext/hash/hash_xxhash.c
Anatol Belski 20501c72c8 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>
2021-02-20 23:26:59 +01:00

307 lines
9.8 KiB
C

/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Anatol Belski <ab@php.net> |
+----------------------------------------------------------------------+
*/
#include "php_hash.h"
#include "php_hash_xxhash.h"
static int php_hash_xxh32_unserialize(
php_hashcontext_object *hash, zend_long magic, const zval *zv);
static int php_hash_xxh64_unserialize(
php_hashcontext_object *hash, zend_long magic, const zval *zv);
const php_hash_ops php_hash_xxh32_ops = {
"xxh32",
(php_hash_init_func_t) PHP_XXH32Init,
(php_hash_update_func_t) PHP_XXH32Update,
(php_hash_final_func_t) PHP_XXH32Final,
(php_hash_copy_func_t) PHP_XXH32Copy,
php_hash_serialize,
php_hash_xxh32_unserialize,
PHP_XXH32_SPEC,
4,
4,
sizeof(PHP_XXH32_CTX),
0
};
PHP_HASH_API void PHP_XXH32Init(PHP_XXH32_CTX *ctx, HashTable *args)
{
/* XXH32_createState() is not used intentionally. */
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. */
if (seed && IS_LONG == Z_TYPE_P(seed)) {
XXH32_reset(&ctx->s, (XXH32_hash_t)Z_LVAL_P(seed));
} else {
XXH32_reset(&ctx->s, 0);
}
} else {
XXH32_reset(&ctx->s, 0);
}
}
PHP_HASH_API void PHP_XXH32Update(PHP_XXH32_CTX *ctx, const unsigned char *in, size_t len)
{
XXH32_update(&ctx->s, in, len);
}
PHP_HASH_API void PHP_XXH32Final(unsigned char digest[4], PHP_XXH32_CTX *ctx)
{
XXH32_hash_t const h = XXH32_digest(&ctx->s);
digest[0] = (unsigned char)((h >> 24) & 0xff);
digest[1] = (unsigned char)((h >> 16) & 0xff);
digest[2] = (unsigned char)((h >> 8) & 0xff);
digest[3] = (unsigned char)(h & 0xff);
}
PHP_HASH_API int PHP_XXH32Copy(const php_hash_ops *ops, PHP_XXH32_CTX *orig_context, PHP_XXH32_CTX *copy_context)
{
copy_context->s = orig_context->s;
return SUCCESS;
}
static int php_hash_xxh32_unserialize(
php_hashcontext_object *hash, zend_long magic, const zval *zv)
{
PHP_XXH32_CTX *ctx = (PHP_XXH32_CTX *) hash->context;
int r = FAILURE;
if (magic == PHP_HASH_SERIALIZE_MAGIC_SPEC
&& (r = php_hash_unserialize_spec(hash, zv, PHP_XXH32_SPEC)) == SUCCESS
&& ctx->s.memsize < 16) {
return SUCCESS;
} else {
return r != SUCCESS ? r : -2000;
}
}
const php_hash_ops php_hash_xxh64_ops = {
"xxh64",
(php_hash_init_func_t) PHP_XXH64Init,
(php_hash_update_func_t) PHP_XXH64Update,
(php_hash_final_func_t) PHP_XXH64Final,
(php_hash_copy_func_t) PHP_XXH64Copy,
php_hash_serialize,
php_hash_xxh64_unserialize,
PHP_XXH64_SPEC,
8,
8,
sizeof(PHP_XXH64_CTX),
0
};
PHP_HASH_API void PHP_XXH64Init(PHP_XXH64_CTX *ctx, HashTable *args)
{
/* XXH64_createState() is not used intentionally. */
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. */
if (seed && IS_LONG == Z_TYPE_P(seed)) {
XXH64_reset(&ctx->s, (XXH64_hash_t)Z_LVAL_P(seed));
} else {
XXH64_reset(&ctx->s, 0);
}
} else {
XXH64_reset(&ctx->s, 0);
}
}
PHP_HASH_API void PHP_XXH64Update(PHP_XXH64_CTX *ctx, const unsigned char *in, size_t len)
{
XXH64_update(&ctx->s, in, len);
}
PHP_HASH_API void PHP_XXH64Final(unsigned char digest[8], PHP_XXH64_CTX *ctx)
{
XXH64_hash_t const h = XXH64_digest(&ctx->s);
digest[0] = (unsigned char)((h >> 56) & 0xff);
digest[1] = (unsigned char)((h >> 48) & 0xff);
digest[2] = (unsigned char)((h >> 40) & 0xff);
digest[3] = (unsigned char)((h >> 32) & 0xff);
digest[4] = (unsigned char)((h >> 24) & 0xff);
digest[5] = (unsigned char)((h >> 16) & 0xff);
digest[6] = (unsigned char)((h >> 8) & 0xff);
digest[7] = (unsigned char)(h & 0xff);
}
PHP_HASH_API int PHP_XXH64Copy(const php_hash_ops *ops, PHP_XXH64_CTX *orig_context, PHP_XXH64_CTX *copy_context)
{
copy_context->s = orig_context->s;
return SUCCESS;
}
const php_hash_ops php_hash_xxh3_64_ops = {
"xxh3",
(php_hash_init_func_t) PHP_XXH3_64_Init,
(php_hash_update_func_t) PHP_XXH3_64_Update,
(php_hash_final_func_t) PHP_XXH3_64_Final,
(php_hash_copy_func_t) PHP_XXH3_64_Copy,
php_hash_serialize,
php_hash_unserialize,
NULL,
8,
8,
sizeof(PHP_XXH3_64_CTX),
0
};
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)
{
memset(&ctx->s, 0, sizeof ctx->s);
if (args) {
zval *_seed = zend_hash_str_find_deref(args, "seed", sizeof("seed") - 1);
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)) {
/* 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;
}
}
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)
{
XXH3_64bits_update(&ctx->s, in, len);
}
PHP_HASH_API void PHP_XXH3_64_Final(unsigned char digest[8], PHP_XXH3_64_CTX *ctx)
{
XXH64_hash_t const h = XXH3_64bits_digest(&ctx->s);
digest[0] = (unsigned char)((h >> 56) & 0xff);
digest[1] = (unsigned char)((h >> 48) & 0xff);
digest[2] = (unsigned char)((h >> 40) & 0xff);
digest[3] = (unsigned char)((h >> 32) & 0xff);
digest[4] = (unsigned char)((h >> 24) & 0xff);
digest[5] = (unsigned char)((h >> 16) & 0xff);
digest[6] = (unsigned char)((h >> 8) & 0xff);
digest[7] = (unsigned char)(h & 0xff);
}
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)
{
copy_context->s = orig_context->s;
return SUCCESS;
}
static int php_hash_xxh64_unserialize(
php_hashcontext_object *hash, zend_long magic, const zval *zv)
{
PHP_XXH64_CTX *ctx = (PHP_XXH64_CTX *) hash->context;
int r = FAILURE;
if (magic == PHP_HASH_SERIALIZE_MAGIC_SPEC
&& (r = php_hash_unserialize_spec(hash, zv, PHP_XXH64_SPEC)) == SUCCESS
&& ctx->s.memsize < 32) {
return SUCCESS;
} else {
return r != SUCCESS ? r : -2000;
}
}
const php_hash_ops php_hash_xxh3_128_ops = {
"xxh128",
(php_hash_init_func_t) PHP_XXH3_128_Init,
(php_hash_update_func_t) PHP_XXH3_128_Update,
(php_hash_final_func_t) PHP_XXH3_128_Final,
(php_hash_copy_func_t) PHP_XXH3_128_Copy,
php_hash_serialize,
php_hash_unserialize,
NULL,
16,
8,
sizeof(PHP_XXH3_128_CTX),
0
};
PHP_HASH_API void PHP_XXH3_128_Init(PHP_XXH3_128_CTX *ctx, HashTable *args)
{
_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)
{
XXH3_128bits_update(&ctx->s, in, len);
}
PHP_HASH_API void PHP_XXH3_128_Final(unsigned char digest[16], PHP_XXH3_128_CTX *ctx)
{
XXH128_hash_t const h = XXH3_128bits_digest(&ctx->s);
digest[0] = (unsigned char)((h.high64 >> 56) & 0xff);
digest[1] = (unsigned char)((h.high64 >> 48) & 0xff);
digest[2] = (unsigned char)((h.high64 >> 40) & 0xff);
digest[3] = (unsigned char)((h.high64 >> 32) & 0xff);
digest[4] = (unsigned char)((h.high64 >> 24) & 0xff);
digest[5] = (unsigned char)((h.high64 >> 16) & 0xff);
digest[6] = (unsigned char)((h.high64 >> 8) & 0xff);
digest[7] = (unsigned char)(h.high64 & 0xff);
digest[8] = (unsigned char)((h.low64 >> 56) & 0xff);
digest[9] = (unsigned char)((h.low64 >> 48) & 0xff);
digest[10] = (unsigned char)((h.low64 >> 40) & 0xff);
digest[11] = (unsigned char)((h.low64 >> 32) & 0xff);
digest[12] = (unsigned char)((h.low64 >> 24) & 0xff);
digest[13] = (unsigned char)((h.low64 >> 16) & 0xff);
digest[14] = (unsigned char)((h.low64 >> 8) & 0xff);
digest[15] = (unsigned char)(h.low64 & 0xff);
}
PHP_HASH_API int PHP_XXH3_128_Copy(const php_hash_ops *ops, PHP_XXH3_128_CTX *orig_context, PHP_XXH3_128_CTX *copy_context)
{
copy_context->s = orig_context->s;
return SUCCESS;
}