filiprigoir / pswkey
A deterministic base converter using shuffled alphabets derived from salt and optional pepper, with streamkey and one-time-pad support.
Requires
- php: ^8.2
- ext-sodium: *
Requires (Dev)
- phpunit/phpunit: ^11.5
Suggests
- ext-gmp: Used for faster base conversions when available.
README
The conversion layer operates on derived shuffled alphabets.
How to install
composer require filiprigoir/pswkey
Core idea
Alphabet layouts are automatically derived from a seedphrase (salt) and optional keyphrase (pepper), creating isolated conversion spaces between systems, services and/or users.
This affects compatibility between instances, since alphabet layouts differ per setup.
All inputs are validated before decoding against their base alphabet. If this validation fails, decoding could be rejected by:
- early validation (quick check)
- deep validation (during the process)
For BaseX to Base10, Base100 and Base256 conversions, decoding additionally requires extra normalization into the canonical format.
In these cases, decoding follows a deterministic rejection model:
- invalid symbols are rejected
- incompatible alphabets are rejected
- decoding does not produce valid canonical output when the setup does not match
→ See: Normalization
Design philosophy
The repository separates:
- key derivation
- stream management
- data normalization before conversion
- transport representation
into independent layers that can be combined when needed.
This repository consists of three integrated components:
KeyStream()— standalone stream and derived key providerPswKey()— deterministic base conversion driven byKeyStream()OneTimePad()— XOR and digit-based OTP for intermediate steps powered byKeyStream()
Components:
KeyStream()
Handles stream and derived key generation.
Keys are exposed through callbacks only and are cleared as quickly as possible after use.
Built on Libsodium and intended for encryption-related workflows.
PswKey()
Provides deterministic base conversion using shuffled alphabets driven by KeyStream().
Supports:
- system-defined alphabets (always shuffled)
- custom-defined alphabets (with optional alphabet shuffling)
PswKey() uses KeyStream() through dependency injection and is intended for deterministic transport representation, not encryption.
OneTimePad()
Provides XOR and digit-based OTP operations using derived stream keys.
OneTimePad() also depends on KeyStream() and can be used as a preprocessing step before deterministic transport conversion.
Inputs PswKey
Base10, Base100 and Base256 represent canonical input formats.
→ See: Input/output encoding
These formats are used as the source or target for conversion.
Base32, Base58, Base62 and Base64 represent encoded symbol formats used during decoding.
A typical flow is:
->from(100)->to(N) ->from(N)->to(100)
→ See: system-defined
Note: when used for security reason, do not provide structured or human-readable input. Use high-entropy data or an intermediate transformation step instead.
Custom-defined
Custom-defined conversion follows a simple directional model:
customTo(x, y, z)is used for encodecustomFrom(x, y, z)is used for decode
A typical flow is:
->from(100)->customTo($alfabet, X, true) ->customFrom($alfabet, X, true)->to(100)
→ See: Custom-defined
Usage
Common Use
Convert 100,000 random bytes into a deterministic Base32 representation and decode it back to the original byte sequence.
use PswKey\Service\KeyStream; use PswKey\Service\PswKey; //Time-based key $date = new DateTime(); $key = \strtotime($date->format('Y-m-d H:i:s')) . $date->format('u'); //Derived stream provider $keyStream = new KeyStream("Service=README.md : user=536984 : alias=VisalStudio", $key); //Deterministic converter $pswkey = new PswKey($keyStream); //Generate input bytes $randomBytes = random_bytes(100_000); //Conversion to encode $encode = $pswkey->from(256)->to(32)->convert($randomBytes); //Encode status $status = $pswkey->status(); if($status->valid) { //$encode !== null => is also possible //Encoded output must differ from the original input if($encode !== $randomBytes) { echo "RandomBytes is not equal as Encode \n"; } //Conversion to decode $decode = $pswkey->from(32)->to(256)->convert($encode); //Decode status $newStatus = $pswkey->status(); if($newStatus->valid) { //Decoded output must match the original input if($decode === $randomBytes) { echo "Decode is equal as RandomBytes \n"; } } else { //Public/client-safe message echo $status->clientMessage; } } else { //Internal/dev-only message echo $status->internalMessage; }
One-time-pad digit stream conversion with reverse flow.
use PswKey\Service\KeyStream; use PswKey\Service\OneTimePad; //Digit number $originalDigits = "0931024538975689521014785"; // Example workflow (a possible path): // - Base100/Base256 → Base10 // - OneTimePad digit transformation // - Base10 → BaseX conversion // - Reverse flow for decoding //Time-based key $date = new DateTime(); $key = \strtotime($date->format('Y-m-d H:i:s')) . $date->format('u'); //Derived stream provider $keyStream = new KeyStream("Service=README.md : user=5236947 : alias=VisalStudio", $key); //OTP instance $oneTimePad = new OneTimePad($keyStream); //Using any numeric identifier and 8-byte derivation context $encode = $oneTimePad->digit($originalDigits, 5236947, "MyDigits"); if($encode !== $originalDigits) { echo "Encode is not equal as originalDigits \n"; } $status = $oneTimePad->status(); if($status->valid) { //Decode into orginal digits $decode = $oneTimePad->digit($encode, 5236947, "MyDigits"); if($decode === $originalDigits) { echo "Decode is equal as originalDigits \n"; } } else { echo $status->internalMessage; }
Advanced Use Case
Create a derived transport key from a user password and store a secure password hash in the database.
use PswKey\Service\KeyStream; use PswKey\Service\PswKey; use PswKey\Util\Secure\Memezero; try { //User credentials $email = "info@pswkey.com"; $password = "My_PswKeyµ2025!"; //Derived stream provider $keyStream = new KeyStream( "Service=README.md | emailadress={$email}" //Seedphrase can be anything related to your service ); //Attach password as custom key $keyStream->setCustomKey($password); //Generated outputs $transportkey = ""; $passwordHash = ""; $keyStream->byteStream( function($entropyPassword) use (&$transportkey, &$passwordHash, &$keyStream) { $pswKey = new PswKey($keyStream); //Derived password => transport-safe Base32 representation $transportkey = $pswKey->from(256)->to(32)->convert($entropyPassword); //Derived password => password hash for database storage $passwordHash = password_hash($entropyPassword, PASSWORD_DEFAULT); unset($pswKey); }, 64, //Entropy password length "MySecret" //Your service-specific derivation context (most be 8 bytes) ); echo $transportkey . "\n <br>"; echo $passwordHash . "\n <br>"; //To validate: //regenerate the "derived password" from the incoming password //and compare it with the stored database hash //Example password hash: //$2y$10$WiLT89W8P60h9mZgqU3TweFT9EjFB57iv05kzvMsd1hEJ6R53vBJe //Optional "transport key" representation: //(cookies, transport identifiers, login flows, etc.) //Example transport key: //mdvWkllAVlVULqMLBsGWL2BVm4bkz14UgMgUAmUBBZdG7m4kBVGLg4LWRBtsq1Aes4yegyL1WzUggq4lsMbRVveMq //MGRgAlKgm2gBBL7sMzWh2Mqnkdk1tUsl4GG } finally { //Clear sensitive data Memezero::overwriteString($password); unset($keyStream); }
Validate a transport key by decoding it into its derived password representation and comparing it against the stored password hash.
use PswKey\Service\KeyStream; use PswKey\Service\PswKey; use PswKey\Util\Secure\Memezero; try { //User credentials $email = "info@pswkey.com"; //Incoming transport key $transportKey = "gUi0zrccwPdLL88IvwUwB2eMO2UtP0UvvNcITTgOv8cNl0W0o8lgPwQtoNTTlTgAA8l0P2vBl2WtAU" . "twANtOfIpoovpyO2UfOlUTWMUHBNvicA2OfvzfO8UANeoz"; //Derived stream provider $keyStream = new KeyStream("Service=README.md | emailadress={$email}"); $pswKey = new PswKey($keyStream); //Decoded transport key into derived password bytes $entropyPsw = $pswKey->from(32)->to(256)->convert($transportKey); //Validate transport key conversion if($entropyPsw !== null) { //Retrieve stored password hash only after successful transport-key validation $dbHashed = "\$2y\$10\$RGbITslA7qBNeiJ8/Zk3A.t7sOXpOLHqTgqT5.mtSmrGpIL9c75jG"; //Compare derived password against stored hash if (password_verify($entropyPsw, $dbHashed)) { echo 'Password is valid!'; } else { echo 'Invalid password.'; } } } finally { //Clear sensitive data Memezero::overwriteString($entropyPsw); unset($keyStream, $pswKey); }
Core System Documentation
→ complete: Technical documentation