Metadata-Version: 2.4
Name: rclone-crypt-python
Version: 0.2.0
Summary: Pure-Python reader/writer for rclone crypt files
Author: rclone-crypt-python contributors
License: MIT License
        
        Copyright (c) 2026 rclone-crypt-python contributors
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pynacl>=1.5.0
Requires-Dist: cryptography>=42.0.0
Provides-Extra: test
Requires-Dist: pytest>=8.0.0; extra == "test"
Requires-Dist: pytest-cov>=5.0.0; extra == "test"
Dynamic: license-file

# rclone-crypt-python

Pure-Python reader/writer for rclone crypt files and names.

The PyPI distribution is `rclone-crypt-python`; Python imports remain
`rclone_crypt`.

## Background

This tool was written with three goals in mind:

1. **Proof of concept**: I wanted to demonstrate that the rclone crypt format is sufficiently documented to be re-implemented, originally without the help of AI but later with it. And I wanted to demonstrate that this can be a standard used intraoperatively.
2. **Real utility**: I wanted to work with rclone crypt without having to subprocess rclone each time.
3. **AI Coding Assistant Playground**: When I first made this (Jan 2026, posted much later), I was new to AI coding assistants and it was a fun way to learn more.


## Features

- Streamed read/write of rclone crypt files (very large files supported)
- Filename encryption/decryption (base32, base64, base32768)
- Decrypt encrypted rclone config and reveal obscured passwords
- Explicit, composable APIs for secrets, names, and file IO

## Start here

Common entry points (all available from top-level `rclone_crypt`):

- `RcloneConfig`: load config and derive secrets
- `RcloneCrypt`: high-level path-like interface bound to a local crypt root
- `CryptSecrets`: explicit passwords (plain or obscured)
- `NameCipher`: encrypt/decrypt names and paths
- `CryptFile`: stream file read/write
- `crypt_ls`: list entries with decrypted names

Quick distinction: `RcloneCrypt` always works in decrypted names (it handles name
encryption for you), while `CryptFile` expects the encrypted on-disk path and
only handles file content encryption/decryption.

Typical flow (primary interface):

```python
from rclone_crypt import RcloneConfig, RcloneCrypt

cfg = RcloneConfig.from_path("~/.config/rclone/rclone.conf", allow_prompt=False)
crypt = RcloneCrypt.from_config(cfg, "crypt")
path = crypt / "dir" / "file.txt"
```

For more examples using the high-level interface, see the
[high-level guide](docs/high_level_guide.md).

If you need lower-level control (config parsing, name encryption without a root,
or streaming file IO), the guide also includes a mapping of high-level and
low-level APIs.
For advanced workflows, see [advanced usage](docs/advanced_usage.md).

## Non-goals

- Cloud backend implementations
- Calling rclone at runtime (tests only)

## Why use this tool

- You want to inspect or manipulate rclone crypt data directly from Python.
- You need streaming read/write support without shelling out to rclone.
- You want explicit, testable crypt operations inside your own tooling.

## Requirements

- Python 3.10+
- Dependencies: `cryptography`, `pynacl`

## Install

Install the latest published release from PyPI:

```bash
python -m pip install rclone-crypt-python
```

From a local clone:

```bash
python -m pip install -e '.[test]'
```

For runtime-only installs, drop the `[test]` extra:

```bash
python -m pip install -e .
```

The PyPI distribution is named `rclone-crypt-python`; import it as
`rclone_crypt`.

## Quickstart

### High-level interface

```python
from rclone_crypt import RcloneConfig, RcloneCrypt

cfg = RcloneConfig.from_path("~/.config/rclone/rclone.conf", allow_prompt=False)
crypt = RcloneCrypt.from_config(cfg, "crypt")

path = crypt / "dir" / "file.txt"
path.write_text("hello", overwrite=False)
print(path.read_text())
print(crypt.ls())
```

### Secrets

```python
from rclone_crypt import CryptSecrets

secrets = CryptSecrets.from_passwords("password", "password2")
```

Or from obscured config values:

```python
from rclone_crypt import CryptSecrets

secrets = CryptSecrets.from_obscured(
    "vENjtZL-E-6OQ77fGY6H4WwF57s",
    "DJrXvTm8658avycmzDjpASEMuiI",
)
```

### Obscure / reveal

```python
from rclone_crypt import obscure, reveal

obscured = obscure("password")
plaintext = reveal(obscured)
```

### Decrypt rclone config

```python
from rclone_crypt import RcloneConfig

cfg = RcloneConfig.from_path(
    "~/.config/rclone/rclone.conf",
    password=None,
    allow_prompt=True,
)
# Convenience aliases:
cfg = RcloneConfig("~/.config/rclone/rclone.conf", allow_prompt=True)
cfg = RcloneConfig.load("~/.config/rclone/rclone.conf", allow_prompt=True)
section = cfg.section("crypt")
```

