infocyph / epicrypt
Modern cryptography, token, password and data-protection toolkit for PHP.
Requires
- php: >=8.4
- ext-hash: *
- ext-json: *
- ext-openssl: *
- ext-sodium: *
- phpseclib/phpseclib: ^4.0.1
- psr/clock: ^1.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/simple-cache: ^3.0
Requires (Dev)
- infocyph/pathwise: ^4.0
- infocyph/phpforge: dev-main@dev
- nyholm/psr7: ^1.8
- php-http/mock-client: ^1.6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-10 04:02:57 UTC
README
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.
Installation
composer require infocyph/epicrypt
Requirements
- PHP
>=8.4 ext-sodium,ext-openssl,ext-json,ext-hash
Usage Examples
Encrypt and decrypt a string
<?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);
Encrypt and decrypt a file
<?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);
Rotate keys with a key ring
<?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);
Hash, verify and rehash password
<?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);
Issue and verify CSRF token
<?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);
Generate and verify signed URL
<?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);
Issue and verify JWT (HS512)
<?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);
Exchange and rotate OAuth credentials
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.
Issue, authorize, and revoke a personal/API token
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.
Generate certificate with SAN
<?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);
Security
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.
Made with ❤️ for the PHP communityMIT Licensed
Documentation • Security • Code of Conduct • Contributing
🗂️ Bug • Feature • Documentation • Question • CI failure
🔀 General • Bug fix • Feature • Refactor • Performance • Security & reliability • Documentation • Maintenance