t2pcorp/libcrypto

A PHP library for cryptographic operations with KMS integration, Caching(local,redis) and Secret manager.

Installs: 105

Dependents: 0

Suggesters: 0

Security: 0

pkg:composer/t2pcorp/libcrypto

v1.6.8 2026-02-15 16:39 UTC

README

PHP implementation of the LibCrypto library, converted from the original Golang version. This library provides secure encryption/decryption functionality with support for multiple key versions, caching, and AWS integration.

Features

  • AES-256-GCM Encryption: Secure encryption using industry-standard algorithms
  • Key Versioning: Support for multiple key versions with automatic rotation
  • Multi-level Caching: Local and Redis caching for improved performance
  • AWS Integration: Support for AWS Secrets Manager and KMS
  • Mock Support: Complete mock implementations for testing and development
  • Thread-Safe: Singleton pattern ensures thread safety

Directory Structure

PHP/
├── Cache/
│   ├── LocalCache/         # In-memory caching implementation
│   └── RedisCache/         # Redis-based caching implementation
├── Encryption/
│   └── LibKMS/             # AWS KMS encryption services
├── LibAWS/                 # AWS configuration and utilities
├── LibConfig/              # Configuration management
│   └── MockConfig/         # Mock configuration for testing
├── Stores/
│   ├── SecretManager/      # AWS Secrets Manager integration
│   └── MockSecretManager/  # Mock secret store for testing
├── Types/                  # Interface definitions and data structures
├── Utils/                  # Utility functions and helpers
├── tests/                  # Comprehensive test suite
├── examples.php            # Usage examples
├── LibCrypto.php           # Main library entry point
└── run.sh                  # Test runner script

Installation

Prerequisites

  • PHP 8.0 or higher
  • Required PHP extensions:
    • openssl
    • json
    • mbstring
  • Composer (for dependency management)

Dependencies

Install required dependencies using Composer:

composer install

AWS Dependencies (Optional)

For AWS integration, you'll need:

  • AWS SDK for PHP
  • Predis (for Redis support)

These are automatically installed via Composer.

Quick Start

Basic Usage with Mock Store

<?php
require_once 'vendor/autoload.php';

use LibCrypto\CryptoManager;
use LibCrypto\Types\CryptoConfig;
use LibCrypto\Stores\MockSecretManager\KeyStore as MockKeyStore;

// Create configuration
$config = new CryptoConfig([
    'LIBC_SM_NAME' => 'T2P-LIBC-DEK-poc',
    'LIBC_SM_REGION' => 'ap-southeat-7'
]);

// Use mock store for testing
$mockStore = new MockKeyStore($config);

// Get CryptoManager instance
$result = CryptoManager::getCryptoManager($config, $mockStore);
if ($result[1] !== null) {
    throw new Exception('Failed to initialize: ' . $result[1]);
}

$cryptoManager = $result[0];

// Encrypt data
$plaintext = "Hello, World!";
$encryptResult = $cryptoManager->encrypt($plaintext);
if ($encryptResult[1] !== null) {
    throw new Exception('Encryption failed: ' . $encryptResult[1]);
}

$encrypted = $encryptResult[0];
echo "Encrypted: " . $encrypted . "\n";

// Decrypt data
$decryptResult = $cryptoManager->decrypt($encrypted);
if ($decryptResult[1] !== null) {
    throw new Exception('Decryption failed: ' . $decryptResult[1]);
}

$decrypted = $decryptResult[0];
echo "Decrypted: " . $decrypted . "\n";

Usage with AWS Integration

<?php
use LibCrypto\Stores\SecretManager\KeyStore as RealKeyStore;

// Configure with AWS credentials
$config = new CryptoConfig([
    'LIBC_SM_NAME' => 'your-secret-name',
    'LIBC_SM_ID' => 'your-aws-access-key',
    'LIBC_SM_SECRET' => 'your-aws-secret-key',
    'LIBC_SM_REGION' => 'ap-southeat-7',
    'LIBC_CACHE_REDISHOST' => 'your-redis-host',
    'LIBC_CACHE_REDISPORT' => '6379'
]);

$realStore = new RealKeyStore($config);
$result = CryptoManager::getCryptoManager($config, $realStore);
// ... use as above

Configuration

Environment Variables

