Epicrypt is a capability-first PHP security toolkit and transport-neutral authentication protocol core.
It provides focused security building blocks for:
- Certificate / PKI / key exchange
- Crypto primitives
- Token security (JWT, JWS, JWE, JWK/JWKS, payload and opaque identifiers)
- OAuth 2.1 Authorization Code + PKCE, Client Credentials, refresh rotation, revocation/introspection and DPoP
- OpenID Connect Authorization Code provider mechanics, ID Tokens, UserInfo and discovery
- Generic personal/API tokens with authoritative revocation and abilities
- Password and secret protection
- Integrity verification
- Secure generation
- Data protection workflows
- Security utilities (signed URL, CSRF, reset/action tokens)
Epicrypt owns protocol/security mechanics and public persistence contracts; host applications own HTTP routing, login/consent UI, durable store adapters, rate limiting, audit and application authorization policy.
composer require infocyph/epicrypt- PHP
>=8.4 ext-sodium,ext-openssl,ext-json,ext-hash
Runwire is optional. Install infocyph/runwire:2.1.1 only when forwarding a
host-owned runtime, request and active scope into Epicrypt operations. The
Runwire integration guide
covers explicit context forwarding and cancellation. Pathwise 4.2 is used
for development interoperability tests and is not a production dependency.
See the 3.2.0 release notes for security acceptance changes and the JWKS cache namespace transition.
<?php
declare(strict_types=1);
use Infocyph\Epicrypt\DataProtection\ProtectionOptions;
use Infocyph\Epicrypt\DataProtection\StringProtector;
use Infocyph\Epicrypt\Generate\KeyMaterial\KeyMaterialGenerator;
$key = (new KeyMaterialGenerator())->forAead();
$options = new ProtectionOptions('application-secret');
$protector = StringProtector::create();
$ciphertext = $protector->protect('secret-value', $key, $options);
$plaintext = $protector->unprotect($ciphertext, $key, $options);<?php
declare(strict_types=1);
use Infocyph\Epicrypt\DataProtection\FileProtector;
use Infocyph\Epicrypt\DataProtection\ProtectionOptions;
use Infocyph\Epicrypt\Generate\KeyMaterial\KeyMaterialGenerator;
$key = (new KeyMaterialGenerator())->forSecretStream();
$options = new ProtectionOptions('file-backup');
$files = new FileProtector();
$files->protect('/data/plain.txt', '/data/plain.txt.ep2', $key, $options);
$files->unprotect('/data/plain.txt.ep2', '/data/plain.out.txt', $key, $options);<?php
declare(strict_types=1);
use Infocyph\Epicrypt\DataProtection\ProtectionOptions;
use Infocyph\Epicrypt\DataProtection\StringProtector;
use Infocyph\Epicrypt\Security\KeyPurpose;
use Infocyph\Epicrypt\Security\KeyRing;
use Infocyph\Epicrypt\Security\KeyRingEntry;
use Infocyph\Epicrypt\Security\KeyStatus;
$ring = new KeyRing([
new KeyRingEntry('2026-01', $oldKey, KeyStatus::FALLBACK, KeyPurpose::DATA_PROTECTION, 'xchacha20-poly1305-ietf'),
new KeyRingEntry('2026-05', $newKey, KeyStatus::ACTIVE, KeyPurpose::DATA_PROTECTION, 'xchacha20-poly1305-ietf'),
]);
$options = new ProtectionOptions('rotating-data');
$protector = StringProtector::create();
$ciphertext = $protector->protectWithKeyRing('rotating-data', $ring, $options);
$result = $protector->unprotectWithKeyRing($ciphertext, $ring, $options);<?php
declare(strict_types=1);
use Infocyph\Epicrypt\Password\PasswordHasher;
$hasher = new PasswordHasher();
$hash = $hasher->hashPassword('MyStrongPassword!2026');
$isValid = $hasher->verifyPassword('MyStrongPassword!2026', $hash);
$rehash = $hasher->verifyAndRehash('MyStrongPassword!2026', $hash);<?php
declare(strict_types=1);
use Infocyph\Epicrypt\Generate\KeyMaterial\Enum\KeyMaterialEncoding;
use Infocyph\Epicrypt\Generate\KeyMaterial\KeyMaterialGenerator;
use Infocyph\Epicrypt\Security\CsrfTokenManager;
$csrfSecret = new KeyMaterialGenerator()->forMasterSecret(KeyMaterialEncoding::RAW);
$csrf = new CsrfTokenManager($csrfSecret);
$token = $csrf->issueToken('session-1');
$ok = $csrf->verifyToken('session-1', $token);<?php
declare(strict_types=1);
use Infocyph\Epicrypt\Generate\KeyMaterial\Enum\KeyMaterialEncoding;
use Infocyph\Epicrypt\Generate\KeyMaterial\KeyMaterialGenerator;
use Infocyph\Epicrypt\Security\SignedUrl;
$urlSecret = new KeyMaterialGenerator()->forMasterSecret(KeyMaterialEncoding::RAW);
$signed = new SignedUrl($urlSecret);
$url = $signed->generate('https://example.com/download', ['file' => 'report.pdf'], time() + 300);
$ok = $signed->verify($url);<?php
declare(strict_types=1);
use Infocyph\Epicrypt\Token\Jwt\JwtClaims;
use Infocyph\Epicrypt\Token\Jwt\JwtPolicy;
use Infocyph\Epicrypt\Token\Jwt\SymmetricJwt;
$key = SymmetricJwt::generateBinaryKey();
$claims = JwtClaims::issue(
'issuer-service',
'user-1',
['api'],
600,
['client_id' => 'web-client', 'scope' => 'profile:read'],
);
$token = SymmetricJwt::issuer($key, 'at+jwt')->issue($claims);
$verifier = SymmetricJwt::verifier($key, JwtPolicy::oauthAccessToken('issuer-service', 'api'));
$ok = $verifier->verify($token);OAuthTokenEndpoint owns the supported grant mechanics after the host adapter has parsed the HTTP request and authenticated the client when required.
<?php
declare(strict_types=1);
// Authorization Code + mandatory PKCE S256.
$codeResult = $tokenEndpoint->authorizationCode(
clientId: $clientId,
authentication: $clientAuthentication,
code: $presentedCode,
redirectUri: $presentedRedirectUri,
pkceVerifier: $presentedPkceVerifier,
dpopProof: $presentedDpopProof,
);
if (!$codeResult->successful()) {
throw new RuntimeException($codeResult->error?->code->value ?? 'token exchange failed');
}
// Later: rotate the encrypted refresh artifact. Requested scopes may only narrow.
$refreshResult = $tokenEndpoint->refreshToken(
clientId: $clientId,
authentication: $clientAuthentication,
refreshToken: $presentedRefreshToken,
requestedScopes: ['profile:read'],
dpopProof: $presentedDpopProof,
);
if (!$refreshResult->successful()) {
throw new RuntimeException($refreshResult->error?->code->value ?? 'refresh failed');
}The raw authorization-code JWE, refresh-token JWE and personal-access-token JWT are never persisted by Epicrypt stores. Applications implement the durable store contracts with the documented atomic consume/rotation/revocation guarantees.
The complete OAuth/OIDC lifecycle covers authorization, token grants, resource validation, DPoP, refresh rotation, revocation/introspection and OIDC extension. The authentication standards profile records the supported OAuth 2.1/OIDC behavior and explicit exclusions. The token storage guide defines the durable atomicity requirements.
PersonalAccessTokenManager combines a purpose-isolated pat+jwt credential with authoritative application-owned token state. The configured manager below uses a durable store and dedicated API_PERSONAL_TOKEN_SIGNING keys.
<?php
declare(strict_types=1);
$issue = $personalTokens->issue(
subject: 'user-1',
name: 'deployment-cli',
abilities: ['releases:read', 'releases:deploy'],
);
// Return $issue->token once over TLS. Persist metadata, never the raw JWT.
$validation = $personalTokens->verify($presentedBearerToken);
if (!$validation->accepted() || !$validation->allows('releases:deploy')) {
throw new RuntimeException('Personal access token rejected.');
}
$personalTokens->revoke($issue->record->tokenId, 'user-1');The complete personal/API-token lifecycle covers signing-key setup, issue, verification, exact/wildcard abilities, usage tracking, listing, key rotation, single-token revocation, revokeAll(), and persistence/concurrency requirements.
<?php
declare(strict_types=1);
use Infocyph\Epicrypt\Certificate\CertificateOptions;
use Infocyph\Epicrypt\Certificate\Enum\OpenSslRsaBits;
use Infocyph\Epicrypt\Certificate\KeyPairGenerator;
use Infocyph\Epicrypt\Certificate\OpenSSL\CertificateBuilder;
$pair = KeyPairGenerator::rsa(OpenSslRsaBits::BITS_3072)->generate();
$dn = ['commonName' => 'service.example.test'];
$options = new CertificateOptions(
sanDns: ['service.example.test', 'api.example.test'],
);
$certPem = (new CertificateBuilder())->selfSign($dn, $pair['private'], options: $options);Do not disclose suspected vulnerabilities in a public issue, discussion or pull request. Review the security policy, then use GitHub private vulnerability reporting to contact the maintainers confidentially.
Epicrypt is protected by PHPForge, an automated quality and security gate covering tests, static and taint analysis, dependency auditing, architecture checks, and release readiness. Automated controls reduce risk but do not replace responsible disclosure or manual review.
MIT Licensed
Documentation • Security • Code of Conduct • Contributing
🗂️ Bug • Feature • Documentation • Question • CI failure
🔀 General • Bug fix • Feature • Refactor • Performance • Security & reliability • Documentation • Maintenance