Files
markmywords/ENCRYPTION.md
T

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 .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

# 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

  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

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 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

  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

# 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.