The library supports configuration via environment variables:

  • LIBC_SM_NAME: AWS Secrets Manager secret name
  • LIBC_SM_ID: AWS Access Key ID
  • LIBC_SM_SECRET: AWS Secret Access Key
  • LIBC_SM_ROLE: AWS IAM Role ARN (optional)
  • LIBC_SM_REGION: AWS Region
  • LIBC_CACHE_REDISHOST: Redis host
  • LIBC_CACHE_REDISPORT: Redis port
  • LIBC_CACHE_REDISPASS: Redis password
  • LIBC_CACHE_REDISDB: Redis database number
  • LIBC_CACHE_REDISTLS: Enable Redis TLS (true/false)

Configuration Object

$config = new CryptoConfig([
    'LIBC_SM_NAME' => 'my-secret',
    'LIBC_SM_REGION' => 'us-west-2',
    // ... other options
]);

API Reference

CryptoManager

Main class for encryption/decryption operations.

Methods

  • getCryptoManager(CryptoConfig $config, StoreInterface $store = null, ConfigInterface $libConfig = null): array

    • Returns [CryptoManager instance, error]
    • Singleton pattern - returns the same instance across calls
  • encrypt(string $plaintext): array

    • Returns [encrypted_string, error]
    • Encrypts plaintext using the active key
  • decrypt(string $encrypted): array

    • Returns [decrypted_string, error]
    • Decrypts encrypted data
  • getCipherMeta(string $encrypted): array

    • Returns [CipherMeta object, error]
    • Gets metadata about encrypted data
  • clearCache(): ?string

    • Returns error message or null
    • Clears all cached keys and forces refresh

CipherMeta

Contains metadata about encrypted data:

class CipherMeta {
    public string $version;     // Key version used
    public string $algorithm;   // Encryption algorithm
    public bool $isSupport;     // Whether format is supported
}

Testing

Run All Tests

./run.sh

Run Specific Tests

# Basic examples
./run.sh 1

# Comprehensive tests
./run.sh 2

# Performance benchmark
./run.sh 3

# Installation check
./run.sh 4

Manual Testing

# Run examples
php examples.php

# Run test suite
php tests/LibCryptoTest.php

Performance

The library includes performance benchmarking:

use LibCrypto\Tests\PerformanceTest;

$perfTest = new PerformanceTest($cryptoManager);
$perfTest->benchmarkEncryption(1000); // Run 1000 cycles

Typical performance on modern hardware:

  • Encryption: ~2-5ms per operation
  • Decryption: ~2-5ms per operation
  • Throughput: ~200-500 operations/second

Error Handling

The library uses a Go-style error handling pattern where methods return arrays:

  • [result, null] on success
  • [null, error_message] on failure
$result = $cryptoManager->encrypt($data);
if ($result[1] !== null) {
    // Handle error
    echo "Error: " . $result[1];
} else {
    // Use result
    $encrypted = $result[0];
}

Caching

Local Cache

In-memory caching for single-process applications:

  • Fast access
  • No external dependencies
  • Lost on process restart

Redis Cache

Distributed caching for multi-process/multi-server applications:

  • Persistent across restarts
  • Shared between processes
  • Requires Redis server

Cache Timeout

  • Default timeout: 20-24 hours (randomized)
  • Automatic refresh on timeout
  • Manual refresh via clearCache()

Security Considerations

  1. Key Storage: Keys are stored securely in memory when possible
  2. Network Security: Use TLS for Redis connections in production
  3. AWS Security: Use IAM roles instead of hardcoded credentials when possible
  4. Memory Safety: Sensitive data is cleared from memory when possible

AWS Credential Caching

When using AWS IAM role assumption (LIBC_SM_ROLE), the library automatically caches credentials:

  • Automatic Caching: Temporary credentials are cached in memory by AWS SDK
  • Credential Duration: 1 hour (3600 seconds)
  • Auto-Refresh: Credentials are automatically refreshed before expiration
  • Rate Limiting Prevention: Eliminates excessive AssumeRole API calls
  • No Manual Management: Caching and refresh are handled transparently by the SDK

This significantly improves performance and prevents AWS API rate limiting when the same role is used repeatedly.

PHP-FPM Deployment Considerations

Singleton Verification

Want to verify the singleton is working correctly?

Run the verification script:

php examples/verify-singleton-worker.php

This demonstrates that the singleton persists across requests and provides 100-1500x performance improvement.

📖 Full Documentation: See SINGLETON-VERIFICATION.md for complete verification guide, performance benchmarks, and troubleshooting.

