Cipher is a local file encryption and decryption service. It solves the problem of safely creating Fernet keys and processing file encryption or decryption jobs through a small HTTP API.
Cipher is scoped to local file operations and keeps task state in memory while background workers process queued jobs. The service binds to 127.0.0.1 on port 49158 and rejects API calls that do not come from the local device.
Features:
- Fernet Key Creation — generate and save a Fernet symmetric key file to any permitted directory.
- File Encryption / Decryption — queue background encryption or decryption tasks for one or more files with a single API call.
- Large File Streaming — files are processed in 1 MiB chunks using a chunked Fernet format (
FRTN1magic header), avoiding full memory load. Legacy single-token files are still supported on decryption. - Filename Transformation — optionally encrypt or decrypt the file name itself, returning the obfuscated or restored name.
- Overwrite Mode — replace source files in-place with their encrypted or decrypted content.
- Path Policy — configurable allowlist and blacklist of root directories control which paths the API is permitted to operate on.
- Background Task Cleanup — finished tasks are automatically removed after a configurable retention period.
- ServiceHandler Integration — optionally registers with ServiceHandler for service discovery and endpoint registration.
Safety notice: Cipher is intended only for local, trusted environments. The encryption key file must be kept safe; losing it makes encrypted data permanently unrecoverable.
- Install Python dependencies:
pip install -r requirements.txt. - Review
resources/configuration.jsonto configureport,servicehandlerEnabled,servicehandlerPort,taskRetentionMinutes,allowedRoots, andblacklistedRoots. See below for the path policy behaviour. - Leave the project structure intact so the service can find
resources/andsrc/.
| Variable | Default | Description |
|---|---|---|
TASK_CLEANUP_INTERVAL_SECONDS |
60 |
Seconds between task cleanup cycles. |
The path policy uses two lists in resources/configuration.json:
allowedRoots: list of root paths the API is allowed to operate inside. If this list is non-empty, ONLY these roots are permitted and the blacklist is ignored.blacklistedRoots: list of root paths that are forbidden whenallowedRootsis empty. If both lists are empty, all paths on the system are permitted.
Example resources/configuration.json:
{
"port": 49158,
"servicehandlerEnabled": true,
"servicehandlerPort": 49155,
"allowedRoots": [],
"blacklistedRoots": []
}- Windows: run
scripts\run.bat(add--verbosefor debug output). - Unix-like: run
bash scripts/run.sh(add--verbosefor debug output). - Manual: run
python src/main.pyfrom the project root (add--verbosefor debug output).
All /api/* endpoints are local-device only. Requests from non-local addresses are rejected with:
403->{ "error": "Local device access only." }- All endpoints also support
HEADandOPTIONS. - API responses use
Connection: close.
Creates a new Fernet key file.
- Auth: local-device only (no API key required)
- Body (JSON object):
directory_path(string, optional): absolute path to an existing directory where the key file should be created. If omitted, defaults to the repository root. The chosen directory must be permitted by the server policy (allowedRoots/blacklistedRoots).file_name(string, required): file name only (not a path). Must not already exist indirectory_path.
- Returns:
201->{ "status": "created", "file_name": "mykey.key" }400->{ "error": "Request body must be a JSON object." }400->{ "error": "Invalid request payload" }500->{ "error": "Failed to create key file" }
Queues one encryption task executed in a background thread.
- Auth: local-device only (no API key required)
- Body (JSON object):
key_path(string, required): absolute path to existing key file.file_path(string or array of strings, required unlessfile_pathsused): absolute path(s) of existing file(s) to encrypt.file_paths(array of strings, optional alias): alternative tofile_path.encrypt_file_name(boolean, required): iftrue, encrypts the file name itself and returns the resulting absolute output path in the task result.overwrite_file(boolean, optional, defaultfalse): iftrue, encrypted content is written into the source file before any optional rename.output_file_path(string, optional unless required by rule below): absolute output file path for one input file.output_file_paths(array of strings, optional alias): one absolute output file path per input file.- Requirement rule: when
encrypt_file_nameisfalseandoverwrite_fileisfalse,output_file_pathoroutput_file_pathsis required. - Output path safety: input, key and output paths must be permitted by the server policy defined by
allowedRootsandblacklistedRootsinresources/configuration.json. IfallowedRootsis non-empty, only paths inside those roots are permitted. IfallowedRootsis empty butblacklistedRootscontains entries, any path inside a blacklisted root is forbidden. If both lists are empty, all paths are permitted. Ifoverwrite_fileisfalse, output paths must not already exist.
- Returns:
202->{ "task_id": "...", "status": "queued", "operation": "encrypt", "file_count": 1 }400->{ "error": "Request body must be a JSON object." }400->{ "error": "Invalid request payload" }500->{ "error": "Could not start the background worker. The server may be under heavy load." }
Queues one decryption task executed in a background thread.
- Auth: local-device only (no API key required)
- Body (JSON object):
key_path(string, required): absolute path to existing key file.file_path(string or array of strings, required unlessfile_pathsused): absolute path(s) of existing file(s) to decrypt.file_paths(array of strings, optional alias): alternative tofile_path.decrypt_file_name(boolean, required): iftrue, decrypts the file name itself and returns the resulting absolute output path in the task result.overwrite_file(boolean, optional, defaultfalse): iftrue, decrypted content is written into the source file before any optional rename.output_file_path(string, optional unless required by rule below): absolute output file path for one input file.output_file_paths(array of strings, optional alias): one absolute output file path per input file.- Requirement rule: when
decrypt_file_nameisfalseandoverwrite_fileisfalse,output_file_pathoroutput_file_pathsis required. - Output path safety: input, key and output paths must be permitted by the server policy defined by
allowedRootsandblacklistedRootsinresources/configuration.json. IfallowedRootsis non-empty, only paths inside those roots are permitted. IfallowedRootsis empty butblacklistedRootscontains entries, any path inside a blacklisted root is forbidden. If both lists are empty, all paths are permitted. Ifoverwrite_fileisfalse, output paths must not already exist. - Note: the file referenced by
key_pathmust not be included in thefile_path/file_pathsinput or inoutput_file_path/output_file_paths. The server will reject requests that attempt to process the key file itself.
- Returns:
202->{ "task_id": "...", "status": "queued", "operation": "decrypt", "file_count": 1 }400->{ "error": "Request body must be a JSON object." }400->{ "error": "Invalid request payload" }500->{ "error": "Could not start the background worker. The server may be under heavy load." }
Returns current task state and final result or error once finished.
- Auth: local-device only (no API key required)
- Path parameters:
task_id(string, required): task identifier returned byPOST /api/encryptorPOST /api/decrypt.
- Returns:
200->{ "task_id": "<uuid>", "operation": "encrypt", "status": "queued|in_progress|completed", "result": { "operation": "encrypt", "file_count": 1, "files": [ { "input_name": "...", "output_name": "..." } ] } }500->{ "task_id": "<uuid>", "operation": "encrypt", "status": "failed", "error": "<failure-reason>", "error_detail": "<short exception message>" }404->{ "error": "Task not found." }
- Notes:
resultis present only for completed tasks;error(anderror_detail) only for failed tasks.- When
encrypt_file_nameordecrypt_file_nameistrue, each file entry usesinput_pathandoutput_pathwith absolute paths instead of the name-only fields. - When filename transformation is disabled, each file entry returns
input_nameandoutput_name. error_detailcontains a short exception message intended to help debug local failures (e.g., permission denied caused by file syncing software).
Service and queue health snapshot.
- Auth: local-device only (no API key required)
- Body: none
- Returns:
200->{ "status": "ok", "service": "Cipher", "bind_address": "127.0.0.1", "port": 49158, "hostname": "...", "pid": 12345, "task_counts": { "queued": 0, "in_progress": 0, "completed": 0, "failed": 0, "total": 0 }, "task_retention_minutes": 30, "task_cleanup_interval_seconds": 60, "cipher_algorithm": "fernet" }
- Open an issue on GitHub for bug reports, feature requests, or help.