Comprehensive security documentation for the CryptNote PHP Library.
- Security Architecture
- Encryption Details
- Password Protection
- Secure Deletion
- Best Practices
- Threat Model
- Known Limitations
- Security Checklist
CryptNote uses a layered security approach to protect sensitive data:
┌─────────────────────────────────────────────────────────────────┐
│ Security Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Layer 1: Encryption │ │
│ │ │ │
│ │ Content ──▶ AES-256-GCM ──▶ Encrypted Data (AEAD) │ │
│ │ (Random 12-byte IV/nonce, 16-byte auth tag) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Layer 2: Password Protection │ │
│ │ (Optional) │ │
│ │ │ │
│ │ Password ──▶ PBKDF2-SHA256 ──▶ Derived Key │ │
│ │ (100,000+ iterations) │ │
│ │ │ │
│ │ Base Key + Derived Key ──▶ Combined Key │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Layer 3: Access Control │ │
│ │ │ │
│ │ • View limits (1-100 views) │ │
│ │ • Time expiration (up to 7 days) │ │
│ │ • Automatic destruction │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Layer 4: Secure Storage │ │
│ │ │ │
│ │ • SQLite with WAL mode │ │
│ │ • Secure deletion (overwrite before delete) │ │
│ │ • Automatic cleanup of old records │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
CryptNote defaults to AES-256-GCM (AEAD) for authenticated encryption.
Specifications:
- Algorithm: AES (Rijndael)
- Key Size: 256 bits (32 bytes)
- IV (nonce): 12 bytes per encryption
- Auth Tag: 16 bytes (GCM authentication tag)
Data Format (v2, no password):
┌──────────────────────────────────────────────────────────────┐
│ Base64 Encoded (v2) │
├──────────────────────────────────────────────────────────────┤
│ IV (12 bytes) │ Auth Tag (16 bytes) │ Encrypted Content │
└──────────────────────────────────────────────────────────────┘
Legacy AES-256-CBC with HMAC-SHA256 is available via encryption_version = v1 for backward compatibility.
Specifications:
- Algorithm: AES (Rijndael)
- Key Size: 256 bits (32 bytes)
- Block Size: 128 bits (16 bytes)
- Mode: CBC (Cipher Block Chaining)
- IV: Random 16 bytes per encryption
- HMAC: SHA-256 for integrity verification (32 bytes)
Data Format (v1, no password):
┌──────────────────────────────────────────────────────────────┐
│ Base64 Encoded (v1) │
├──────────────────────────────────────────────────────────────┤
│ IV (16 bytes) │ Encrypted Content │ HMAC-SHA256 (32 bytes) │
└──────────────────────────────────────────────────────────────┘
Encryption keys are generated using cryptographically secure random bytes:
// 256-bit key generation
$key = base64_encode(random_bytes(32));Entropy Sources:
random_bytes()- PHP's CSPRNG- Additional entropy from
microtime(),getmypid(),uniqid() - SHA-256 hash for final key derivation
Tokens are generated with high entropy to prevent guessing:
// Token generation process
$entropy = random_bytes(32) .
hash('sha256', microtime(true) . getmypid() . uniqid('', true), true) .
random_bytes(32);
$token = bin2hex(substr(hash('sha256', $entropy, true), 0, 32));
// Result: 64-character hexadecimal stringToken Properties:
- 64 hexadecimal characters (256 bits of entropy)
- Collision probability: ~1 in 2^256
- Validated format:
/^[a-f0-9]{64}$/i
When password protection is enabled, CryptNote uses PBKDF2 (Password-Based Key Derivation Function 2) to derive a key from the user's password.
Specifications:
- Algorithm: PBKDF2-HMAC-SHA256
- Iterations: 100,000 (default, configurable)
- Salt: Random 16 bytes per encryption
- Output: 256-bit derived key
Data Format (with password):
┌──────────────────────────────────────────────────────────────┐
│ Base64 Encoded │
├──────────────────────────────────────────────────────────────┤
│ Salt (16 bytes) │ IV (16 bytes) │ Encrypted Content │
└──────────────────────────────────────────────────────────────┘
The final encryption key combines the base key with the password-derived key:
// Key combination process
$passwordKey = hash_pbkdf2('sha256', $password, $salt, $iterations, 32, true);
$combinedKey = hash('sha256', $baseKey . $passwordKey, true);This provides:
- Defense in depth: Even if the database is compromised, password-protected notes require the password
- Unique keys: Each note has a unique combined key
- Brute-force resistance: PBKDF2 iterations slow down attacks
| Requirement | Value |
|---|---|
| Minimum length | 12 characters |
| Maximum length | 100 characters |
| Character set | Any UTF-8 characters |
Recommendations:
- Use at least 16 characters for high-security environments
- Mix uppercase, lowercase, numbers, and symbols
- Use the built-in password generator for strong passwords
$crypto = new CryptNoteStandalone();
$strongPassword = $crypto->generatePassword(20, true);CryptNote implements secure deletion to prevent data recovery:
┌─────────────────────────────────────────────────────────────┐
│ Secure Deletion Process │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Overwrite encrypted_data with random bytes (1KB) │
│ │ │
│ ▼ │
│ 2. Overwrite encryption_key with random bytes (64 bytes) │
│ │ │
│ ▼ │
│ 3. DELETE record from database │
│ │ │
│ ▼ │
│ 4. VACUUM database (during cleanup) │
│ │
└─────────────────────────────────────────────────────────────┘
Notes are automatically destroyed when:
- View limit reached: After the last allowed view
- Time expired: After the expiration time passes
- Cleanup runs: Unviewed notes older than
cleanup_days
-
Use HTTPS
# Apache - Force HTTPS RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
-
Secure Headers
header('Strict-Transport-Security: max-age=31536000; includeSubDomains'); header('X-Content-Type-Options: nosniff'); header('X-Frame-Options: DENY'); header('X-XSS-Protection: 1; mode=block'); header('Content-Security-Policy: default-src \'self\'');
-
Database Location
// Store outside web root 'db_path' => '/var/lib/cryptnote/notes.db'
-
File Permissions
chmod 700 /var/lib/cryptnote chmod 600 /var/lib/cryptnote/notes.db chown www-data:www-data /var/lib/cryptnote -R
-
Rate Limiting
// Implement rate limiting for password attempts class RateLimiter { public function checkLimit(string $ip, string $token): bool { // Allow max 5 attempts per minute per token // Implementation depends on your caching solution } }
-
Input Validation
// Always validate tokens before use if (!$cryptnote->validateToken($token)) { throw new Exception('Invalid token'); }
-
Error Handling
// Don't expose internal errors try { $note = $cryptnote->view($token, $password); } catch (Exception $e) { // Log detailed error internally error_log($e->getMessage()); // Show generic message to user echo "Unable to retrieve note"; }
-
Logging
// Log access attempts (without sensitive data) $logger->info('Note access attempt', [ 'token_prefix' => substr($token, 0, 8), 'ip' => $_SERVER['REMOTE_ADDR'], 'success' => $success, ]);
-
HTML Content
// Always escape output echo htmlspecialchars($note['content'], ENT_QUOTES, 'UTF-8');
-
Markdown Rendering
// Use a secure Markdown parser // Sanitize HTML output $html = $markdownParser->parse($note['content']); $safeHtml = $htmlPurifier->purify($html);
| Threat | Protection |
|---|---|
| Database theft | Content is encrypted; password-protected notes require password |
| Token guessing | 256-bit tokens with ~10^77 possibilities |
| Brute-force passwords | PBKDF2 with 100,000+ iterations |
| Data recovery | Secure deletion overwrites data before removal |
| Replay attacks | View limits and expiration |
| Timing attacks | Constant-time comparison for tokens |
| Threat | Mitigation |
|---|---|
| Server compromise | Use disk encryption, secure hosting |
| Memory dumps | Use encrypted swap, secure memory |
| Network interception | Always use HTTPS |
| Weak passwords | Enforce password policies, educate users |
| Social engineering | User education |
| Malware on client | Out of scope for server-side library |
Attacker obtains: encrypted_data, encryption_key, token
Without password:
- Attacker can decrypt content using stored key
- Mitigation: Use password protection for sensitive content
With password:
- Attacker needs password to derive combined key
- PBKDF2 makes brute-force expensive
- Mitigation: Use strong passwords
Token space: 16^64 = 2^256 possibilities
Attempts needed (50% probability): 2^255
At 1 billion attempts/second:
Time needed: ~10^60 years
Conclusion: Computationally infeasible
With 100,000 PBKDF2 iterations:
- ~10 attempts/second on modern hardware
- 6-char password (lowercase): ~3 days
- 8-char password (mixed): ~centuries
- 12-char password: ~heat death of universe
Mitigation: Require strong passwords, implement rate limiting
-
CBC Mode (v1): Legacy CBC mode with HMAC provides integrity but is less efficient than GCM
- Mitigation: Use default GCM mode (v2) for new implementations
-
Key in Database: Encryption key stored alongside encrypted data
- Mitigation: Use password protection or enable
key_wrappingfor sensitive content
- Mitigation: Use password protection or enable
-
PBKDF2 vs Argon2: PBKDF2 is used for password derivation
- Mitigation: For maximum security, consider Argon2id in future versions
-
Single Server: No built-in clustering or replication
- Mitigation: Use external database replication if needed
-
SQLite Concurrency: Limited concurrent write performance
- Mitigation: Use MySQL/PostgreSQL for high-traffic applications
-
No Key Rotation: Keys are not automatically rotated
- Mitigation: Implement key rotation if required
-
No Recovery: Destroyed notes cannot be recovered
- This is by design for security
-
Lost Passwords: Cannot recover password-protected notes without password
- This is by design for security
- Database stored outside web root
- Database file permissions set to 600
- Directory permissions set to 700
- HTTPS enabled and enforced
- Security headers configured
- Error messages don't expose internals
- Logging configured (without sensitive data)
- Rate limiting implemented
- Backup strategy in place (encrypted)
-
pbkdf2_iterations>= 100,000 -
auto_cleanupenabled -
cleanup_daysset appropriately -
base_urluses HTTPS -
db_pathoutside web root
- All user input validated
- Output properly escaped
- Exceptions handled securely
- No sensitive data in logs
- Tokens validated before use
- Password requirements enforced
If you discover a security vulnerability, please report it responsibly:
- Do not open a public issue
- Email security concerns to: contato@dolutech.com
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
We will respond within 48 hours and work with you to address the issue.