Understanding PHP-FPM Worker Behavior

When running with PHP-FPM, it's important to understand how the library behaves:

Singleton Pattern Persistence with Configuration Change Detection

  • CryptoManager uses a singleton pattern - getInstance() returns the same instance for the same configuration
  • PHP-FPM workers persist - Unlike CGI, workers handle multiple requests
  • Static variables survive - The singleton instance lives for the worker's lifetime
  • Configuration change detection - Automatically detects when AWS credentials or configuration changes between requests
  • Automatic re-initialization - Creates a new instance if configuration differs from cached instance
  • Impact:
    • First request initializes the CryptoManager
    • Subsequent requests with the same config reuse the instance (optimal performance)
    • Requests with different config get a new instance (supports multi-tenant scenarios)

AWS Client Caching (Optimized)

The library implements two-level caching for AWS clients:

  1. Client Instance Caching (KeyStore level):

    • AWS Secrets Manager client is cached as a static variable
    • Reused across multiple getSecretConfig() calls in the same worker
    • Automatically invalidated if AWS configuration changes
  2. Credential Caching (AWS SDK level):

    • AssumeRoleCredentialProvider caches temporary credentials
    • Credentials valid for 1 hour
    • Auto-refresh before expiration

Benefits in PHP-FPM

  • Reduced AWS API Calls: AssumeRole called once per hour per worker (not per request)
  • Better Performance: Client creation overhead eliminated for subsequent requests
  • Cost Savings: Fewer AWS API calls = lower costs
  • Rate Limit Protection: Prevents hitting AWS AssumeRole rate limits

Best Practices

  1. Configure Appropriate Worker Count:

    ; php-fpm pool configuration
    pm = dynamic
    pm.max_children = 50
    pm.start_servers = 5
    pm.min_spare_servers = 5
    pm.max_spare_servers = 10
    pm.max_requests = 500  ; Recycle workers periodically
    
  2. Worker Recycling:

    • Set pm.max_requests to recycle workers periodically
    • Prevents memory leaks and refreshes cached clients
    • Recommended: 500-1000 requests per worker
  3. Multi-Tenant Considerations:

    • Now Supported: Configuration change detection automatically handles different credentials
    • When getInstance() is called with different credentials, a new instance is created
    • Previous instance is replaced (one active config per worker at a time)
    • Best Practices:
      • For high-performance single-tenant: Use environment variables (automatic caching)
      • For multi-tenant: Pass different configs to getInstance() per request (automatic switching)
      • For strict tenant isolation: Use separate PHP-FPM pools per tenant (most secure)
  4. Health Monitoring:

    // Check if CryptoManager is initialized
    $result = CryptoManager::getInstance();
    if ($result[1] !== null) {
        error_log("CryptoManager initialization failed: " . $result[1]);
    }
    
  5. Graceful Restarts:

    # Reload PHP-FPM gracefully to pick up new credentials
    systemctl reload php-fpm
    

Clearing Caches

To clear various caches (for testing or credential rotation):

// Option 1: Clear AWS client cache only (keeps CryptoManager instance)
\LibCrypto\Stores\SecretManager\KeyStore::clearClientCache();

// Option 2: Clear CryptoManager's key cache (forces re-fetch from AWS)
[$cryptoManager, $error] = CryptoManager::getInstance();
if ($error === null) {
    $cryptoManager->clearCache();
}

// Option 3: Clear CryptoManager singleton instance (forces full re-initialization)
\LibCrypto\CryptoManager::clearInstance();

// Option 4: Clear everything (complete reset)
\LibCrypto\CryptoManager::clearInstance();
\LibCrypto\Stores\SecretManager\KeyStore::clearClientCache();

When to use each option:

  • Option 1: When AWS credentials change but CryptoManager config is the same
  • Option 2: When secrets in AWS Secrets Manager are updated
  • Option 3: When you want to force CryptoManager re-initialization (e.g., testing)
  • Option 4: Complete reset, useful for credential rotation or major config changes

Differences from Go Version

Skipped Components

As requested, the following components were not converted:

  • limitqueue package (timeout functionality)
  • Timeout/refresh queue functionality in config

PHP-Specific Adaptations

  1. Error Handling: Go-style [result, error] pattern
  2. Interfaces: PHP interfaces instead of Go interfaces
  3. Memory Management: PHP garbage collection instead of manual memory management
  4. Concurrency: Single-threaded execution model
  5. Dependencies: Composer instead of Go modules

