SHA256
Updated to do local content w/ passwording and encryption
This commit is contained in:
+283
@@ -0,0 +1,283 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user