3ncr / tokencrypt-php
Implementation of 3ncr.org V1 token(strings) encryption standard.
Requires
- php: >=8.1
- ext-json: *
- ext-openssl: *
- ext-sodium: *
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- phpunit/phpunit: ^10.5
This package is auto-updated.
Last update: 2026-04-26 12:14:47 UTC
README
3ncr.org is a tiny encrypted-string format for config values, tokens, and app secrets — encrypt a UTF-8 string in one language, store it as a recognizable value, and decrypt it in another. v1 uses AES-256-GCM for authenticated encryption with a 12-byte random IV:
3ncr.org/1#<base64(iv[12] || ciphertext || tag[16])>
Encrypted values look like
3ncr.org/1#pHRufQld0SajqjHx+FmLMcORfNQi1d674ziOPpG52hqW5+0zfJD91hjXsBsvULVtB017mEghGy3Ohj+GgQY5MQ.
This is the PHP 8.1+ implementation. See github.com/3ncr for implementations in other languages (Go, Node.js, Python, Rust, Java, C#, Ruby).
Install
composer require 3ncr/tokencrypt-php
Requires PHP 8.1+ with ext-openssl, ext-json, and ext-sodium (the last is
used for Argon2id key derivation).
Usage
Pick a constructor based on the entropy of your secret — see the 3ncr.org v1 KDF guidance for the canonical recommendation.
Recommended: raw 32-byte key (high-entropy secrets)
If you already have a 32-byte AES-256 key (random key, API token hashed to 32 bytes via SHA3-256, etc.), skip the KDF and pass it directly.
$key = random_bytes(32); // or: load from env / secret store $tokenCrypt = \ThreeEncr\TokenCrypt::fromRawKey($key);
Recommended: Argon2id (passwords / low-entropy secrets)
For passwords or passphrases, use fromArgon2id. It uses the parameters
recommended by the 3ncr.org v1 spec
(m=19456 KiB, t=2, p=1). The salt must be exactly 16 bytes (libsodium's
crypto_pwhash requirement, which matches the spec's "at least 16 random
bytes" recommendation at its minimum length and is interoperable with the Go
and Node implementations when the same salt is used).
$tokenCrypt = \ThreeEncr\TokenCrypt::fromArgon2id($password, $salt);
Legacy: PBKDF2-SHA3 (existing data only)
The original (secret, salt, iterations) constructor is kept for backward
compatibility with data encrypted by earlier versions. It is deprecated —
prefer fromRawKey or fromArgon2id for new code.
$tokenCrypt = new \ThreeEncr\TokenCrypt($secret, $salt, 1000);
$secret and $salt are inputs to PBKDF2-SHA3 (technically one is the key,
the other is the salt, but you need to store them both somewhere, preferably
in different places). 1000 is the number of PBKDF2 rounds.
Encrypt / decrypt
After constructing an instance, use encrypt3ncr and decrypt3ncr:
$token = '08019215-B205-4416-B2FB-132962F9952F'; // your secret you want to encrypt $encryptedSecretToken = $tokenCrypt->encrypt3ncr($token); // $encryptedSecretToken === '3ncr.org/1#pHRufQld0SajqjHx+FmLMcORfNQi1d674ziOPpG52hqW5+0zfJD91hjXsBsvULVtB017mEghGy3Ohj+GgQY5MQ' // ... some time later in another context ... $decryptedSecretToken = $tokenCrypt->decrypt3ncr($encryptedSecretToken); // $decryptedSecretToken === '08019215-B205-4416-B2FB-132962F9952F'
decrypt3ncr returns the input unchanged when it does not start with the
3ncr.org/1# header, so it is safe to route every configuration value through
it regardless of whether it was encrypted.
For JSON config files you can decrypt all 3ncr-encoded values in one pass:
$encConfig = json_decode(file_get_contents('config.json'), true); $config = $tokenCrypt->decrypt3ncrArray($encConfig);
License
MIT — see LICENSE.