Troubleshooting

Common Issues

  1. Missing PHP Extensions

    # Install required extensions (Ubuntu/Debian)
    sudo apt-get install php-openssl php-json php-mbstring
    
  2. AWS Credentials Not Found

    • Set environment variables
    • Configure AWS credentials file
    • Use IAM roles on EC2
  3. Redis Connection Failed

    • Check Redis server is running
    • Verify connection parameters
    • Check firewall settings
  4. Memory Issues

    • Increase PHP memory limit: ini_set('memory_limit', '512M');
    • Use Redis cache for better memory management

Debug Mode

Enable debug output:

// Add to your code for debugging
error_reporting(E_ALL);
ini_set('display_errors', 1);

Contributing

  1. Follow PSR-12 coding standards
  2. Write tests for new functionality
  3. Update documentation
  4. Run the test suite before submitting

License

This PHP conversion maintains the same license as the original Go implementation.

Support

For issues specific to the PHP conversion, please check:

  1. PHP version compatibility (8.0+)
  2. Required extensions are installed
  3. Dependencies are up to date
  4. Configuration is correct

For general LibCrypto issues, refer to the original Go documentation.

LibCrypto PHP Library

A PHP library for cryptographic operations with KMS integration, Caching (local, Redis), and Secret Manager support.

Installation

You can install this library via Composer:

composer require t2pcorp/libcrypto

Usage

Please see example.php for basic usage.

<?php

require_once __DIR__ . '/vendor/autoload.php';

use LibCrypto\CryptoManager;

