Three paths, and they are not variations on one install. Each needs a different set of things from the machine, and the differences are large enough that picking the wrong one costs an afternoon.
| Needs | Does not need | Runs on | |
|---|---|---|---|
| Docker | Docker | anything else | anywhere Docker runs |
| The offline analyser | Python 3.10+ | root, eBPF, a kernel, a database, an interface | macOS, Linux, Windows, BSD |
| The live sensor | Linux 5.8+, bcc, kernel headers, root, MongoDB | — | Linux only |
The question that decides it is whether you have a capture file or a network. A capture file is answered by the first two, and the offline analyser reads more out of a capture than the live sensor could ever have seen (see architecture.md). A live network — a continuous series rather than a snapshot — is the only reason to take the third path.
Nothing to install but Docker itself, and three images to choose from.
docker/README.md is the reference; this is the
thirty-second version.
# analyse a capture: 164MB, non-root, read-only filesystem, no network
docker build -f docker/Dockerfile.offline -t cryptomon-offline .
docker run --rm --network none --read-only \
-v "$PWD:/captures:ro" cryptomon-offline /captures/your-capture.pcap
# the API, the upload UI, the dashboard, and a MongoDB beside them
cd docker
cp env.example .env # then put a real password in MONGO_PASSWORD
docker compose up --build # http://127.0.0.1:8000/
# does eBPF work on this host at all?
docker build -f docker/Dockerfile.sensor -t cryptomon-sensor .
docker run --rm --cap-add BPF cryptomon-sensorThat last command is issue #26
made answerable: it compiles the kernel filter and puts it past the verifier
in both attach modes, touching no interface and no database. If it prints
OK compiles and two OK verifies, the kernel half of CryptoMon works on
this machine and anything still wrong is configuration.
Not verified on this machine. The Docker commands above are quoted from
docker/README.md, which records where and when they were checked. Building an image needs the network, and these pages were written with none.
The password that ships in docker/compose.yaml is
change-me-this-is-not-a-password — a placeholder written out in full so it
cannot be mistaken for a generated secret. Both published ports bind
127.0.0.1.
The path most people want. It reads a capture file, needs no privileges and touches no database.
git clone https://github.com/Santandersecurityresearch/CryptoMon.git
cd CryptoMon
pip install cryptography
python -m pcapscan your-capture.pcapPython 3.10 is the floor. cryptography is the only third-party package
involved, and it does exactly one job: parsing the X.509 chain out of the
handshake. Everything else — reading pcap and pcapng, reassembling TCP,
walking TLS records, classifying algorithms, writing NDJSON, CSV, JSON and a
CycloneDX CBOM — is the standard library. This was checked by blocking the
import of every other package in requirements.txt and running all five
output formats; they all work.
Without cryptography the analyser still runs, and the certificate section
of the report is silently empty rather than absent. That failure has its
own entry in troubleshooting.md
because an estate with no certificate keys and an estate whose certificates
could not be parsed look identical in the output.
pip install -r requirements.txt installs the rest of the project too —
FastAPI, motor, uvicorn, scapy, TinyDB — and is what you want if you also
intend to run the service. It is not needed to analyse a capture.
Recent distributions mark the system Python as externally managed (PEP 668)
and refuse pip install into it:
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txtLinux only, root, and a kernel that will load a BPF program. This is the half of CryptoMon that watches an interface instead of reading a file.
| Linux kernel | 5.8 or newer, so CAP_BPF and CAP_PERFMON exist as their own capabilities. Older kernels can only be given CAP_SYS_ADMIN. |
bcc |
From the distribution — bpfcc-tools, which pulls in python3-bpfcc. Not the PyPI package called bcc, which is something else entirely. |
| Kernel headers | Matching the kernel that is actually running, because bcc compiles the program against them at load time. |
pyroute2 |
python3-pyroute2, and only for the -tc traffic-control attach mode. |
| MongoDB | The sensor writes one document per parsed frame and has nowhere else to put them. |
| root | Or the capability set in docker/README.md. |
bcc and pyroute2 are deliberately absent from requirements.txt. They
are Linux-only and come from the distribution, and keeping them out is what
lets the test suite and the offline tools import on a machine that has
neither.
The repository ships a 40-line apt-get script, and the README has always
said it "will install all the necessary files". Read it before you run it.
It has not aged well, and on Ubuntu 24.04 "Noble Numbat" — the release the
README names — several of its lines fail.
sudo ./ubuntu-setup.shWhat is in it, and what to expect:
- It has no
set -eand no root check. Every line that fails is passed over in silence and the script exits 0 regardless. That matters because several lines do fail. linux-tools-5.15.0-41-genericis pinned to one specific kernel. That is a 5.15 kernel — Ubuntu 22.04's. On 24.04, whose kernel is 6.8, the package does not exist and the line fails. Nothing downstream needs it; the laterlinux-tools-$(uname -r)line is the one that matters.llvm-12,clang-12andclang-format-12are pinned to an LLVM release that 24.04 does not carry. Where those packages are absent the three lines fail, and then the loop below them that strips the-12suffix runswhich clang-12on nothing and handsln -san empty path. Harmless, noisy, and confusing to read in the output. Unversionedclangandllvmare installed by the second line of the script regardless.sudo snap install --devmode bpftracecarries a# TODO - find out why this doesn't workbeside it. It has not worked and it does not need to: nothing in this repository uses bpftrace. It can be ignored.apt-get install bpfcc-tools linux-headers-$(uname -r)has no-y, unlike every other line, so it prompts. It is also the line that fails inside a container or on a host whose running kernel has no matching headers package — the single most common way the sensor fails to start.linux-headers-genericis what the project's own container images install, for exactly that reason.- It installs Python packages from
apt, not fromrequirements.txt:python3-scapy,python3-motor,python3-psutil,python3-pyroute2,python3-pymongo,python3-fastapi,python3-tinydb. Those are the distribution's versions, which are not the versionsrequirements.txtpins. - It does not install everything the API needs.
uvicorn,jinja2,python-multipartandcryptographyare inrequirements.txtand are not in this script, sopython api.pywill not start after running it alone. - It adds MongoDB 7.0 from the
jammy(22.04) repository regardless of what you are running. - The final
python3 -m pip install jcwill be refused on a PEP 668 distribution.jcis used by one function on the live path (cryptomon.utils.cert_guess), which prints a message and returns nothing when it is missing. The apt line above it usually covers it.
A reasonable reading of all that: run it for the eBPF toolchain, then install the Python side properly.
sudo ./ubuntu-setup.sh # the apt/eBPF half
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt # the pinned Python halfNot verified on this machine. Everything in this section is read from
ubuntu-setup.sh, the package lists of the releases it names, and the project's own commit history. It was not executed: these pages were written on macOS, with no network.
The sensor needs somewhere to write. Create the database and a user for it:
mongoshuse cryptomon
db.createCollection('cryptomon')
db.createUser({user: "cryptomonUser", pwd: passwordPrompt(),
roles: [{ role: "readWrite", db: "cryptomon" }]})Then point CryptoMon at it. The variable names carry no prefix — see configuration.md:
export DB_URL="mongodb://cryptomonUser:<password>@127.0.0.1:27017/cryptomon?retryWrites=true&w=majority"
export DB_NAME="cryptomon"Or, for Atlas or another hosted service:
export DB_URL="mongodb+srv://<connection-url>/cryptomon?retryWrites=true&w=majority"
export DB_NAME="cryptomon"Worth giving the sensor its own user with write access only. The sensor and the API have very different exposure, and there is no reason a compromise of one should hand over the other's credential.
sudo python3 ./cryptomon.py -i eth0 # the sensor
python3 ./api.py # the API, in another shellSee live-sensor.md for what it sees, and
deploy/README.md for running both as systemd services
behind nginx. create-service.sh installs the units and deliberately starts
neither, so that you can read them and put the database password in place
first.
python -m pcapscan tests/fixtures/streams/tls13_hello_retry.pcapThis is a committed 24-packet capture of one real handshake, and it exercises the whole offline path — reader, reassembler, record walker, TLS parser, certificate handling and the classification table. It should print one session, one classical key exchange, and one post-quantum offer that the server refused.
python -m pytest -m smoke # ~5s, no captures, no database, no network
python -m pytest # everything, ~12sSee tests/README.md for what the two sets cover and why they are split.