SHA256
284 lines
9.1 KiB
Markdown
284 lines
9.1 KiB
Markdown
# Encrypted Files & Password Protection
|
|
|
|
This document describes how to encrypt and password-protect markdown files in markmywords.
|
|
|
|
## Overview
|
|
|
|
markmywords supports file encryption with automatic password protection. When you encrypt a file:
|
|
- It becomes password-protected automatically (same password for both encryption and browser access)
|
|
- Users must authenticate in the browser before viewing
|
|
- File is decrypted and displayed with full markdown support (code highlighting, tables, etc.)
|
|
- File can also be decrypted from CLI for offline access or backups
|
|
- Only encrypted files (`.md.enc`) support password protection; regular `.md` files are always public
|
|
|
|
## Encryption Details
|
|
|
|
- **Algorithm**: Fernet (symmetric encryption based on AES-128)
|
|
- **Key Derivation**: PBKDF2 with SHA256, 100,000 iterations
|
|
- **Fixed Salt**: `markmywords_salt` (enables consistent key derivation across sessions)
|
|
- **File Format**: Encrypted files use `.md.enc` extension
|
|
|
|
## Security Features
|
|
|
|
- **Bcrypt Hashing**: Passwords are hashed using bcrypt for secure storage
|
|
- **Session Management**: Once authenticated in browser, users remain authenticated during their session
|
|
- **File Permissions**: Password database stored with restricted permissions (0600)
|
|
- **No Plain Text**: Passwords never stored in plain text (only bcrypt hashes)
|
|
- **HTTPS Ready**: Works seamlessly with HTTPS/SSL in production
|
|
- **Same Password Model**: One password serves both encryption key derivation and browser authentication
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
```bash
|
|
# REQUIRED for production - strong secret key for Flask sessions
|
|
export FLASK_SECRET_KEY="your-very-secure-random-key"
|
|
```
|
|
|
|
These variables should be set in your Docker environment or `.env` file.
|
|
|
|
## Quick Start
|
|
|
|
### Encrypt a File
|
|
|
|
```bash
|
|
# Encrypt a markdown file with a password
|
|
# Automatically sets password protection - if encrypted, it requires a password
|
|
python manage_content.py encrypt content/secret.md "mypassword"
|
|
|
|
# This creates: content/secret.md.enc
|
|
# The original file is deleted
|
|
# The password is hashed and stored for browser access
|
|
```
|
|
|
|
### Decrypt a File from CLI
|
|
|
|
```bash
|
|
# Decrypt and view a file (prints to stdout)
|
|
python manage_content.py decrypt content/secret.md.enc "mypassword"
|
|
|
|
# Save to a new file
|
|
python manage_content.py decrypt content/secret.md.enc "mypassword" > secret_decrypted.md
|
|
```
|
|
|
|
### View Encrypted Files in Browser
|
|
|
|
1. Navigate to the file in the browser
|
|
2. You'll be prompted for the password (same password used for encryption)
|
|
3. Enter the password and the file will be decrypted and displayed with full markdown support
|
|
|
|
## Using the CLI Tool
|
|
|
|
### Encrypt a File
|
|
|
|
```bash
|
|
python manage_content.py encrypt <filepath> <password>
|
|
|
|
# Example:
|
|
python manage_content.py encrypt content/docs/secret-guide.md "secure_password_123"
|
|
```
|
|
|
|
**Result**:
|
|
- Original file is deleted
|
|
- New encrypted file created: `content/docs/secret-guide.md.enc`
|
|
- Password hashed and stored in config (required for browser access)
|
|
- Decryption key is derived from the password
|
|
- Both encryption and browser authentication use the same password
|
|
- File is NOT viewable in browser without password
|
|
|
|
### Decrypt a File
|
|
|
|
```bash
|
|
# Print decrypted content to stdout
|
|
python manage_content.py decrypt <filepath> <password>
|
|
|
|
# Example:
|
|
python manage_content.py decrypt content/docs/secret-guide.md.enc "secure_password_123"
|
|
|
|
# Save to a file
|
|
python manage_content.py decrypt content/docs/secret-guide.md.enc "secure_password_123" > decrypted.md
|
|
```
|
|
|
|
### Decrypt and Display as HTML
|
|
|
|
```bash
|
|
# Show formatted markdown (requires markdown library)
|
|
python manage_content.py view content/docs/secret-guide.md.enc "secure_password_123"
|
|
```
|
|
|
|
### List Encrypted Files
|
|
|
|
```bash
|
|
# List all encrypted files
|
|
python manage_content.py list
|
|
|
|
# List encrypted files in a directory
|
|
python manage_content.py list content/docs/
|
|
```
|
|
|
|
### Check File Status
|
|
|
|
```bash
|
|
# Check if a file is encrypted and protected
|
|
python manage_content.py status content/secret.md.enc
|
|
```
|
|
|
|
## Workflow: Encryption Always Requires Password
|
|
|
|
When you encrypt a file, password protection is automatic:
|
|
|
|
```bash
|
|
# Single command encrypts file AND sets password protection
|
|
python manage_content.py encrypt content/secret.md "mypassword"
|
|
|
|
# When viewing in browser:
|
|
# - User sees password prompt (required)
|
|
# - User enters "mypassword"
|
|
# - Password is validated against stored hash
|
|
# - File is decrypted and rendered as HTML
|
|
```
|
|
|
|
**Design principle**: If a file is encrypted, it must be password-protected. This ensures encrypted content is never readable without authentication.
|
|
|
|
## Optional: Remove Password Requirement
|
|
|
|
If you want to make an encrypted file publicly viewable (while staying encrypted on disk):
|
|
|
|
```bash
|
|
# Unprotect - removes password requirement but keeps encryption
|
|
python manage_content.py unprotect content/secret.md.enc
|
|
|
|
# Now the file is viewable in browser without password
|
|
# But it's still encrypted in the filesystem
|
|
# You can re-protect it later by encrypting again with new password
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
### Password Requirements
|
|
- Minimum 4 characters
|
|
- Should be strong and unique for sensitive content
|
|
- The same password is used for both encryption and key derivation
|
|
|
|
### File Access Control
|
|
- Encrypted files require password authentication to view in browser
|
|
- Encrypted content is never readable without the password
|
|
- Password database is stored with restricted permissions (0600)
|
|
- Session-based authentication persists only for the current browser session
|
|
|
|
### Key Derivation
|
|
- Fixed salt is used for consistency (not random per file)
|
|
- This allows decryption on any machine with the password
|
|
- PBKDF2 with 100,000 iterations provides strong key derivation
|
|
- Should be secure for typical use cases; for extremely sensitive data, consider additional measures
|
|
|
|
### Recommendations
|
|
- Use HTTPS in production to prevent password interception
|
|
- Use strong, unique passwords for sensitive content
|
|
- Combine encryption + password protection for critical files
|
|
- Regularly backup encrypted content with passwords stored securely
|
|
- Consider using environment variables for passwords in automated scripts
|
|
|
|
## Programmatic Usage
|
|
|
|
### Python API
|
|
|
|
```python
|
|
from app import encrypt_file_content, decrypt_file_content
|
|
|
|
# Encrypt content
|
|
password = "mypassword"
|
|
original_content = "# My Secret Document\n\nThis is secret."
|
|
encrypted = encrypt_file_content(original_content, password)
|
|
|
|
# Save encrypted content
|
|
with open("secret.md.enc", "w") as f:
|
|
f.write(encrypted)
|
|
|
|
# Decrypt content
|
|
with open("secret.md.enc", "r") as f:
|
|
encrypted_content = f.read()
|
|
decrypted = decrypt_file_content(encrypted_content, password)
|
|
print(decrypted) # "# My Secret Document\n\nThis is secret."
|
|
```
|
|
|
|
### Key Derivation Details
|
|
|
|
```python
|
|
from cryptography.hazmat.primitives import hashes
|
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2
|
|
from cryptography.hazmat.backends import default_backend
|
|
import base64
|
|
|
|
def derive_encryption_key(password):
|
|
salt = b'markmywords_salt' # Fixed salt
|
|
kdf = PBKDF2(
|
|
algorithm=hashes.SHA256(),
|
|
length=32,
|
|
salt=salt,
|
|
iterations=100000,
|
|
backend=default_backend()
|
|
)
|
|
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
|
return key
|
|
|
|
# Same password always produces same key (due to fixed salt)
|
|
key1 = derive_encryption_key("mypassword")
|
|
key2 = derive_encryption_key("mypassword")
|
|
assert key1 == key2 # True
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### "Failed to decrypt" error
|
|
- Verify the password is correct
|
|
- Ensure the file is actually encrypted (has .md.enc extension)
|
|
- Check that the file hasn't been corrupted
|
|
|
|
### Encrypted files not appearing in search
|
|
- Encrypted files are excluded from the search index for security
|
|
- They only appear when accessed directly with proper authentication
|
|
- Once authenticated in browser, they display with full markdown support
|
|
|
|
### Browser shows incomplete markdown
|
|
- Make sure authentication succeeded (file should have rendered)
|
|
- Check browser console for errors
|
|
- Verify the password was entered correctly
|
|
|
|
### Decryption works CLI but fails in browser
|
|
- Browser authentication and decryption share the same password
|
|
- Session may have expired; try re-authenticating
|
|
- Check that file protection is set up correctly
|
|
|
|
## File Management Workflow
|
|
|
|
### Recommended Process
|
|
|
|
1. **Create and edit** markdown files in `content/` directory
|
|
2. **Test** locally: `python manage_content.py view content/file.md`
|
|
3. **Encrypt** when ready: `python manage_content.py encrypt content/file.md "password"`
|
|
- Automatically sets password protection for browser access
|
|
4. **Deploy** via Docker: `docker-compose up -d`
|
|
5. **Access** in browser: File requires password to view; all markdown features work after authentication
|
|
|
|
### Backup Workflow
|
|
|
|
```bash
|
|
# Export decrypted version for backup
|
|
python manage_content.py decrypt content/secret.md.enc "password" > backup/secret_backup.md
|
|
|
|
# Store password securely (not in version control)
|
|
echo "password" > backup/secret_password.txt # Secure this file!
|
|
chmod 600 backup/secret_password.txt
|
|
```
|
|
|
|
## Environment Variables
|
|
|
|
```bash
|
|
# Enable/disable encryption feature (default: true)
|
|
export ENCRYPTION_ENABLED="true"
|
|
```
|
|
|
|
All encryption runs with the selected interpreter's cryptography library. Ensure `cryptography>=41.0.7` is installed.
|