mkinyua53/unhash

Reverse-lookup (unhash) phone numbers from SHA-256 hashes, with a local lookup table, bulk hash generation, and a configurable HTTP API fallback, for Laravel.

Maintainers

Package info

gitlab.com/MKinyua53/unhash

Issues

pkg:composer/mkinyua53/unhash

Transparency log

Statistics

Installs: 53

Dependents: 0

Suggesters: 0

Stars: 0

v1.8.0 2026-08-07 23:36 UTC

This package is auto-updated.

Last update: 2026-08-07 20:40:15 UTC


README

pipeline status coverage report Latest Release

Reverse-lookup ("unhash") phone numbers from SHA-256 hashes in Laravel, backed by a local lookup table with a configurable HTTP API fallback, plus a console command to bulk-generate the lookup table for a set of MSISDN prefixes.

Numbers

Installation

composer require mkinyua53/unhash

The service provider and Unhash facade are registered automatically via Laravel package discovery.

Publish the config and migration if you want to customize them:

php artisan vendor:publish --tag=unhash-config
php artisan vendor:publish --tag=unhash-migrations
php artisan migrate

Configuration

See config/unhash.php. Key options (all overridable via .env):

Env varPurpose
UNHASH_TABLETable name for the local hash lookup (default hashed_numbers)
UNHASH_PREFIXES_TABLETable name for the MSISDN prefixes list (default unhash_prefixes)
UNHASH_LOG_CHANNELLog channel used during bulk hash generation (default unhash)
UNHASH_LOOKUP_LOG_CHANNELLog channel used during getNumber() lookups (default hashes)
UNHASH_LOGGING_ENABLEEnable/disable all package logging (default false)
UNHASH_QUEUE_CONNECTIONQueue connection used for both hash-generation jobs (default: the application's default connection)
UNHASH_PREFIX_JOB_QUEUEQueue name for GenerateHashesForPrefixJob (default: the connection's default queue)
UNHASH_BATCH_JOB_QUEUEQueue name for GenerateHashesForPrefixBatchJob (default: the connection's default queue)
UNHASH_API_ENABLEDEnable/disable the remote API fallback (default true)
UNHASH_API_URLRemote endpoint that decodes a hash into a phone number
UNHASH_API_KEYAPI key sent with the remote request
UNHASH_API_KEY_HEADERHeader name for the API key (default x-api-key)

The remote API is expected to accept POST {url} with JSON body {"hash": "..."} and respond with JSON containing a phone key.

Usage

use Mkinyua53\Unhash\Facades\Unhash;

$phoneNumber = Unhash::getNumber($hash);

Resolution order: local hashed_numbers table lookup, then the remote API fallback (if configured).

Managing prefixes

The set of MSISDN prefixes hashes are generated for lives in the unhash_prefixes database table, not in config/unhash.php. The prefixes array in the config file is only used to seed that table the first time its migration runs — after that, the table is the source of truth, so a consuming app can extend the list without publishing/editing the package config:

use Mkinyua53\Unhash\Facades\Unhash;

Unhash::prefixes();          // all prefixes currently tracked, e.g. ['0110', '0700', ...]
Unhash::addPrefix('0680');   // add one (must match /^0\d{3}$/); returns false if malformed or already present
Unhash::removePrefix('0680'); // remove one; returns whether a row was removed

php artisan unhash:generate-hashes (with no prefix argument) reads from this table, so newly added prefixes are picked up automatically.

Generating the lookup table

php artisan unhash:generate-hashes            # all configured prefixes
php artisan unhash:generate-hashes 0722       # a single prefix
php artisan unhash:generate-hashes --delete-duplicates
php artisan unhash:generate-hashes --delete-all-duplicates
php artisan unhash:generate-hashes 0722 --delete-duplicates  # scoped to a single prefix

Each prefix dispatches a Mkinyua53\Unhash\Jobs\GenerateHashesForPrefixJob queued job, which in turn dispatches one Mkinyua53\Unhash\Jobs\GenerateHashesForPrefixBatchJob per 10,000-number batch (100 per prefix by default). Both jobs are ShouldBeUnique — per prefix, and per prefix+batch respectively — so batches can be processed in parallel across queue workers and a failed/retried batch doesn't require re-scanning the whole prefix. Both jobs also check hash_prefix_counts first and skip immediately once a prefix has reached 1,000,000 generated hashes, so re-dispatching an already-complete prefix (or a stray retried batch) is cheap regardless of how large hashed_numbers has grown.

--delete-duplicates/--delete-all-duplicates scan for duplicate phone_number rows one prefix at a time (all prefixes in unhash_prefixes, or just the one given as an argument), skipping any prefix range with no rows at all, instead of scanning the whole hashed_numbers table in one pass. A prefix removed from unhash_prefixes after being generated won't be covered by this scan.

Which connection and queue each job runs on is configurable via config('unhash.queue') (UNHASH_QUEUE_CONNECTION, UNHASH_PREFIX_JOB_QUEUE, UNHASH_BATCH_JOB_QUEUE) — useful for routing the lighter-weight prefix-dispatch job and the heavier batch-processing job to different queues/workers.

Database support

The package is database-agnostic: index checks use Schema::hasIndex(), duplicate cleanup uses the query builder (subqueries, whereIn/whereNotIn), and bulk inserts use insertOrIgnore(). This has been tested against SQLite and MySQL and should also work on PostgreSQL. Requires Laravel 11+ for Schema::hasIndex()/schema introspection.

insertOrIgnore() is not supported on SQL Server — the unhash:generate-hashes command will not work there, though the lookup path (Unhash::getNumber()) is unaffected.

Testing

composer install
vendor/bin/pest