Building a Flask-based API Server for KeePass Database Operations

Building a Flask-based API Server for KeePass Database Operations

KeePass Basics

KeePass is a free, open-source password manager that helps users store and manage their passwords securely. It stores all passwords in a single encrypted database file (.kdbx), protected by a master password and/or key file.

Database Structure

  1. Groups: Similar to folders, organize entries hierarchically

  2. Entries: Individual password records containing:

    • Title

    • Username

    • Password

    • URL

    • Notes

    • Custom fields

    • File attachments

Security Features

  1. Encryption

    • AES-256 Encryption: Military-grade encryption for the database

    • Key Derivation: Uses Argon2 (KDBX4) or AES-KDF (KDBX3)

  2. Authentication Methods

    • Master Password

    • Key File

    • Windows User Account

    • Hardware Keys

  3. File Format Versions

    • KDBX4 (Latest):

      • Improved security with Argon2 KDF

      • ChaCha20 cipher support

      • Better attachment handling

    • KDBX3:

      • AES-KDF

      • Widely supported

      • Compatible with most clients

PyKeePass Integration

  1. Basic Usage
from pykeepass import PyKeePass

# Open database
kp = PyKeePass('database.kdbx', password='master_password')

# Access entries
all_entries = kp.entries
root_group = kp.root_group
  1. Entry Operations
# Create entry
entry = kp.add_entry(group, title='Sample', username='user', password='pass')

# Find entries
entry = kp.find_entries(title='Sample', first=True)
entries = kp.find_entries(group='Banking')

# Update entry
entry.title = 'New Title'
entry.password = 'new_password'

# Delete entry
kp.delete_entry(entry)

# Save changes
kp.save()
  1. Group Operations
# Create group
group = kp.add_group(kp.root_group, 'New Group')

# Find group
group = kp.find_groups(name='New Group', first=True)

# Move entry to group
entry.move(group)

Maintenance and Troubleshooting

  1. Database Management

    • Regular backups

    • Database optimization

    • Entry cleanup

    • Access logging

  2. Common Issues

    • File permissions

    • Lock file conflicts

    • Concurrent access

    • Database corruption

  3. Performance

    • Response time

    • Memory usage

    • File size

    • Query optimization

  4. Security Best Practices

    • Regular security audits

    • Password rotation

    • Access control review

    • Encryption validation

Architecture Overview

The application follows a modular architecture with the following components:

backend/
├── app.py              # Main application entry point
├── config.py           # Configuration settings
├── __init__.py         # Package initializer
└── services/          # Service modules
    └── kdbx/         # KeePass database handling
        ├── __init__.py
        └── routes.py   # API endpoints

The architecture implements:

  • Blueprint Pattern: For modular route organization

  • Service Layer: Separates business logic from route handlers

  • Configuration Management: Environment-based configuration

  • CORS Support: For cross-origin resource sharing

  • Logging: Comprehensive logging system

Project Structure

1. Main Application (app.py)

import logging
from flask import Flask
from flask_cors import CORS
from .services.kdbx import kdbx_bp
from .config import DEBUG, PORT

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def create_app():
    app = Flask(__name__)
    CORS(app)
    app.register_blueprint(kdbx_bp)
    return app

def main():
    app = create_app()
    logger.info("Starting KeePass Reader API server...")
    app.run(port=PORT, debug=DEBUG)

2. Configuration (config.py)

import os

DEBUG = os.getenv('FLASK_DEBUG', True)
PORT = int(os.getenv('FLASK_PORT', 5000))
SECRET_KEY = os.getenv('SECRET_KEY', 'your-secret-key')

Setting Up the Project

  1. Dependencies
    Create a requirements.txt with the following content:
# Web Framework and Extensions
Flask==2.0.1
Flask-Cors==3.0.10
Werkzeug==2.0.1
click==8.0.1
itsdangerous==2.0.1
Jinja2==3.0.1
MarkupSafe==2.0.1

# KeePass Database Handling
pykeepass==4.0.3
construct==2.10.67
argon2-cffi==21.3.0
pycryptodomex==3.10.1
lxml==4.9.1

# Environment and Configuration
python-dotenv==0.19.0
PyYAML==6.0

# Testing
pytest==7.0.1
pytest-cov==3.0.0

# Utilities
python-dateutil==2.8.2
six==1.16.0

Install the dependencies using:

pip install -r requirements.txt
  1. Environment Setup
    Create a .env file:
