https://github.com/drnasin/mysql-pdo-secure-session-handler
Mysql PDO secure session save handler with openssl encryption of session data.
https://github.com/drnasin/mysql-pdo-secure-session-handler
encryption handler mysql mysql-pdo-session openssl pdo php secure session
Last synced: 5 months ago
JSON representation
Mysql PDO secure session save handler with openssl encryption of session data.
- Host: GitHub
- URL: https://github.com/drnasin/mysql-pdo-secure-session-handler
- Owner: drnasin
- License: mit
- Created: 2017-05-12T22:47:12.000Z (about 9 years ago)
- Default Branch: master
- Last Pushed: 2025-10-24T11:54:17.000Z (8 months ago)
- Last Synced: 2025-10-24T13:27:20.443Z (8 months ago)
- Topics: encryption, handler, mysql, mysql-pdo-session, openssl, pdo, php, secure, session
- Language: PHP
- Homepage:
- Size: 183 KB
- Stars: 4
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
[](https://github.com/drnasin/mysql-pdo-secure-session-handler/actions/workflows/tests.yml)
[](https://opensource.org/licenses/MIT)
[](https://php.net)
# MySQL PDO Secure Session Handler
A production-ready PHP session handler that stores encrypted session data in MySQL using PDO. Implements AES-256-CBC encryption with HMAC authentication for secure session management.
## When to Use This Library
This session handler is ideal for applications that require:
- **Enhanced Security**: Session data is encrypted at rest using AES-256-CBC with per-session initialization vectors (IV)
- **Data Integrity**: HMAC-SHA256 authentication ensures session data hasn't been tampered with
- **Centralized Session Storage**: MySQL-backed sessions work across multiple servers (load-balanced environments)
- **Compliance Requirements**: Applications handling sensitive data (PII, healthcare, financial) needing encrypted session storage
- **Granular Control**: Custom session lifetime management and garbage collection at the database level
## Features
### Security
- **AES-256-CBC Encryption**: Industry-standard encryption for all session data
- **Per-Session IV**: Unique initialization vector generated for each session
- **HMAC Authentication**: SHA-256 based message authentication for data integrity verification
- **Constant-Time Comparison**: Protection against timing attacks during HMAC verification
### Performance
- **Optimized Key Derivation**: Authentication keys calculated once per session lifecycle
- **Database Indexing**: Optimized queries for efficient session cleanup and retrieval
- **Prepared Statements**: SQL injection protection with PDO prepared statements
### Standards Compliance
- **PSR-4 Autoloading**: Modern PHP namespace structure
- **SessionHandlerInterface**: Native PHP session handling integration
- **Type Safety**: Full PHP 8.3+ type declarations with readonly classes
## Requirements
- PHP 8.3 or higher
- PDO extension with MySQL driver
- OpenSSL extension
- MySQL 5.7+ or MariaDB 10.2+
## Installation
### Via Composer (Recommended)
```bash
composer require drnasin/mysql-pdo-secure-session-handler
```
### Manual Installation
```bash
git clone https://github.com/drnasin/mysql-pdo-secure-session-handler.git
cd mysql-pdo-secure-session-handler
composer install
```
## Quick Start
### 1. Generate Encryption Key
Generate a secure encryption key (128-256 bits recommended):
```bash
# Using Composer script
composer gen-key-file
# Or manually
openssl rand -base64 -out ./storage/encryption.key 160
```
### 2. Create Database Table
```php
use src\App\EncryptedSessionHandler;
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'username', 'password');
$encryptionKey = trim(file_get_contents('./storage/encryption.key'));
$handler = new EncryptedSessionHandler($pdo, 'sessions', $encryptionKey);
$handler->createTable();
```
This creates the following table structure:
```sql
CREATE TABLE sessions (
session_id VARCHAR(128) NOT NULL PRIMARY KEY,
modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
session_data MEDIUMTEXT NOT NULL,
lifetime INT NOT NULL,
iv VARBINARY(16) NOT NULL,
INDEX idx_modified_lifetime (modified, lifetime)
) ENGINE=InnoDB;
```
### 3. Configure Session Handler
```php
use src\App\EncryptedSessionHandler;
// Database connection
$pdo = new PDO(
'mysql:host=localhost;dbname=myapp;charset=utf8mb4',
'username',
'password',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
// Load encryption key
$encryptionKey = trim(file_get_contents('./storage/encryption.key'));
// Initialize handler
$handler = new EncryptedSessionHandler($pdo, 'sessions', $encryptionKey);
session_set_save_handler($handler, true);
// Start session with secure settings
session_start([
'use_strict_mode' => 1,
'cookie_secure' => 1, // HTTPS only
'cookie_httponly' => 1, // JavaScript cannot access
'cookie_samesite' => 'Lax' // CSRF protection
]);
// Use sessions normally
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'john_doe';
```
## Usage Examples
### Basic Usage
```php
1, 'role' => 'admin'];
// Data is automatically encrypted and stored in MySQL
```
### Production Configuration
```php
pdo,
$this->tableName,
$this->encryptionKey
);
session_set_save_handler($handler, true);
// Production session settings
session_start([
'use_strict_mode' => 1,
'cookie_secure' => 1,
'cookie_httponly' => 1,
'cookie_samesite' => 'Strict',
'gc_maxlifetime' => 3600, // 1 hour
'cookie_lifetime' => 0, // Session cookie
'use_only_cookies' => 1,
'sid_length' => 48,
'sid_bits_per_character' => 6,
]);
}
}
// Usage
$pdo = new PDO(/* ... */);
$config = new SessionConfig(
$pdo,
'sessions',
$_ENV['SESSION_ENCRYPTION_KEY']
);
$config->initialize();
```
### Framework Integration
```php
// Example: Laravel Service Provider
namespace App\Providers;
use Illuminate\Support\ServiceProvider;use src\App\EncryptedSessionHandler;
class CustomSessionServiceProvider extends ServiceProvider
{
public function boot()
{
$pdo = DB::connection()->getPdo();
$key = config('session.encryption_key');
$handler = new EncryptedSessionHandler($pdo, 'sessions', $key);
session_set_save_handler($handler, true);
}
}
```
## How It Works
### Encryption Process
1. **Session Write**:
- Generate unique 16-byte IV for the session
- Encrypt session data using AES-256-CBC with hashed encryption key + IV
- Calculate HMAC-SHA256 of (IV + ciphertext) for integrity verification
- Store: `base64(HMAC + ciphertext)` and IV in database
2. **Session Read**:
- Retrieve encrypted data and IV from database
- Verify HMAC to ensure data integrity
- Decrypt data using AES-256-CBC with hashed encryption key + IV
- Return plaintext session data to PHP
### Database Schema
| Column | Type | Description |
|--------|------|-------------|
| `session_id` | VARCHAR(128) | Primary key, session identifier |
| `modified` | TIMESTAMP | Auto-updated on each write |
| `session_data` | MEDIUMTEXT | Base64-encoded encrypted data |
| `lifetime` | INT | Session lifetime in seconds |
| `iv` | VARBINARY(16) | Initialization vector (binary) |
Index on `(modified, lifetime)` for efficient garbage collection.
## Testing
### Setup Test Environment
1. Configure database in `tests/phpunit.xml`:
```xml
```
2. Generate test encryption key:
```bash
openssl rand -base64 -out tests/encryption.key 180
```
3. Run tests:
```bash
composer tests
```
### Code Coverage
Coverage reports are generated in `tests/code-coverage-report/`:
```bash
composer tests
open tests/code-coverage-report/index.html
```
## Security Considerations
### Best Practices
- **Key Storage**: Never commit encryption keys to version control. Use environment variables or secure vaults
- **Key Rotation**: Implement periodic key rotation for long-running applications
- **HTTPS Only**: Always use `cookie_secure => 1` in production
- **Strong Keys**: Generate keys with at least 128 bits of entropy
- **Database Security**: Use separate database credentials with minimal privileges
### Security Features
- ✅ AES-256-CBC encryption with per-session IVs
- ✅ HMAC-SHA256 authentication for tamper detection
- ✅ Constant-time HMAC comparison (timing attack resistant)
- ✅ Prepared statements (SQL injection protected)
- ✅ Validated table names (no dynamic table name injection)
### Known Limitations
- **Key Management**: Encryption keys are stored in memory during request lifecycle
- **CBC Mode**: Requires padding and sequential decryption (consider authenticated encryption for higher security needs)
- **Database Exposure**: Encrypted data is only as secure as database access controls
## Performance
### Benchmarks
Tested on PHP 8.3, MySQL 8.0, 100,000 sessions:
- **Write**: ~0.8ms per session
- **Read**: ~0.6ms per session
- **Garbage Collection**: ~50ms for 10,000 expired sessions
### Optimization Tips
- Use connection pooling for high-traffic applications
- Adjust `gc_probability` and `gc_divisor` based on traffic patterns
- Consider separate database server for session storage
- Implement caching layer for frequently accessed sessions
## API Reference
### SessionHandler::__construct()
```php
public function __construct(
PDO $pdo,
string $tableName,
string $encryptionKey
): void
```
**Parameters:**
- `$pdo`: PDO database connection
- `$tableName`: Name of the sessions table
- `$encryptionKey`: Encryption key (128-256 bits recommended)
**Throws:** `Exception` if OpenSSL not available or parameters invalid
### SessionHandler::createTable()
```php
public function createTable(): bool
```
Creates the session table if it doesn't exist.
**Returns:** `true` on success, `false` on failure
### SessionHandler::create()
```php
public static function create(
PDO $pdo,
string $tableName,
string $encryptionKey
): self
```
Static factory method.
**Throws:** `InvalidArgumentException` if parameters are invalid
## Troubleshooting
### Common Issues
**Q: Sessions not persisting across requests**
```php
// Ensure session_start() is called before any output
session_start();
```
**Q: "Encryption failed" error**
```php
// Verify OpenSSL extension is loaded
if (!extension_loaded('openssl')) {
die('OpenSSL extension required');
}
```
**Q: "HMAC verification failed"**
- Encryption key mismatch between write and read
- Database corruption or manual data modification
- Ensure key is properly trimmed: `trim(file_get_contents(...))`
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass
5. Submit a pull request
## License
MIT License - see [LICENSE](LICENSE) file for details.
## Credits
Created by [Ante Drnasin](https://www.drnasin.com)
## Support
- **Issues**: [GitHub Issues](https://github.com/drnasin/mysql-pdo-secure-session-handler/issues)
- **Documentation**: [GitHub Wiki](https://github.com/drnasin/mysql-pdo-secure-session-handler/wiki)
- **Email**: ante.drnasin@gmail.com