# pyconcrete: shipping Python as encrypted .pye instead of readable source

> pyconcrete is a C extension that turns compiled .pyc bytecode into encrypted .pye files and decrypts them at import time through an import hook, with the secret key compiled into a binary you build yourself. It is a distribution tool for people who must hand over Python code, not a security boundary against anyone determined.

**Falldog/pyconcrete** — Protect your python script, encrypt it as .pye and decrypt when import it

- Repository: https://github.com/Falldog/pyconcrete
- Stars: 752 · Forks: 146
- Language: C
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/falldog-pyconcrete

## What is encrypted is bytecode, not your source file

The name is misleading if you read it quickly. pyconcrete does not encrypt .py source files. The project description is explicit: it encrypts .pyc to .pye. So the pipeline starts with the normal Python compile step, takes the resulting bytecode, and encrypts that. The encrypted artefact is a .pye file, and the documentation gives the extension a special meaning: a file with that suffix cannot be executed by a normal python invocation. You must run it through the pyconcrete executable instead. The runtime flow is described in the workflow section of the README. The pyconcrete binary reads MAIN.pye, which was encrypted with your passphrase, decrypts the source file, and then launches the Python interpreter so that normal Python behaviour applies from that point on. The decryption step is where the project spends its effort, because the whole point is that the file on disk is not usable without the key.

## An import hook that looks for .pye before .py

The clever part, and the part that makes the library form of pyconcrete usable, is the import hook. The README states that pyconcrete hooks the import module mechanism, so when your script runs import MODULE, the hook first tries to find MODULE.pye and then decrypts it through the native module _pyconcrete.pyd before executing the decrypted data as .pyc content. Without the hook, a .pye file is just noise to the interpreter; with it, encrypted modules load as if they were compiled modules. The same mechanism extends to zip archives. A .pyz file is a zip archive containing encrypted .pye modules, and pyconcrete extracts and decrypts __main__.pye from it, while modules inside the archive import one another through the PyeZipImporter hook. The practical effect is that you can deliver a single archive and still have a multi module program with normal import statements inside it.

## The secret key is compiled into the binary, not stored in a file

Here is the design decision that separates pyconcrete from a packaging format with a password file next to it. The secret key is encrypted and decrypted inside the native module _pyconcrete.pyd, described as working like a DLL or SO, and the README says the key is hidden in binary code so it cannot be seen directly in a hex view. The key is not a runtime argument you supply to the encrypted program. Instead, the build system generates the secret key header file from the passphrase you provide at compile time, and that key is baked into the shipped binary. The consequence is an important operational one: the passphrase is a build input, and the same passphrase always produces the same secret key. That makes reproducible builds possible and also means the passphrase must be handled as a secret for the lifetime of the shipped software, because a binary and an encrypted file produced from a lost passphrase cannot be reconciled.

## Installing means building the extension with your own passphrase

There are no pre-built packages, so installation is a build step, and the passphrase is a mandatory part of it. The documentation is clear that the same passphrase generates the same secret key, and that pip 23.1 or newer is needed because passing build arguments requires the -C or --config-settings option. The basic install looks like this: 
```bash
$ pip install pyconcrete \
    --no-cache-dir \
    --config-settings=setup-args="-Dpassphrase=<Your_Passphrase>"
```
The --no-cache-dir flag is not optional in practice. pip caches built wheels, and a cached wheel was compiled with the previous passphrase, so reusing it produces a binary whose key does not match your files. The same flag appears in the justfile install recipe for a local development install. Build arguments are passed with the setup-args prefix, and the documented options are passphrase, which is mandatory, ext for a custom encrypted file extension defaulting to .pye, mode set to exe or lib and defaulting to exe, and install-cli which decides whether the pyecli command line tool is installed and defaults to true. The underlying build is Meson, driven by meson-python as the build backend since version 1.0.0, with the C extension compiled from src/ and the key header generated at build time.

## pyecli compile and build-zip are the two commands you will use

Once installed, the pyecli tool does the packaging work. To turn a script or a module directory into encrypted files: 
```bash
$ pyecli compile --pye -s=<your py script>
$ pyecli compile --pye -s=<your py module dir>
$ pyecli compile --pye -s=<your py module dir> -e=<your file extension>
```
The -s flag selects the source and -e overrides the encrypted file extension. You then remove the .py and .pyc originals, or copy the .pye files elsewhere, because leaving the plaintext script next to the encrypted one defeats the point. The documented layout puts the encrypted entry script beside a directory of encrypted libraries, and the entry point is invoked through the installed pyconcrete executable rather than python. For single file distribution there is a second command, build-zip, which packages a source directory into a .pyz archive: 
```bash
$ pyecli build-zip --source=<your_project_dir> --output=app.pyz
$ pyecli build-zip --source=<your_project_dir> --output=app.pyz --main=pkg.mod:fn
```
The documented arguments are --source and --output, both mandatory, --main in pkg.mod:fn format for generating __main__.py, --ext for a custom extension, and --ignore-file-list for patterns to skip. The archive is then run directly: 
```bash
$ pyconcrete app.pyz [args...]
```

