Files
meshcore-spec/versions/v0.1.0/14-crypto.md
T
swaitsandClaude Opus 4.7 f83d4088e3 spec: link corpus references to GitHub, version-aware
Every "Test vectors: \`corpus/...\`" mention in the spec is now a
markdown link pointing at the corresponding file or directory on
GitHub. Each version build resolves its links to the correct git
ref:

  - /latest/ -> https://github.com/swaits/meshcore-spec/tree/main/corpus/...
  - /v0.1.0/ -> https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/...
  - future /vX.Y.Z/ -> /tree/vX.Y.Z/corpus/...

Two changes:

1. src/*.md (18 files): the inline-code corpus paths in each
   "Cross-References" section are now markdown links. Directories
   point at /tree/main/corpus/..., individual .json files point at
   /blob/main/corpus/.... CHANGELOG and other non-corpus main links
   are intentionally left pointing at main — the canonical history
   lives there regardless of which version you're reading.

2. justfile: `cut-version` now also runs a sed over the snapshot's
   *.md files replacing /tree/main/corpus -> /tree/<VERSION>/corpus
   and /blob/main/corpus -> /blob/<VERSION>/corpus. So a freshly-cut
   version is self-consistent.

versions/v0.1.0/* regenerated by re-running
`just cut-version v0.1.0` after the new src/ links + recipe sed
landed.

Tagging the release commit (`git tag v0.1.0`) is now REQUIRED — not
"optional" — because the rendered /v0.1.0/ pages reference
/tree/v0.1.0/corpus/... on GitHub. Without the tag the links 404.
cut-version's output now says so.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:35:09 -06:00

121 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MeshCore Protocol Specification
## Section 14: Cryptography
### Overview
MeshCore uses a combination of symmetric and asymmetric cryptography. Data
confidentiality is provided by AES-128 in ECB mode with zero padding. Data
integrity is provided by HMAC-SHA256 truncated to 2 bytes. These are combined
in an encrypt-then-MAC scheme.
### Algorithms
| Operation | Algorithm | Key Size | Output |
|-----------|-----------|----------|--------|
| Symmetric Encryption | AES-128-ECB | 16 bytes | Multiple of 16 bytes |
| Message Authentication | HMAC-SHA256 | 32 bytes | 2 bytes (truncated) |
| Digital Signature | Ed25519 | 32/64 bytes | 64 bytes |
| Key Exchange | X25519 (ECDH) | 32 bytes | 32 bytes |
| Hashing | SHA-256 | — | Up to 32 bytes |
### AES-128-ECB Encryption
MeshCore uses AES-128 in Electronic Codebook (ECB) mode with zero-byte padding.
**Key**: The first 16 bytes (CIPHER_KEY_SIZE) of the shared secret.
**Encryption**:
1. Set the AES-128 key to `shared_secret[0..15]`.
2. Process the plaintext in 16-byte blocks:
a. For each complete 16-byte block, encrypt in place.
b. For the final partial block (< 16 bytes):
- Copy the remaining bytes to a 16-byte buffer.
- Fill remaining bytes with zeros.
- Encrypt the padded block.
3. If the plaintext is empty, encrypt a 16-byte all-zero block.
4. The output is always a multiple of 16 bytes.
**Decryption**:
1. Set the AES-128 key to `shared_secret[0..15]`.
2. Process the ciphertext in 16-byte blocks, decrypting each.
3. The output length equals the ciphertext length.
4. The final block MAY contain trailing zero bytes from padding.
The application must know the original plaintext length or use
a length-prefixed format to determine where meaningful data ends.
### HMAC-SHA256 (Truncated to 2 Bytes)
MeshCore computes HMAC-SHA256 over the ciphertext and truncates the result to
CIPHER_MAC_SIZE (2) bytes.
**Key**: The full 32 bytes (PUB_KEY_SIZE) of the shared secret.
Note: The HMAC key uses the full 32-byte shared secret, while the AES key uses
only the first 16 bytes. This is an important distinction.
**Computation**:
```
mac = HMAC-SHA256(key=shared_secret[0..31], message=ciphertext)[0..1]
```
The first 2 bytes of the HMAC-SHA256 output are used as the MAC.
### Encrypt-then-MAC
The encrypt-then-MAC scheme combines AES-128-ECB encryption with HMAC-SHA256
authentication:
**Encoding** (`encryptThenMAC`):
1. Encrypt the plaintext using AES-128-ECB (see above).
Store ciphertext at `dest[2..]` (offset by MAC size).
2. Compute HMAC-SHA256 over the ciphertext using the full 32-byte shared
secret. Truncate to 2 bytes.
3. Store the 2-byte MAC at `dest[0..1]`.
4. Return total length: 2 + ciphertext_length.
Output format:
```
[MAC (2 bytes)][Ciphertext (N × 16 bytes)]
```
**Decoding** (`MACThenDecrypt`):
1. If input length ≤ 2 (CIPHER_MAC_SIZE), return failure (invalid).
2. Recompute HMAC-SHA256 over `src[2..]` (the ciphertext portion) using the
full 32-byte shared secret. Truncate to 2 bytes.
3. Compare the computed MAC with `src[0..1]`.
4. If MACs do not match, return failure (0 = invalid HMAC).
5. If MACs match, decrypt `src[2..]` using AES-128-ECB.
6. Return the decrypted plaintext length.
### Security Notes
- **ECB mode**: AES-ECB encrypts each 16-byte block independently. Identical
plaintext blocks produce identical ciphertext blocks. This is a known
weakness of ECB mode, but MeshCore's short payloads and mesh-network context
make this acceptable for its use case.
- **2-byte MAC**: The truncated HMAC provides only 16 bits of authentication.
This means there is approximately a 1-in-65536 chance of a forged MAC being
accepted. This trade-off prioritizes bandwidth over authentication strength.
- **Key derivation**: The shared secret is used directly as both AES key
(first 16 bytes) and HMAC key (full 32 bytes). No key derivation function
(KDF) is applied.
### Cross-References
- [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Usage in packet payloads
- [Section 15: Identity](15-identity.md) — Shared secret computation via ECDH
- Test vectors: [`corpus/crypto/`](https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/crypto/)
### Reference Implementation
- `Utils::encrypt()` in `src/Utils.cpp` — AES-128-ECB encryption
- `Utils::decrypt()` in `src/Utils.cpp` — AES-128-ECB decryption
- `Utils::encryptThenMAC()` in `src/Utils.cpp` — Encrypt-then-MAC
- `Utils::MACThenDecrypt()` in `src/Utils.cpp` — MAC-then-Decrypt