try {
    // Initialize CryptoManager (assumes environment variables are set for configuration)
    [$cryptoManager, $error] = CryptoManager::getCryptoManager();
    if ($error !== null) {
        throw new \Exception("Failed to initialize CryptoManager: " . $error);
    }

    $plaintext = "Hello, secure world!";
    [$encrypted, $encError] = $cryptoManager->encrypt($plaintext);
    if ($encError !== null) {
        throw new \Exception("Encryption failed: " . $encError);
    }

    echo "Encrypted: " . $encrypted . "\n";

    [$decrypted, $decError] = $cryptoManager->decrypt($encrypted);
    if ($decError !== null) {
        throw new \Exception("Decryption failed: " . $decError);
    }

    echo "Decrypted: " . $decrypted . "\n";

} catch (\Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

Publishing to Packagist

To make this library publicly available via Composer, follow these steps to publish it to Packagist.org:

  1. Ensure your code is in a public Git repository:

    • Packagist tracks your Git repository (e.g., on GitHub, GitLab, Bitbucket).
    • Make sure your library's code, including the composer.json file and the src/ directory, is committed and pushed to a public repository.
  2. Sign up/Log in to Packagist:

    • Go to Packagist.org.
    • Sign up or log in. Using your GitHub account is often the easiest if your code is hosted there.
  3. Submit Your Package:

    • Once logged in, click the "Submit" button.
    • In the "Repository URL (Git/Svn/Hg)" field, paste the public URL of your Git repository (e.g., https://github.com/your-username/libcrypto.git).
    • Click "Check". Packagist will attempt to read your composer.json.
    • If successful and the package name t2pcorp/libcrypto is available, confirm the submission.
  4. Set up Automatic Updates (Webhook):

    • After submission, Packagist will show your package page. It's highly recommended to set up automatic updates.
    • Packagist will provide a webhook URL.
    • Go to your Git repository settings (e.g., GitHub: Settings -> Webhooks -> Add webhook).
    • Paste the Payload URL from Packagist, set Content type to application/json, and select the "Pushes" or "Push event" trigger.
  5. Versioning with Git Tags:

    • Composer relies on Git tags for versioning. To release a new version (e.g., v1.0.0):
      1. Commit all changes: git commit -am "Release v1.0.0"
      2. Create a Git tag: git tag v1.0.0
      3. Push the tag: git push origin v1.0.0 (or git push --tags)
    • Packagist will be notified (via webhook) and make the new version available.

🧩 4. Submit Your Package to Packagist A. On Packagist: Go to: https://packagist.org/packages/submit Paste your GitLab repository URL (e.g.): https://gitlab.com/t2plib/libcrypto.git If it’s public, it should just work.

🔄 5. Set Up Auto-Update (Recommended) To allow Packagist to auto-update when you push a new tag or commit:

Option 1: Use GitLab Webhook Go to your GitLab project → Settings → Webhooks

Add this webhook URL:

perl Copy Edit https://packagist.org/api/update-package?username=YOUR_USERNAME&apiToken=YOUR_API_TOKEN You can get apiToken from your Packagist API page

Set it to trigger on Push events.

This README.md was generated based on the composer.json for t2pcorp/libcrypto.


#if have redis if use same redis host settings in CICD-PIPELINE
export LIBC_CACHE_REDISHOST="localhost"
export LIBC_CACHE_REDISPORT="6379"
export LIBC_CACHE_REDISUSER=
export LIBC_CACHE_REDISPASS=""
export LIBC_CACHE_REDISDB="0"
export LIBC_CACHE_REDISTLS="DISABLED"
export LIBC_CACHE_REDISCERT=

#SET by CICD-Pipeline
export LIBC_SM_NAME=T2P-LIBC-DEK-poc
export LIBC_SM_ID=""
export LIBC_SM_SECRET=""
export LIBC_SM_ROLE=""
export LIBC_SM_REGION="ap-southeast-7"


export AWS_DEFAULT_REGION=ap-southeast-7
export AWS_REGION=ap-southeast-1
export AWS_SECRET_ACCESS_KEY=SSO
export AWS_ACCESS_KEY_ID=SSO

export LOG_LEVEL=debug



Local Wrapping Key (LWK) System

LibCrypto uses a deterministic HKDF-based Local Wrapping Key system for per-instance key management. This enables automatic key rotation on instance restart while maintaining cache validity.

Features

  • Deterministic Key Derivation: Uses HKDF-SHA256 to derive per-instance keys from instance metadata
  • Multi-Environment Support: Works on EC2, ECS Fargate, Lambda, and local development
  • Automatic Rotation: New instances automatically get new keys
  • Cache Persistence: Worker restarts don't invalidate cached wrapped DEKs
  • Fallback Chain: L1 (local memory) → Redis → AWS Secrets Manager

How It Works

Instance Start
  ↓
Detect Metadata (EC2/ECS/Lambda/Local)
  ↓
LWK = HKDF-SHA256(instance_id, region, type)
  ↓
Try L1 Cache → Try Redis → Fetch from Secret Manager
  ↓
Wrap DEK with LWK using AES-256-GCM
  ↓
Cache in L1 (memory) and Redis (24hr TTL)

Instance Metadata Detection

EnvironmentMetadata SourceExample ID
EC2IMDSv2i-1234567890abcdef0
ECSTask Metadata Endpointtask-abc123xyz789
LambdaEnvironment Variablesmyfunction-latest
LocalHostname + PIDlocal-macbook-12345

Cache Key Format

Per-instance cache keys prevent collision and enable isolation:

LIBC_DK:{type}:{instance_id}:{key_version}

Examples:
  - LIBC_DK:ec2:i-1234567890ab:v1
  - LIBC_DK:ecs:task-abc123:v1
  - LIBC_DK:lambda:myfunction-$:v1

Performance

  • LWK Derivation: <1ms average
  • L1 Cache Hit: ~0.1ms
  • Redis Cache Hit: ~2ms
  • Secret Manager: ~150ms (only on cache miss)

Impact: Worker restart with same instance ID results in Redis cache hit instead of Secret Manager fetch (100x faster)

Verification

Run the verification script to test LWK derivation:

php examples/verify-lwk-derivation.php

Expected output:

=== Local Wrapping Key (LWK) Derivation Verification ===
✅ Instance metadata detected successfully
✅ LWK derivation is deterministic
✅ Different instances produce different LWKs
✅ Same instance produces same LWK after restart
✅ All tests passed!

Benchmarking

Run performance benchmarks:

php examples/benchmark-lwk.php

Architecture Documentation

For comprehensive documentation, see LWK-ARCHITECTURE.md:

  • Detailed system architecture
  • Security considerations
  • Performance characteristics
  • Troubleshooting guide
  • Operational best practices

Migration

The LWK system is enabled by default. No configuration changes needed.

Cache Migration:

# Clear old cache format (one-time)
redis-cli KEYS "LIBC_DATAKEYCACHE*" | xargs redis-cli DEL

# Workers will automatically rebuild cache with new per-instance format

Lambda Optimization

Lambda instances automatically skip Redis to prevent key bloat. Cached wrapped DEKs are stored in local memory only for Lambda execution environments.