Search by

infocyph / epicrypt

abmmhasan

Modern cryptography, token, password and data-protection toolkit for PHP.

Package info

github.com/infocyph/Epicrypt

pkg:composer/infocyph/epicrypt

Statistics

Installs: 12 646

Dependents: 1

Suggesters: 1

Stars: 4

Open Issues: 0

3.0 2026-09-10 02:49 UTC

README

Security & Standards Packagist Downloads License: MIT Packagist Version Packagist PHP Version GitHub Code Size Documentation

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 community
MIT Licensed
DocumentationSecurityCode of ConductContributing
🗂️ BugFeatureDocumentationQuestionCI failure
🔀 GeneralBug fixFeatureRefactorPerformanceSecurity & reliabilityDocumentationMaintenance