Partial Key Verification Library for PHP
This is a port of my other C# library Partial Key Verification Library for Compact Framework into a PHP package.
This library implements Partial Key Verification (PKV). PKV is a cryptographic technique that allows verification of a subset of a key without revealing the entire key, enhancing security and privacy in various applications.
Partial Key Verification
Partial Key Verification (PKV) is a software licensing key technique that breaks a product key into multiple subkeys. With each version of your software, you check a different subset of the subkeys. The beauty of this approach is that a cracker cannot generate a complete keygen. They might be able to generate one for a given version, but it won't work on a different release (assuming you check a different subkey). Another nice feature of PVK is that the key contains an embedded serial number. This allows you to easily check the key against a list of stolen/posted/refunded keys. For more information about the PKV technique, see this blog post by Brandon Staggs.
This version of PKV differs slightly from the one discussed by Brandon Staggs.
Instead of using 8-bit subkeys, I used 32-bit subkeys (just check one key instead
of four). My version also Base-32 (5-bit) encodes the keys to shrink the key size
by 20%, and allows you to specify a different hash algorithm for each subkey.
Installation
You can install the package via composer:
composer require kduma/pkv
Usage
Quick Start
To quick start using the library, you can use the KeyGenerator.Desktop
tool from the C# version.
This tool generates a key and a definition file. You can then use the definition file to generate validation code via CodeGenerator
. The validation code
can be used to validate the key. For more information, see the C# version.
\KDuma\PKV\Generator\PartialKeyGenerator
API
To generate a key, create a PartialKeyGenerator class specifying the checksum and hash functions to use, along with the base values for each subkey. Then call the Generate function, passing it a serial number or a string (such as the customer's e-mail address) to generate a key. You can optionally tell the generator to add a separator between a certain number of characters in the key by setting the Spacing property.
$generator = \KDuma\PKV\Generator\PartialKeyGenerator::fromSingleHash(
new \KDuma\PKV\Checksum\Adler16(),
new \KDuma\PKV\Hash\Jenkins96(0),
[1, 2, 3, 4]
);
$generator->setSpacing(6);
$key = $generator->generateFromString('bob@smith.com');
This will generate the key: QDKZUO-JLLWPY-XWOULC-ONCQIN-5R5X35-ZS3KEQ
. Adler16 is
the checksum function, and Jenkins96 is the hash function. You can have as many
subkeys as you like, but each subkey adds seven more characters to the key.
Static constructors:
\KDuma\PKV\Generator\PartialKeyGenerator::fromKeyDefinition(\KDuma\PKV\Generator\KeyDefinition $def): self
\KDuma\PKV\Generator\KeyDefinition $def
- Serial generation definition
\KDuma\PKV\Generator\PartialKeyGenerator::fromSingleHash(\KDuma\PKV\Checksum\Checksum16Interface $checksum, \KDuma\PKV\Hash\HashInterface $hash, array $baseKeys): self
\KDuma\PKV\Checksum\Checksum16Interface $checksum
- The checksum algorithm to use.\KDuma\PKV\Hash\HashInterface $hash
- The hash algorithm to use.array $baseKeys
- The integer bases keys used to generate the sub keys (one base key for each sub key).
\KDuma\PKV\Generator\PartialKeyGenerator::fromMultipleHashes(\KDuma\PKV\Checksum\Checksum16Interface $checksum, array $hashFunctions, array $baseKeys): self
\KDuma\PKV\Checksum\Checksum16Interface $checksum
- The checksum algorithm to use.array $hashFunctions
- A list of hash functions to use. If the number of hash functions is less than the number `baseKeys, then the functions cycles back to the first function. It is recommended to use several different hash functions.array $baseKeys
- The integers used to generate the sub key.
Methods:
setSpacing(int $spacing): void
- Sets the spacing of the key separator.
int $spacing
- The number of characters to insert a separator after.
generate(int $seed): string
- Generate a key based on the given seed.
int $seed
- The seed value to generate the key from.- Returns a licensing key.
generateFromString(string $seed): string
- Generate a key based on the given string seed. Generate will hash the given string to create an int seed.
string $seed
- The seed value to generate the key from.- Returns a licensing key.
generateMany(int $numberOfKeys, ?\Random\Randomizer $randomizer = null): array
- Generates a set of random keys.
int $numberOfKeys
- The number of keys to generate.?\Random\Randomizer $randomizer
- The random number generator to use.- Returns an array of randomly generate keys.
Key Definitions
For ease of use, there is a prepared format for storing and retrieving key definition in a file.
When saved with .pkvk
file extension, definitions are interchangeable with KeyGenerator.Desktop
tool from the C# version.
There is also a generation tool for generating definitions and verification code for automated pipelines.
$numberOfKeys = 10;
$definition = \KDuma\PKV\Generator\DefinitionGenerator::makeDefinition($numberOfKeys);
$file = 'secret.pkvk';
\KDuma\PKV\Generator\XmlKeyDefinitionSerializer.SaveToFile($file, $definition);
$file = 'secret.pkvk';
$definition = \KDuma\PKV\Generator\XmlKeyDefinitionSerializer::loadFromFile($file);
$file = 'secret.pkvk';
$definition = \KDuma\PKV\Generator\DefinitionGenerator::makeDefinition(5);
$enabledKeys = [1, 2, 5];
$serials = [111111, 22222, 33333, 44444];
$validateUsername = true;
$geerator = new CodeGenerator($definition);
$geerator->setValidateUsername($validateUsername);
$geerator->setBlacklistedSerials($serials);
$geerator->setVerifiedKeys($enabledKeys);
$code = (string) $generator;
\KDuma\PKV\PartialKeyValidator
API
To validate the key, use the PartialKeyValidator static class. Again telling it the checksum and hash functions to use, along with which subkey to check and the base value for that subkey. For example, to check the first subkey of the key generated above:
$isValid = \KDuma\PKV\PartialKeyValidator::validateKey(new \KDuma\PKV\Checksum\Adler16(), new \KDuma\PKV\Hash\OneAtATime(), $key, 0, 1)
Static methods:
PartialKeyValidator::validateKey(\KDuma\PKV\Checksum\Checksum16Interface $checksum, \KDuma\PKV\Hash\HashInterface $hash, string $key, int $subkeyIndex, int $subkeyBase): bool
- Validates the given key. Verifies the checksum and each sub key.
\KDuma\PKV\Checksum\Checksum16Interface $checksum
- The hash algorithm used to compute the sub key.\KDuma\PKV\Hash\HashInterface $hash
- The checksum algorithm used to compute the key's checksum.string $key
- The key to validate.int $subkeyIndex
- The index (zero based) of the sub key to check.int $subkeyBase
- The unsigned base integer used create the sub key.- Returns
true
if the$key
is valid;false
otherwise.
\KDuma\PKV\PartialKeyValidator::validateKeyWithSeedString(\KDuma\PKV\Checksum\Checksum16Interface $checksum, \KDuma\PKV\Hash\HashInterface $hash, string $key, int $subkeyIndex, int $subkeyBase, string $seedString): bool
- Validates the given key. Verifies the given string seed matches the seed embedded in the key, verifies the checksum and each sub key. This version is useful if the seed used to generate a key was derived from some user information such as the user's name, e-mail, etc.
\KDuma\PKV\Checksum\Checksum16Interface $checksum
- The hash algorithm used to compute the sub key.\KDuma\PKV\Hash\HashInterface $hash
- The checksum algorithm used to compute the key's checksum.string $key
- The key to validate.int $subkeyIndex
- The index (zero based) of the sub key to check.int $subkeyBase
- The unsigned base integer used create the sub key.string $seedString
- The string used to generate the seed for the key.- Returns
true
if the$key
is valid;false
otherwise.
\KDuma\PKV\PartialKeyValidator::getSerialNumberFromKey(string $key): int
- Extracts the serial number from a key.
string $key
- The key to extract the serial number from.- Returns the serial number embedded in the key.
\KDuma\PKV\PartialKeyValidator::getSerialNumberFromSeed(string $seed): int
- Converts a string seed into a serial number (uint seed).
string $seed
- The string seed to convert.- Returns the string seed converted to a serial number.
Sample validation code
private static function validateKey(string $key): bool {
$seed = \KDuma\PKV\PartialKeyValidator::getSerialNumberFromKey($key);
$blacklist = [1518008798, 42];
if (in_array($seed, $blacklist, true))
return false;
// Validation for key with index 1
if (!\KDuma\PKV\PartialKeyValidator::validateKey(new \KDuma\PKV\Checksum\Adler16(), new \KDuma\PKV\Hash\OneAtATime(), $key, 1, 766109221))
return false;
// Validation for key with index 4
if (!\KDuma\PKV\PartialKeyValidator::validateKey(new \KDuma\PKV\Checksum\Adler16(), new \KDuma\PKV\Hash\Fnv1a(), $key, 4, 4072442218))
return false;
return true;
}
\KDuma\PKV\Checksum\Checksum16Interface
) Available Checksum Algorithms (
\KDuma\PKV\Checksum\Adler16
\KDuma\PKV\Checksum\Crc16
\KDuma\PKV\Checksum\CrcCcitt
\KDuma\PKV\Hash\HashInterface
) Available Hashing Algorithms (
\KDuma\PKV\Hash\Crc32
\KDuma\PKV\Hash\Fnv1a
\KDuma\PKV\Hash\GeneralizedCrc
\KDuma\PKV\Hash\Jenkins06
\KDuma\PKV\Hash\Jenkins96
\KDuma\PKV\Hash\OneAtATime
\KDuma\PKV\Hash\SuperFast