SHA256
9.1 KiB
9.1 KiB
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.mdfiles 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.encextension
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
# 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
# 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
# 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
- Navigate to the file in the browser
- You'll be prompted for the password (same password used for encryption)
- Enter the password and the file will be decrypted and displayed with full markdown support
Using the CLI Tool
Encrypt a File
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
# 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
# Show formatted markdown (requires markdown library)
python manage_content.py view content/docs/secret-guide.md.enc "secure_password_123"
List Encrypted Files
# 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
# 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:
# 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):
# 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
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
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
- Create and edit markdown files in
content/directory - Test locally:
python manage_content.py view content/file.md - Encrypt when ready:
python manage_content.py encrypt content/file.md "password"- Automatically sets password protection for browser access
- Deploy via Docker:
docker-compose up -d - Access in browser: File requires password to view; all markdown features work after authentication
Backup Workflow
# 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
# 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.