alex-kassel / stable-fingerprint
Deterministic canonicalization and hashing for PHP 8.4+ payloads adhering to RFC 8785 JCS
Requires (Dev)
- phpunit/phpunit: ^11.0
README
Deterministic canonicalization and payload hashing for PHP 8.4+ adhering strictly to RFC 8785 JSON Canonicalization Scheme (JCS).
Overview
In modern PHP applications, generating deterministic hashes for data structures (DTOs, API request payloads, database entities, webhooks) is often unreliable. Default json_encode() outputs vary across environments due to:
- Unsorted associative array keys.
- Differing float serialization precision settings (
serialize_precision). - Non-standardized
DateTimeInterfacetimezones and formats. - Inconsistent Unicode normalization forms (NFC vs NFD).
- Presence of transient dynamic fields like timestamps, nonces, or request IDs.
StableFingerprint solves this by normalizing any PHP payload (arrays, objects, Enums, dates, scalar values) into a canonical structure following RFC 8785 JCS principles before producing a stable cryptographic digest.
Key Features
- RFC 8785 JCS Compliance: Strict float serialization precision handling (
serialize_precision = -1) and key ordering. - Deterministic Array Key Sorting: Alphabetical recursive
ksortfor associative arrays while preserving indexed list sequence integrity (array_is_list). - UTC DateTime Normalization: Automatic conversion of any
DateTimeInterfaceinstance to standardized UTC ISO-8601 strings (Y-m-d\TH:i:s.u\Z). - PHP 8 Enums & Interfaces Support: Built-in support for
BackedEnum,UnitEnum,JsonSerializable,Traversable, and standard Data Transfer Objects (DTOs). - Wildcard Segment Path Exclusion: Dot-notation wildcard pattern matching (e.g.
meta.timestamp,*.nonce,user.id) to ignore volatile attributes during hashing. - Circular Reference Guard: Memory safety via
SplObjectStoragepreventing infinite loops on nested object graphs. - Unicode Canonicalization: Normalization of UTF-8 strings into Unicode Form C (NFC) via
ext-intl. - Flexible Algorithm Choice: Support for any PHP native hashing algorithm (
md5,sha256,xxh128,sha3-512, etc.).
Requirements
- PHP:
^8.4 - PHP Extensions:
ext-json,ext-hash,ext-intl
Installation
Install the package via Composer:
composer require alex-kassel/stable-fingerprint
Quick Start
use AlexKassel\StableFingerprint\StableFingerprint; $fingerprint = new StableFingerprint(); // 1. Basic deterministic payload hashing $payload = [ 'b' => 2, 'a' => 1, 'user' => [ 'email' => 'user@example.com', 'role' => 'admin', ], ]; // Returns deterministic MD5 hash (default algorithm) $hash = $fingerprint->hash($payload);
Regardless of key order, float precision, or PHP environment, the generated hash remains identical.
Advanced Usage
Custom Hashing Algorithms
You can specify any algorithm supported by hash_algos() (e.g. sha256, xxh128, sha3-256):
$sha256Hash = $fingerprint->hash($payload, algo: 'sha256'); $xxhHash = $fingerprint->hash($payload, algo: 'xxh128');
Excluding Volatile & Dynamic Fields
To hash payloads while ignoring transient properties (such as timestamps, request IDs, or nonces), pass dot-notation exclusion patterns as the second argument:
$payload = [ 'order_id' => 1042, 'amount' => 99.99, 'meta' => [ 'created_at' => new DateTimeImmutable(), 'nonce' => 'abc-123-xyz', ], 'history' => [ ['timestamp' => 1700000000, 'status' => 'pending'], ['timestamp' => 1700000500, 'status' => 'completed'], ], ]; // Exclude exact paths or wildcard sub-keys $hash = $fingerprint->hash($payload, excludePaths: [ 'meta.created_at', '*.nonce', 'history.*.timestamp', ]);
Object, Enum & DateTime Handling
StableFingerprint handles complex PHP structures out of the box:
enum UserStatus: string { case Active = 'active'; } class OrderDTO { public function __construct( public int $id, public UserStatus $status, public DateTimeImmutable $createdAt, ) {} } $dto = new OrderDTO( id: 42, status: UserStatus::Active, createdAt: new DateTimeImmutable('2026-08-17 20:00:00', new DateTimeZone('Europe/Berlin')) ); // DateTime objects are automatically converted to UTC ISO-8601 strings // Enums are resolved to their backing scalar values or names $hash = $fingerprint->hash($dto);
Testing
Run the PHPUnit test suite:
composer test
Or execute PHPUnit directly:
vendor/bin/phpunit
License
This package is open-source software licensed under the MIT License.
Created and maintained by Alexander Macenko.