Skip to main content

scramblenames

Obfuscate Python source code by scrambling variable, function, and class names into meaningless eight-character identifiers, while keeping the program working exactly the same. Comments and docstrings are removed by default too. The point is to strip every human-readable clue from a program without changing what it does.

It does not un-scramble — this is a one-way transform.

Install

pip install scramblenames

scramblenames uses libcst to preserve formatting (and comments/docstrings when you choose to keep them).

Library usage

from scramblenames import scramble

source = '''
def add(a, b):
    total = a + b
    return total
'''

print(scramble(source, seed=1))
def gFb51yBM(iK2ZWeqh, FWCEPyYn):
    WXaSCrUZ = iK2ZWeqh + FWCEPyYn
    return WXaSCrUZ

scramble() accepts the code as a string, a pathlib.Path, or a string filename of an existing file:

from pathlib import Path
scramble(Path("mymodule.py"))     # read from a Path
scramble("mymodule.py")           # read from a filename
scramble("x = 1\n")               # scramble a code string directly

Scrambling a whole directory

scramble_tree() scrambles every matching file under a directory, mirroring the tree into a destination directory:

from scramblenames import scramble_tree

scramble_tree("myproject/", "obfuscated/", import_safe=True)

Each file is scrambled independently, so use import_safe=True for multi-file packages — otherwise a public name renamed in one module wouldn't be matched at its import site in another. Use include/exclude globs and recursive=False to control which files are processed.

Errors

Source that can't be parsed raises scramblenames.ScrambleError, carrying the filename, line, and column of the problem when available.

Options

scramble(
    source,
    import_safe=False,       # keep top-level function/class/global names
    strip_comments=True,     # remove all comments
    strip_docstrings=True,   # remove module/function/class docstrings
    style="random",          # "random" | "hex" | "reorder"
    seed=None,               # int for reproducible output
)
Option Effect
import_safe=True Assume the file may be imported by other code. Module-level function, class, and global names are kept; only function-local names are scrambled.
import_safe=False (default) Also scramble module-level function, class, and global names (except those in __all__). Best for standalone scripts.
strip_comments=True (default) Remove every comment. A first-line shebang (#!...) and a PEP 263 encoding cookie are always kept. Set False to keep all comments.
strip_docstrings=True (default) Remove the leading docstring of the module and of every function and class (an empty body becomes pass). Set False to keep them.
style="random" Eight random alphanumeric characters, first is always a letter.
style="hex" Eight hexadecimal characters, first is always a letter (af).
style="reorder" A permutation of the original name's own characters.

Command line

scramblenames mymodule.py                       # scramble to stdout (strips prose)
scramblenames mymodule.py -o out.py             # write to a file
scramblenames mymodule.py --import-safe         # keep the public API
scramblenames mymodule.py --keep-prose          # keep comments + docstrings
scramblenames mymodule.py --style hex --seed 7  # reproducible hex names
cat mymodule.py | scramblenames                 # read from stdin

# scramble a whole project into a mirrored output directory
scramblenames myproject/ -d obfuscated/ --import-safe
scramblenames src/ -d out/ --exclude '*_test.py' --no-recursive

Scrambling a directory (or more than one file) requires -d/--output-dir, which mirrors the input tree. The paths written are listed on stderr. A parse error prints a clean message and exits non-zero.

What is and isn't scrambled

Every generated name is a valid Python identifier and never a keyword, so the output is always syntactically valid and behaves identically to the input.

Scrambled

  • Local variables inside functions.
  • for, with ... as, except ... as, and walrus targets.
  • Comprehension and generator variables.
  • Module-level functions, classes, and globals — only when import_safe=False.
  • Nested function and class names (they are local to their enclosing function).
  • Parameters that can't be reached by keyword — positional-only params, *args, and **kwargs — always.
  • Other parameters, when the function is safe to analyze: it's local (its own name is scrambled too), undecorated, never passed around or stored, and never called with ** unpacking. Every keyword argument at its call sites is rewritten to match (f(name=1)f(x7Kd2p9q=1)).

Never scrambled (renaming these could break the program)

  • Attributesx.foo, self.foo, and the string entries in __slots__, since the object's type generally can't be proven.
  • Method names — reached by attribute access, like other attributes.
  • Method and public-API parameters — outside callers may pass them by keyword, so keyword-eligible parameters of methods, of the public API, and of functions that escape the module are kept.
  • Imported names and builtins.
  • Names listed in __all__, and dunder names like __init__.

Because attributes are preserved and parameters are only renamed when all call sites are visible, code that relies on dynamic access — getattr/setattr with computed strings, locals(), **kwargs forwarding by string key — keeps working.

Development

python -m venv .venv && source .venv/bin/activate
pip install -e ".[test]"
pytest

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

scramblenames-0.1.0.tar.gz (24.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

scramblenames-0.1.0-py3-none-any.whl (17.3 kB view details)

Uploaded Python 3

File details

Details for the file scramblenames-0.1.0.tar.gz.

File metadata

  • Download URL: scramblenames-0.1.0.tar.gz
  • Upload date:
  • Size: 24.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.8

File hashes

Hashes for scramblenames-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6b3e66dc98ecfd9f13af846b4e6908c6f813c0a6474d05c0f93315cbcb435ded
MD5 f166d3494bc20f37d112f5c03555f1d0
BLAKE2b-256 cecd8f7fa06204762fa3e5747a3346d6ede6a4b564ed69e7b94e19c4fae62c5d

See more details on using hashes here.

File details

Details for the file scramblenames-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: scramblenames-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.8

File hashes

Hashes for scramblenames-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 42645dbc3f618bfd808b9545a760b1c8ead9200f3e15d7f44867e725e5d9b3ee
MD5 16ccf4daa87d206be2928576bf02bd6a
BLAKE2b-256 2deaaf6b87face20ee9340b67a0d42a326f4d6c85fd653bad9f452b046dedd4f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page