## Importing from a plain zip placed on sys.path

Archives do not have to be executable to be useful. The documentation describes the PyeZipImporter hook finding and decrypting .pye modules inside any zip file that sits on sys.path, so a library archive can be shipped alongside the program. 
```python
import sys
import pyconcrete
sys.path.insert(0, 'libs.zip')
import mylib  # loads mylib.pye from libs.zip
```
Importing pyconcrete first is what installs the hook, and the plain import statement afterwards then resolves to the encrypted module inside the archive. This is the most convenient mode for teams that want to hand over a directory of modules without producing a single executable, and it keeps the same AES 128 protection as the other modes. The archive is not a .pyz in this case, just a zip, which means the layout can be a main.pye entry script next to a libs.zip file.

## The lib mode carries its own warning, and Windows support is partial

The documentation is unusually candid about one of its own modes. Using pyconcrete as a library instead of an executable is described as not safe enough, and the README tells you that if you want to work this way you need to understand the potential risk. The distinction is real: in lib mode the native module and its key sit inside your project directory, visible to whoever receives it. Two other limits are worth stating before you commit. On Windows the support is described as limited and only tested for a partial environment, specifically Windows 11 with Visual Studio 2022 and the Windows 11 SDK. On unix systems the stated requirements are pkg-config, build-essential and the matching python development package, plus pip 23.1 or newer. And the project is honest about a specific attack surface that the encryption does not address. A .pye file protects the bytes you ship. It does not stop someone who can run the program, with the binary in hand, from reading the decrypted code out of memory. Anyone promising protection against a determined reverse engineer is overstating what this tool does.

## Apache-2.0 licensing, meson build, and the release cadence

pyconcrete is licensed under Apache-2.0, with the license file referenced from pyproject.toml and the license identifier also carried in the project classifiers. That is a permissive licence, so embedding the tool in a commercial product does not oblige you to publish modifications to it, though the notice and licence text must travel with the distribution. The packaging metadata also tells you which Python versions the project claims to support. The classifiers list Python 3.7 through 3.13 and the CPython implementation only, while the README adds that versions before 3.6 are no longer supported and that 0.15.2 and earlier are the ones to use for 3.6 or 2.7. The build backend moved to meson-python at version 1.0.0, requiring meson 1.1.0 or newer, which is also why no wheels are published. On timing, the last push was on 2026-04-13 and the recent releases were v1.3.0 on 2026-03-10, v1.2.0 on 2025-09-01 and v1.1.0 on 2025-07-24. Roughly five to seven months between releases suggests a project you should vendor rather than track blindly. Testing is set up in two ways, pytest for local runs and a justfile target that runs the suite in a Docker environment across the listed Python versions from 3.7 to 3.13.

## Conclusion

Adopt pyconcrete if you ship Python to someone else's machine and need the source bytes to be unreadable in the package, and you are willing to build the C extension and manage a passphrase yourself. Skip it if you need a real security boundary, since the key is in a binary on the client side, or if your environment is a locked down Windows setup that the documentation only covers partially. Verify first that your Python version is covered by the classifier list in pyproject.toml, that pip is 23.1 or newer so that --config-settings is accepted, and that the passphrase you choose produces the same key on every machine that must decrypt your files.

## FAQ

### What does pyconcrete actually encrypt?

It encrypts compiled .pyc bytecode into .pye files, not your .py source. A .pye file cannot be run by a normal python invocation, it needs the pyconcrete executable to decrypt it first.

### How do I install pyconcrete?

No pre-built packages are provided, so you build it with pip 23.1 or newer and pass your passphrase as a build argument using --config-settings=setup-args="-Dpassphrase=<Your_Passphrase>". You should add --no-cache-dir so pip does not reuse a wheel compiled with an older passphrase.

### What encryption does pyconcrete use?

Only AES 128 bit, and the encrypt and decrypt work is done by the OpenAES library. The secret key is generated from your passphrase at build time and is embedded in the native _pyconcrete module rather than being read from a file at runtime.

### Can I ship encrypted modules inside a zip file?

Yes. The PyeZipImporter hook finds and decrypts .pye modules inside any zip archive placed on sys.path, and the pyecli build-zip command packages a source directory into a .pyz archive that the pyconcrete executable can run directly.

### Is using pyconcrete as a library mode safe?

The README states that this mode is not safe enough and that you need to understand the potential risk before using it. In that mode the native module and its key sit inside the project you hand over, so the protection is weaker than the executable mode.

## Sources

- [Falldog/pyconcrete on GitHub](https://github.com/Falldog/pyconcrete)
- [Issues](https://github.com/Falldog/pyconcrete/issues)
- [License: Apache-2.0](https://github.com/Falldog/pyconcrete/blob/master/LICENSE)
- [README](https://github.com/Falldog/pyconcrete/blob/master/README.md)
- [Releases](https://github.com/Falldog/pyconcrete/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/falldog-pyconcrete