FLASK_DEBUG=True
FLASK_PORT=5000
SECRET_KEY=your-secret-key

Implementation Details

1. Blueprint Setup (services/kdbx/init.py)

from flask import Blueprint

kdbx_bp = Blueprint('kdbx', __name__, url_prefix='/api/kdbx')

2. Route Implementation (services/kdbx/routes.py)

from flask import jsonify, request
from . import kdbx_bp

@kdbx_bp.route('/open', methods=['POST'])
def open_database():
    """Open a KeePass database"""
    try:
        data = request.get_json()
        # Implementation details
        return jsonify({"status": "success"})
    except Exception as e:
        return jsonify({"error": str(e)}), 400

API Documentation

KeePass Database Operations

Complete CRUD Implementation for Entries

Here's the complete implementation of CRUD operations for KeePass entries (services/kdbx/routes/entries.py):

import os
import logging
from flask import jsonify, request
from pykeepass import PyKeePass
from . import kdbx_bp
from ..utils import get_entry_data
from ....config import LOCAL_KDBX_PATH, LOCAL_KDBX_PASSWORD

logger = logging.getLogger(__name__)

@kdbx_bp.route('/entries', methods=['POST'])
def create_entry():
    """Create a new entry in the KDBX file"""
    try:
        data = request.get_json()
        required_fields = ['title', 'username', 'password']

        # Validate required fields
        for field in required_fields:
            if field not in data:
                return jsonify({'error': f'Missing required field: {field}'}), 400

        try:
            kp = PyKeePass(LOCAL_KDBX_PATH, password=LOCAL_KDBX_PASSWORD)

            # Get or create group
            group_path = data.get('group', 'Root')
            if group_path == 'Root':
                group = kp.root_group
            else:
                group = kp.find_groups(path=group_path, first=True)
                if group is None:
                    group = kp.add_group(kp.root_group, group_path)

            # Create new entry
            entry = kp.add_entry(
                destination_group=group,
                title=data['title'],
                username=data['username'],
                password=data['password'],
                url=data.get('url', ''),
                notes=data.get('notes', '')
            )

            kp.save()

            return jsonify({
                'success': True,
                'message': 'Entry created successfully',
                'entry': get_entry_data(entry)
            })

        except Exception as e:
            logger.error(f"Error creating entry: {str(e)}")
            return jsonify({
                'error': str(e),
                'details': 'Failed to create entry'
            }), 400

    except Exception as e:
        return jsonify({'error': str(e)}), 500

@kdbx_bp.route('/entries/<title>', methods=['GET'])
def read_entry(title):
    """Read a specific entry from the KDBX file"""
    try:
        kp = PyKeePass(LOCAL_KDBX_PATH, password=LOCAL_KDBX_PASSWORD)
        entry = kp.find_entries(title=title, first=True)

        if entry is None:
            return jsonify({'error': 'Entry not found'}), 404

        return jsonify({
            'success': True,
            'entry': get_entry_data(entry)
        })

    except Exception as e:
        return jsonify({'error': str(e)}), 500

@kdbx_bp.route('/entries/<title>', methods=['PUT'])
def update_entry(title):
    """Update an existing entry in the KDBX file"""
    try:
        data = request.get_json()
        required_fields = ['title', 'username', 'password']

        # Validate required fields
        for field in required_fields:
            if field not in data:
                return jsonify({'error': f'Missing required field: {field}'}), 400

        try:
            kp = PyKeePass(LOCAL_KDBX_PATH, password=LOCAL_KDBX_PASSWORD)
            entry = kp.find_entries(title=title, first=True)

            if entry is None:
                return jsonify({'error': 'Entry not found'}), 404

            # Update fields
            entry.username = data['username']
            entry.password = data['password']
            entry.url = str(data.get('url', ''))
            entry.notes = str(data.get('notes', ''))

            # Handle title change
            if data['title'] != title:
                entry.title = data['title']

            # Handle group change
            group_path = str(data.get('group', 'Root'))
            if group_path == 'Root':
                new_group = kp.root_group
            else:
                new_group = kp.find_groups(path=group_path, first=True)
                if new_group is None:
                    new_group = kp.add_group(kp.root_group, group_path)

            if new_group != entry.group:
                entry.move(new_group)

            kp.save()

            return jsonify({
                'success': True,
                'message': 'Entry updated successfully',
                'entry': get_entry_data(entry)
            })

        except Exception as e:
            return jsonify({
                'error': str(e),
                'details': 'Failed to update entry'
            }), 400

    except Exception as e:
        return jsonify({'error': str(e)}), 500