To encrypt config content:

```python
from rclone_crypt import encrypt_config

data = b"[remote]\ntype = crypt\n"
encrypted = encrypt_config(data, "config-password")
```

Password sourcing order:

1. Explicit `password` argument
2. `password_command` argument or `RCLONE_PASSWORD_COMMAND`
3. `RCLONE_CONFIG_PASS`
4. Prompt (if `allow_prompt=True`)

By default, config passwords are treated as obscured (rclone's default):

```python
secrets = cfg.get_crypt_secrets("crypt", passwords_are_obscured=True)
secrets = cfg.get_crypt_secrets("crypt")  # defaults to obscured
```

Build a name cipher directly from the config section:

```python
name_cipher = cfg.get_name_cipher("crypt", passwords_are_obscured=True)
```

Or get both secrets and name cipher together:

```python
bundle = cfg.get_crypt_remote("crypt", passwords_are_obscured=True)
secrets = bundle.secrets
name_cipher = bundle.name_cipher
```

Password source examples:

```bash
export RCLONE_CONFIG_PASS="my-config-password"
```

```bash
export RCLONE_PASSWORD_COMMAND="pass show rclone/config"
```

```python
cfg = RcloneConfig.from_path("~/.config/rclone/rclone.conf", password="my-config-password")
cfg = RcloneConfig.from_path("~/.config/rclone/rclone.conf", allow_prompt=True)
```

### Filename encryption

```python
from rclone_crypt import CryptSecrets, NameCipher

secrets = CryptSecrets.from_passwords("password", "password2")
name_cipher = NameCipher(
    secrets,
    filename_encryption="standard",
    directory_name_encryption=True,
    filename_encoding="base32",
)

encrypted = name_cipher.encrypt_path("dir/file.txt")
decrypted = name_cipher.decrypt_path(encrypted)
```

### File encryption/decryption

```python
from rclone_crypt import CryptFile, CryptSecrets

secrets = CryptSecrets.from_passwords("password", "password2")

with CryptFile.open_write("encrypted.bin", secrets=secrets) as writer:
    writer.write(b"hello")

with CryptFile.open_read("encrypted.bin", secrets=secrets) as reader:
    data = reader.read()
```

You can also use standard file-like modes:

```python
with CryptFile.open("encrypted.bin", "rt", secrets=secrets) as reader:
    text = reader.read()
```

### Decrypted listing helper

```python
from rclone_crypt import crypt_ls

for entry in crypt_ls("encrypted_dir", name_cipher):
    print(entry.name, entry.path)
```

## Tutorial

See the [tutorial](docs/TUTORIAL.md) for step-by-step user stories, including
writing new files with correct name encoding, reading by plaintext name, and
listing directories.

## Additional docs

- [High-level guide](docs/high_level_guide.md)
- [Passwords and config](docs/passwords_and_config.md)
- [API reference](docs/api_reference.md)
- [Troubleshooting](docs/troubleshooting.md)

## Testing

Unit tests:

```bash
scripts/test_unit.sh
```

Interop tests (requires `rclone` in PATH):

```bash
scripts/test_interop.sh
```

## Security notes

- rclone "obscure" is not strong encryption; it only deters casual viewing.
- Config encryption uses NaCl secretbox with a SHA-256 derived key.

## Troubleshooting and common pitfalls

- **Config password confusion**: rclone config encryption password is different
  from remote passwords. Make sure you use the config password for
  `RcloneConfig.from_path`.
- **Hidden password command**: if `RCLONE_PASSWORD_COMMAND` is set in your
  shell, it will be used unless you provide an explicit password.
- **Name options mismatch**: `filename_encryption`, `directory_name_encryption`,
  `filename_encoding`, and `suffix` must match your rclone remote settings.
- **Append mode not supported**: encrypted files cannot be appended without
  rewriting the last block. Use write mode and rewrite the file if needed.
- **Logging**: the library emits extensive debug logging in core modules
  (names, files, config, and high-level paths). By default nothing is shown
  unless you configure logging (e.g., `logging.basicConfig(level=logging.DEBUG)`).

## References

- rclone crypt docs: https://rclone.org/crypt/
- rclone obscure docs: https://rclone.org/commands/rclone_obscure/
- rclone source (crypt backend): https://github.com/rclone/rclone/tree/master/backend/crypt


## ## AI/LLM Disclosure

As noted in the motivation, a substantial portion of this code was produced with the assistance of large language models (LLMs), primarily various versions of ChatGPT 5+ used through Codex. For all intents and purposes, it was “vibe coded.” This disclosure is intentional: I am not attempting to present the implementation as primarily human-written or to obscure the extent of LLM involvement.

The resulting code has nevertheless been exercised through both LLM-generated test cases and real-world use in complex settings. While LLMs were heavily involved in its implementation, the software has been evaluated based on its observed behavior rather than assumed correctness.
