diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f23533..738ba05 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [[v0.3.0](https://github.com/The-FireHub-Project/Runtime/compare/v0.2.6...v0.3.0)] – 2026-09-16
+
+### Added
+* Add PHP Hash utility wrappers and supporting exceptions by @dieselxxx
+
## [[v0.2.6](https://github.com/The-FireHub-Project/Runtime/compare/v0.2.5...v0.2.6)] – 2026-09-11
### Changed
diff --git a/README.md b/README.md
index 94b5bc6..8fd1745 100644
--- a/README.md
+++ b/README.md
@@ -7,12 +7,12 @@
PHP Runtime Foundation
@@ -22,25 +22,46 @@
/>
-[](https://github.com/The-FireHub-Project/Runtime/actions/workflows/PHPStan.yml)
-[](https://github.com/The-FireHub-Project/Runtime/actions/workflows/PHPUnit.yml)
-[](https://app.codecov.io/gh/The-FireHub-Project/Runtime/tree/develop)
-
-
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+ * The hashing algorithm to use. + *
+ * @param string $data+ * Message to be hashed. + *
+ * @param bool $binary [optional]+ * When set to true, outputs raw binary data. false outputs lowercase hexits. + *
+ * @param array+ * An associative array of hashing options. + *
+ * + * @return non-empty-string Returns a string containing the calculated message digest as lowercase hexits unless + * binary is set to true in which case the raw binary representation of the message digest is returned. + */ + public static function hash (Algorithm $algorithm, string $data, bool $binary = false, array $options = []):string { + + return hash($algorithm->value, $data, $binary, $options); + + } + + /** + * ### Generate a hash value using the contents of a given file + * @since 1.0.0 + * + * @param \FireHub\Runtime\Hash\Algorithm $algorithm+ * The hashing algorithm to use. + *
+ * @param string $data+ * Message to be hashed. + *
+ * @param bool $binary [optional]+ * When set to true, outputs raw binary data. false outputs lowercase hexits. + *
+ * @param array+ * An associative array of hashing options. + *
+ * + * @return non-empty-string|false Returns a string containing the calculated message digest as lowercase hexits + * unless binary is set to true in which case the raw binary representation of the message digest is returned, or + * false on failure. + */ + public static function file (Algorithm $algorithm, string $data, bool $binary = false, array $options = []):string|false { + + return hash_file($algorithm->value, $data, $binary, $options); + + } + + /** + * ### Gets available hashing algorithms + * @since 1.0.0 + * + * @return list+ * The hashing algorithm to use. + *
+ * @param string $data+ * Message to be hashed. + *
+ * @param string $key+ * Shared secret key used for generating the HMAC variant of the message digest + *
+ * @param bool $binary [optional]+ * When set to true, outputs raw binary data. false outputs lowercase hexits. + *
+ * + * @return non-empty-string Returns a string containing the calculated message digest as lowercase hexits unless + * binary is set to true in which case the raw binary representation of the message digest is returned. + */ + public static function hmacHash (Algorithm $algorithm, string $data, #[SensitiveParameter] string $key, bool $binary = false):string { + + return hash_hmac($algorithm->value, $data, $key, $binary); + + } + + /** + * ### Generate a keyed hash value using the HMAC method + * @since 1.0.0 + * + * @param \FireHub\Runtime\Hash\Algorithm $algorithm+ * The hashing algorithm to use. + *
+ * @param string $data+ * Message to be hashed. + *
+ * @param string $key+ * Shared secret key used for generating the HMAC variant of the message digest + *
+ * @param bool $binary [optional]+ * When set to true, outputs raw binary data. false outputs lowercase hexits. + *
+ * + * @return non-empty-string|false Returns a string containing the calculated message digest as lowercase hexits + * unless binary is set to true in which case the raw binary representation of the message digest is returned. + * Returns false if the file filename cannot be read. + */ + public static function hmacFile (Algorithm $algorithm, string $data, #[SensitiveParameter] string $key, bool $binary = false):string|false { + + return hash_hmac_file($algorithm->value, $data, $key, $binary); + + } + + /** + * ### Gets available hashing algorithms suitable for hmac hashing + * @since 1.0.0 + * + * @return list+ * The known string that must be kept secret. + *
+ * @param string $user+ * The user-supplied string to compare against. + *
+ * + * @return bool Returns true when the two strings are equal, false otherwise. + */ + public static function equals (#[SensitiveParameter] string $known, #[SensitiveParameter] string $user):bool { + + return hash_equals($known, $user); + + } + + /** + * ### Generate a HKDF key derivation of a supplied key input + * @since 1.0.0 + * + * @param \FireHub\Runtime\Hash\Algorithm $algorithm+ * The hashing algorithm to use. + *
+ * @param non-empty-string $key+ * Input keying material (raw binary). + *
+ * @param non-negative-int $length [optional]+ * Desired output length in bytes. + * + * Cannot be greater than 255 times the chosen hash function size. + * + * If length is 0, the output length will default to the chosen hash function size. + *
+ * @param string $info [optional]+ * Application/context-specific info string. + *
+ * @param string $salt [optional]+ * Salt to use during derivation. + * + * While optional, adding random salt significantly improves the strength of HKDF. + *
+ * + * @throws \FireHub\Runtime\Exception\EmptyHashKeyException If the key is empty. + * @throws \FireHub\Runtime\Exception\HashByteLengthException If the key is too long. + * + * @return non-empty-string Returns a string containing a raw binary representation of the derived key (also + * known as output keying material - OKM). + */ + public static function hkdf (Algorithm $algorithm, #[SensitiveParameter] string $key, int $length = 0, string $info = '', string $salt = ''):string { + + if ($key === '') throw new EmptyHashKeyException; + + try { + + return hash_hkdf($algorithm->value, $key, $length, $info, $salt); + + } catch (Throwable $e) { + + throw new HashByteLengthException($e->getMessage()); + } + + } + + /** + * ### Generates a PBKDF2 key derivation + * + * Generates a PBKDF2 key derivation using the specified hashing algorithm, password, salt, iteration count, + * and output length. + * @since 1.0.0 + * + * @param \FireHub\Runtime\Hash\Algorithm $algorithm+ * The hashing algorithm to use. + *
+ * @param string $password+ * The password to use for the derivation. + *
+ * @param string $salt+ * The salt to use for the derivation. This value should be generated randomly. + *
+ * @param positive-int $iterations+ * The number of internal iterations to perform for the derivation. + *
+ * @param non-negative-int $length+ * The length of the output string. If binary is true this corresponds to the byte-length of the derived key, if + * binary is false this corresponds to twice the byte-length of the derived key (as every byte of the key is + * returned as two hexits). + * + * If 0 is passed, the entire output of the supplied algorithm is used. + *
+ * @param bool $binary+ * Whether to return raw binary data instead of lowercase hexadecimal output. + *
+ * + * @throws \FireHub\Runtime\Exception\HashIterationException If the number of iterations is less than one. + * @throws \FireHub\Runtime\Exception\HashByteLengthException If the key is too long. + * + * @return non-empty-string Returns a string containing the derived key as lowercase hexits unless binary is set + * to true in which case the raw binary representation of the derived key is returned. + */ + public static function pbkdf2 (Algorithm $algorithm, #[SensitiveParameter] string $password, string $salt, int $iterations, int $length = 0, bool $binary = false):string { + + if ($iterations < 1) throw new HashIterationException("The number of iterations must be greater than zero."); + + try { + + return hash_pbkdf2( + $algorithm->value, + $password, + $salt, + $iterations, + $length, + $binary + ); + + } catch (Throwable $e) { + + throw new HashByteLengthException($e->getMessage()); + } + + } + +} \ No newline at end of file diff --git a/src/Hash/Algorithm.php b/src/Hash/Algorithm.php new file mode 100644 index 0000000..3c95257 --- /dev/null +++ b/src/Hash/Algorithm.php @@ -0,0 +1,474 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Runtime\Tests + */ + +namespace FireHub\Runtime\Hash; + +/** + * ### Hash Algorithm + * + * Defines hashing algorithms supported by the PHP hash extension. + * @since 1.0.0 + */ +enum Algorithm:string { + + + /* * ============================================================ + * MD + * ============================================================ */ + + /** + * ### MD2 message-digest algorithm + * @since 1.0.0 + */ + case MD2 = 'md2'; + + /** + * ### MD4 message-digest algorithm + * @since 1.0.0 + */ + case MD4 = 'md4'; + + /** + * ### MD5 message-digest algorithm + * @since 1.0.0 + */ + case MD5 = 'md5'; + + + /* * ============================================================ + * SHA-1 and SHA-2 + * ============================================================ */ + + /** + * ### SHA-1 secure hash algorithm + * @since 1.0.0 + */ + case SHA1 = 'sha1'; + + /** + * ### SHA-224 secure hash algorithm + * @since 1.0.0 + */ + case SHA224 = 'sha224'; + + /** + * ### SHA-256 secure hash algorithm + * @since 1.0.0 + */ + case SHA256 = 'sha256'; + + /** + * ### SHA-384 secure hash algorithm + * @since 1.0.0 + */ + case SHA384 = 'sha384'; + + /** + * ### SHA-512/224 secure hash algorithm + * @since 1.0.0 + */ + case SHA512_224 = 'sha512/224'; + + /** + * ### SHA-512/256 secure hash algorithm + * @since 1.0.0 + */ + case SHA512_256 = 'sha512/256'; + + /** + * ### SHA-512 secure hash algorithm + * @since 1.0.0 + */ + case SHA512 = 'sha512'; + + + /* * ============================================================ + * SHA-3 + * ============================================================ */ + + /** + * ### SHA3-224 secure hash algorithm + * @since 1.0.0 + */ + case SHA3_224 = 'sha3-224'; + + /** + * ### SHA3-256 secure hash algorithm + * @since 1.0.0 + */ + case SHA3_256 = 'sha3-256'; + + /** + * ### SHA3-384 secure hash algorithm + * @since 1.0.0 + */ + case SHA3_384 = 'sha3-384'; + + /** + * ### SHA3-512 secure hash algorithm + * @since 1.0.0 + */ + case SHA3_512 = 'sha3-512'; + + + /* * ============================================================ + * RIPEMD + * ============================================================ */ + + /** + * ### RIPEMD-128 message-digest algorithm + * @since 1.0.0 + */ + case RIPEMD128 = 'ripemd128'; + + /** + * ### RIPEMD-160 message-digest algorithm + * @since 1.0.0 + */ + case RIPEMD160 = 'ripemd160'; + + /** + * ### RIPEMD-256 message-digest algorithm + * @since 1.0.0 + */ + case RIPEMD256 = 'ripemd256'; + + /** + * ### RIPEMD-320 message-digest algorithm + * @since 1.0.0 + */ + case RIPEMD320 = 'ripemd320'; + + + /* * ============================================================ + * Whirlpool + * ============================================================ */ + + /** + * ### Whirlpool cryptographic hash algorithm + * @since 1.0.0 + */ + case WHIRLPOOL = 'whirlpool'; + + + /* * ============================================================ + * Tiger + * ============================================================ */ + + /** + * ### Tiger-128 hash algorithm with three passes + * @since 1.0.0 + */ + case TIGER128_3 = 'tiger128,3'; + + /** + * ### Tiger-160 hash algorithm with three passes + * @since 1.0.0 + */ + case TIGER160_3 = 'tiger160,3'; + + /** + * ### Tiger-192 hash algorithm with three passes + * @since 1.0.0 + */ + case TIGER192_3 = 'tiger192,3'; + + /** + * ### Tiger-128 hash algorithm with four passes + * @since 1.0.0 + */ + case TIGER128_4 = 'tiger128,4'; + + /** + * ### Tiger-160 hash algorithm with four passes + * @since 1.0.0 + */ + case TIGER160_4 = 'tiger160,4'; + + /** + * ### Tiger-192 hash algorithm with four passes + * @since 1.0.0 + */ + case TIGER192_4 = 'tiger192,4'; + + + /* * ============================================================ + * Snefru + * ============================================================ */ + + /** + * ### Snefru hash algorithm + * @since 1.0.0 + */ + case SNEFRU = 'snefru'; + + /** + * ### Snefru-256 hash algorithm + * @since 1.0.0 + */ + case SNEFRU256 = 'snefru256'; + + + /* * ============================================================ + * GOST + * ============================================================ */ + + /** + * ### GOST hash algorithm + * @since 1.0.0 + */ + case GOST = 'gost'; + + /** + * ### GOST CryptoPro hash algorithm + * @since 1.0.0 + */ + case GOST_CRYPTO = 'gost-crypto'; + + + /* * ============================================================ + * Checksums + * ============================================================ */ + + /** + * ### Adler-32 checksum algorithm + * @since 1.0.0 + */ + case ADLER32 = 'adler32'; + + /** + * ### CRC-32 checksum algorithm + * @since 1.0.0 + */ + case CRC32 = 'crc32'; + + /** + * ### CRC-32B checksum algorithm + * @since 1.0.0 + */ + case CRC32B = 'crc32b'; + + /** + * ### CRC-32C checksum algorithm + * @since 1.0.0 + */ + case CRC32C = 'crc32c'; + + + /* * ============================================================ + * FNV + * ============================================================ */ + + /** + * ### FNV-1 32-bit non-cryptographic hash algorithm + * @since 1.0.0 + */ + case FNV132 = 'fnv132'; + + /** + * ### FNV-1a 32-bit non-cryptographic hash algorithm + * @since 1.0.0 + */ + case FNV1A32 = 'fnv1a32'; + + /** + * ### FNV-1 64-bit non-cryptographic hash algorithm + * @since 1.0.0 + */ + case FNV164 = 'fnv164'; + + /** + * ### FNV-1a 64-bit non-cryptographic hash algorithm + * @since 1.0.0 + */ + case FNV1A64 = 'fnv1a64'; + + + /* * ============================================================ + * Jenkins + * ============================================================ */ + + /** + * ### Jenkins one-at-a-time non-cryptographic hash algorithm + * @since 1.0.0 + */ + case JOAAT = 'joaat'; + + + /* * ============================================================ + * MurmurHash + * ============================================================ */ + + /** + * ### MurmurHash3 32-bit hash algorithm + * @since 1.0.0 + */ + case MURMUR3A = 'murmur3a'; + + /** + * ### MurmurHash3 128-bit x86 hash algorithm + * @since 1.0.0 + */ + case MURMUR3C = 'murmur3c'; + + /** + * ### MurmurHash3 128-bit x64 hash algorithm + * @since 1.0.0 + */ + case MURMUR3F = 'murmur3f'; + + + /* * ============================================================ + * xxHash + * ============================================================ */ + + /** + * ### xxHash 32-bit non-cryptographic hash algorithm + * @since 1.0.0 + */ + case XXH32 = 'xxh32'; + + /** + * ### xxHash 64-bit non-cryptographic hash algorithm + * @since 1.0.0 + */ + case XXH64 = 'xxh64'; + + /** + * ### XXH3 64-bit non-cryptographic hash algorithm + * @since 1.0.0 + */ + case XXH3 = 'xxh3'; + + /** + * ### XXH3 128-bit non-cryptographic hash algorithm + * @since 1.0.0 + */ + case XXH128 = 'xxh128'; + + + /* * ============================================================ + * HAVAL + * ============================================================ */ + + /** + * ### HAVAL-128 hash algorithm with three passes + * @since 1.0.0 + */ + case HAVAL128_3 = 'haval128,3'; + + /** + * ### HAVAL-160 hash algorithm with three passes + * @since 1.0.0 + */ + case HAVAL160_3 = 'haval160,3'; + + /** + * ### HAVAL-192 hash algorithm with three passes + * @since 1.0.0 + */ + case HAVAL192_3 = 'haval192,3'; + + /** + * ### HAVAL-224 hash algorithm with three passes + * @since 1.0.0 + */ + case HAVAL224_3 = 'haval224,3'; + + /** + * ### HAVAL-256 hash algorithm with three passes + * @since 1.0.0 + */ + case HAVAL256_3 = 'haval256,3'; + + /** + * ### HAVAL-128 hash algorithm with four passes + * @since 1.0.0 + */ + case HAVAL128_4 = 'haval128,4'; + + /** + * ### HAVAL-160 hash algorithm with four passes + * @since 1.0.0 + */ + case HAVAL160_4 = 'haval160,4'; + + /** + * ### HAVAL-192 hash algorithm with four passes + * @since 1.0.0 + */ + case HAVAL192_4 = 'haval192,4'; + + /** + * ### HAVAL-224 hash algorithm with four passes + * @since 1.0.0 + */ + case HAVAL224_4 = 'haval224,4'; + + /** + * ### HAVAL-256 hash algorithm with four passes + * @since 1.0.0 + */ + case HAVAL256_4 = 'haval256,4'; + + /** + * ### HAVAL-128 hash algorithm with five passes + * @since 1.0.0 + */ + case HAVAL128_5 = 'haval128,5'; + + /** + * ### HAVAL-160 hash algorithm with five passes + * @since 1.0.0 + */ + case HAVAL160_5 = 'haval160,5'; + + /** + * ### HAVAL-192 hash algorithm with five passes + * @since 1.0.0 + */ + case HAVAL192_5 = 'haval192,5'; + + /** + * ### HAVAL-224 hash algorithm with five passes + * @since 1.0.0 + */ + case HAVAL224_5 = 'haval224,5'; + + /** + * ### HAVAL-256 hash algorithm with five passes + * @since 1.0.0 + */ + case HAVAL256_5 = 'haval256,5'; + + /** + * ### Checks whether the algorithm supports HMAC + * @since 1.0.0 + * + * @return bool True if the algorithm supports HMAC, false otherwise. + */ + public function supportsHmac ():bool { + + return match ($this) { + self::ADLER32, self::CRC32, self::CRC32B, self::CRC32C, + self::FNV132, self::FNV1A32, self::FNV164, self::FNV1A64, + self::JOAAT, + self::MURMUR3A, self::MURMUR3C, self::MURMUR3F, + self::XXH32, self::XXH64, self::XXH3, self::XXH128 + => false, + default => true + }; + + } + +} \ No newline at end of file