@kdbx_bp.route('/entries/<title>', methods=['DELETE'])
def delete_entry(title):
    """Delete an entry from the KDBX file"""
    try:
        kp = PyKeePass(LOCAL_KDBX_PATH, password=LOCAL_KDBX_PASSWORD)
        entry = kp.find_entries(title=title, first=True)

        if entry is None:
            return jsonify({'error': 'Entry not found'}), 404

        kp.delete_entry(entry)
        kp.save()

        return jsonify({
            'success': True,
            'message': 'Entry deleted successfully'
        })

    except Exception as e:
        return jsonify({'error': str(e)}), 500

@kdbx_bp.route('/entries', methods=['GET'])
def list_entries():
    """List all entries in the KDBX file"""
    try:
        kp = PyKeePass(LOCAL_KDBX_PATH, password=LOCAL_KDBX_PASSWORD)
        entries = [get_entry_data(entry) for entry in kp.entries]

        return jsonify({
            'success': True,
            'entries': entries,
            'total': len(entries)
        })

    except Exception as e:
        return jsonify({'error': str(e)}), 500

API Endpoints Summary

  1. Create Entry

    • Endpoint: POST /api/kdbx/entries

    • Request Body:

        {
            "title": "Entry Title",
            "username": "user",
            "password": "pass",
            "url": "https://example.com",
            "notes": "Optional notes",
            "group": "Optional/Group/Path"
        }
      
  2. Read Entry

    • Endpoint: GET /api/kdbx/entries/<title>

    • Returns entry details if found

  3. Update Entry

    • Endpoint: PUT /api/kdbx/entries/<title>

    • Request Body: Same as Create Entry

  4. Delete Entry

    • Endpoint: DELETE /api/kdbx/entries/<title>

    • Removes the entry with the specified title

  5. List All Entries

    • Endpoint: GET /api/kdbx/entries

    • Returns all entries in the database

Implementation Features

  1. Error Handling

    • Proper validation of required fields

    • Appropriate HTTP status codes

    • Detailed error messages

    • Exception handling at multiple levels

  2. Group Management

    • Automatic group creation if not exists

    • Support for nested group paths

    • Root group fallback

  3. Data Validation

    • Required field checking

    • Type conversion for optional fields

    • Null safety checks

  4. Response Format

    • Consistent JSON response structure

    • Success/error indicators

    • Detailed messages

    • Entry data in responses

Security Considerations

  1. Input Validation

    • Required field validation

    • Data type checking

    • Safe string handling

  2. Error Handling

    • No sensitive information in error messages

    • Proper logging of errors

    • Appropriate error status codes

  3. Database Access

    • Secure password handling

    • Safe database operations

    • Proper file locking during saves

Security Considerations

  1. Password Handling

    • Never store passwords in plaintext

    • Use secure environment variables

    • Implement proper session management

  2. API Security

    • Implement rate limiting

    • Use HTTPS in production

    • Validate all inputs

    • Implement proper error handling

  3. Database Security

    • Secure file permissions

    • Implement backup mechanisms

    • Handle concurrent access safely

Best Practices

  1. Code Organization

    • Use blueprints for route organization

    • Implement service layers for business logic

    • Keep configuration separate

    • Use proper logging

  2. Error Handling

from flask import jsonify

class APIError(Exception):
    status_code = 400

    def __init__(self, message, status_code=None):
        super().__init__()
        self.message = message
        if status_code is not None:
            self.status_code = status_code

@app.errorhandler(APIError)
def handle_api_error(error):
    response = jsonify({'error': error.message})
    response.status_code = error.status_code
    return response
  1. Logging
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
  1. Configuration Management

    • Use environment variables

    • Implement different configs for development/production

    • Keep sensitive data out of version control

  2. Testing

    • Write unit tests for all components

    • Implement integration tests

    • Use pytest for testing framework

Conclusion

Building a Flask-based API server for KeePass database operations requires careful consideration of security, proper architecture, and best practices. By following this guide, you can create a robust and maintainable application that safely handles KeePass database operations.

Remember to:

  • Keep security as a top priority

  • Follow Flask best practices

  • Implement proper error handling

  • Use appropriate logging

  • Write comprehensive tests

  • Document your API endpoints

For more information, refer to:

Did you find this article valuable?

Support The art of Code by becoming a sponsor. Any amount is appreciated!