filiprigoir/pswkey

A deterministic base converter using shuffled alphabets derived from salt and optional pepper, with streamkey and one-time-pad support.

Maintainers

Package info

github.com/filiprigoir/PswKey-php

pkg:composer/filiprigoir/pswkey

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-05-20 15:29 UTC

This package is auto-updated.

Last update: 2026-05-21 12:27:43 UTC


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 provider
  • PswKey() — deterministic base conversion driven by KeyStream()
  • OneTimePad() — XOR and digit-based OTP for intermediate steps powered by KeyStream()

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 encode
  • customFrom(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