mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-04-23 15:16:00 +00:00
## Summary
Adds `corescope-decrypt` — a standalone CLI tool that decrypts and
exports MeshCore hashtag channel messages from a CoreScope SQLite
database.
### What it does
MeshCore hashtag channels use symmetric encryption with keys derived
from the channel name. The CoreScope ingestor stores **all** GRP_TXT
packets, even those it can't decrypt. This tool enables retroactive
decryption — decrypt historical messages for any channel whose name you
learn after the fact.
### Architecture
- **`internal/channel/`** — Shared crypto package extracted from
ingestor logic:
- `DeriveKey()` — `SHA-256("#name")[:16]`
- `ChannelHash()` — 1-byte packet filter (`SHA-256(key)[0]`)
- `Decrypt()` — HMAC-SHA256 MAC verify + AES-128-ECB
- `ParsePlaintext()` — timestamp + flags + "sender: message" parsing
- **`cmd/decrypt/`** — CLI binary with three output formats:
- `--format json` — Full metadata (observers, path, raw hex)
- `--format html` — Self-contained interactive viewer with search/sort
- `--format irc` (or `log`) — Plain-text IRC-style log, greppable
### Usage
```bash
# JSON export
corescope-decrypt --channel "#wardriving" --db meshcore.db
# Interactive HTML viewer
corescope-decrypt --channel wardriving --db meshcore.db --format html --output wardriving.html
# Greppable log
corescope-decrypt --channel "#wardriving" --db meshcore.db --format irc | grep "KE6QR"
# From Docker
docker exec corescope-prod /app/corescope-decrypt --channel "#wardriving" --db /app/data/meshcore.db
```
### Build & deployment
- Statically linked (`CGO_ENABLED=0`) — zero dependencies
- Added to Dockerfile (available at `/app/corescope-decrypt` in
container)
- CI: builds and tests in go-test job
- CI: attaches linux/amd64 and linux/arm64 binaries to GitHub Releases
on tags
### Testing
- `internal/channel/` — 9 tests: key derivation, encrypt/decrypt
round-trip, MAC rejection, wrong-channel rejection, plaintext parsing
- `cmd/decrypt/` — 7 tests: payload extraction, channel hash
consistency, all 3 output formats, JSON parseability, fixture DB
integration
- Verified against real fixture DB: successfully decrypts 17
`#wardriving` messages
### Limitations
- Hashtag channels only (name-derived keys). Custom PSK channels not
supported.
- No DM decryption (asymmetric, per-peer keys).
- Read-only database access.
Fixes #723
---------
Co-authored-by: you <you@example.com>
143 lines
4.3 KiB
Markdown
143 lines
4.3 KiB
Markdown
# corescope-decrypt
|
|
|
|
Standalone CLI tool to decrypt and export MeshCore hashtag channel messages from a CoreScope SQLite database.
|
|
|
|
## Why
|
|
|
|
MeshCore hashtag channels use symmetric encryption where the key is derived deterministically from the channel name. The CoreScope ingestor stores **all** `GRP_TXT` packets in the database, including those it cannot decrypt at ingest time.
|
|
|
|
This tool enables:
|
|
|
|
- **Retroactive decryption** — decrypt historical messages for any channel whose name you learn after the fact
|
|
- **Forensics & analysis** — export channel traffic for offline review
|
|
- **Bulk export** — dump an entire channel's history as JSON, HTML, or plain text
|
|
|
|
## Installation
|
|
|
|
### From Docker image
|
|
|
|
The binary is included in the CoreScope Docker image at `/app/corescope-decrypt`:
|
|
|
|
```bash
|
|
docker exec corescope-prod /app/corescope-decrypt --channel "#wardriving" --db /app/data/meshcore.db
|
|
```
|
|
|
|
### From GitHub release
|
|
|
|
Download the static binary from the [Releases](https://github.com/Kpa-clawbot/CoreScope/releases) page:
|
|
|
|
```bash
|
|
# Linux amd64
|
|
curl -LO https://github.com/Kpa-clawbot/CoreScope/releases/latest/download/corescope-decrypt-linux-amd64
|
|
chmod +x corescope-decrypt-linux-amd64
|
|
./corescope-decrypt-linux-amd64 --help
|
|
```
|
|
|
|
### Build from source
|
|
|
|
```bash
|
|
cd cmd/decrypt
|
|
CGO_ENABLED=0 go build -ldflags="-s -w" -o corescope-decrypt .
|
|
```
|
|
|
|
The binary is statically linked — no dependencies, runs on any Linux.
|
|
|
|
## Usage
|
|
|
|
```
|
|
corescope-decrypt --channel NAME --db PATH [--format FORMAT] [--output FILE]
|
|
```
|
|
|
|
Run `corescope-decrypt --help` for full flag documentation.
|
|
|
|
### JSON output (default)
|
|
|
|
Machine-readable, includes all metadata (observers, path hops, raw hex):
|
|
|
|
```bash
|
|
corescope-decrypt --channel "#wardriving" --db meshcore.db
|
|
```
|
|
|
|
```json
|
|
[
|
|
{
|
|
"hash": "a1b2c3...",
|
|
"timestamp": "2026-04-12T17:19:09Z",
|
|
"sender": "XMD Tag 1",
|
|
"message": "@[MapperBot] 37.76985, -122.40525 [0.3w]",
|
|
"channel": "#wardriving",
|
|
"raw_hex": "150206...",
|
|
"path": ["A3", "B0"],
|
|
"observers": [
|
|
{"name": "Observer1", "snr": 9.5, "rssi": -56, "timestamp": "2026-04-12T17:19:10Z"}
|
|
]
|
|
}
|
|
]
|
|
```
|
|
|
|
### HTML output
|
|
|
|
Self-contained interactive viewer — search, sortable columns, expandable detail rows:
|
|
|
|
```bash
|
|
corescope-decrypt --channel "#wardriving" --db meshcore.db --format html --output wardriving.html
|
|
open wardriving.html
|
|
```
|
|
|
|
No external dependencies. The JSON data is embedded directly in the HTML file.
|
|
|
|
### IRC / log output
|
|
|
|
Plain-text, one line per message — ideal for `grep`, `awk`, and piping:
|
|
|
|
```bash
|
|
corescope-decrypt --channel "#wardriving" --db meshcore.db --format irc
|
|
```
|
|
|
|
```
|
|
[2026-04-12 17:19:09] <XMD Tag 1> @[MapperBot] 37.76985, -122.40525 [0.3w]
|
|
[2026-04-12 17:20:25] <XMD Tag 1> @[MapperBot] 37.78075, -122.39774 [0.3w]
|
|
[2026-04-12 17:25:30] <mk 🤠> @[MapperBot] 35.32444, -120.62077
|
|
```
|
|
|
|
```bash
|
|
# Find all messages from a specific sender
|
|
corescope-decrypt --channel "#wardriving" --db meshcore.db --format irc | grep "KE6QR"
|
|
```
|
|
|
|
## How channel encryption works
|
|
|
|
MeshCore hashtag channels derive their encryption key from the channel name:
|
|
|
|
1. **Key derivation**: `AES-128 key = SHA-256("#channelname")[:16]` (first 16 bytes)
|
|
2. **Channel hash**: `SHA-256(key)[0]` — 1-byte identifier in the packet header, used for fast filtering
|
|
3. **Encryption**: AES-128-ECB
|
|
4. **MAC**: HMAC-SHA256 with a 32-byte secret (key + 16 zero bytes), truncated to 2 bytes
|
|
5. **Plaintext format**: `timestamp(4 LE) + flags(1) + "sender: message\0"`
|
|
|
|
See the firmware source at `firmware/src/helpers/BaseChatMesh.cpp` for the canonical implementation.
|
|
|
|
## Testing against the fixture DB
|
|
|
|
```bash
|
|
cd cmd/decrypt
|
|
go test ./...
|
|
|
|
# Manual test with the real fixture:
|
|
go run . --channel "#wardriving" --db ../../test-fixtures/e2e-fixture.db --format irc
|
|
```
|
|
|
|
The shared crypto library also has independent tests:
|
|
|
|
```bash
|
|
cd internal/channel
|
|
go test -v ./...
|
|
```
|
|
|
|
## Limitations
|
|
|
|
- **Hashtag channels only.** Only channels where the key is derived from `SHA-256("#name")` are supported. Custom PSK channels require the raw key (not implemented).
|
|
- **No DM decryption.** Direct messages (`TXT_MSG`) use per-peer asymmetric encryption and cannot be decrypted by this tool.
|
|
- **Read-only.** The tool opens the database in read-only mode and never modifies it.
|
|
- **Timestamps are UTC.** The sender's embedded timestamp is used when available, displayed in UTC.
|