How Bitchat's Binary Protocol Uses PKCS#7 Padding for Traffic Analysis Protection
The Bitchat binary protocol applies PKCS#7 padding exclusively to Noise-encrypted frames, rounding payloads into fixed cryptographic buckets (256 B, 512 B, 1 KiB, or 2 KiB) to prevent adversaries from inferring exact plaintext lengths through packet size observation.
The permissionlesstech/bitchat repository implements a privacy-focused messaging system where metadata resistance is critical. By padding encrypted payloads using the PKCS#7 scheme, the protocol ensures that network observers only see standardized packet sizes rather than the precise length of underlying message content, directly addressing traffic analysis vulnerabilities.
Fixed-Size Buckets and Block Selection
Traffic analysis attacks exploit packet length metadata to infer communication patterns. To neutralize this signal, Bitchat categorizes all Noise-encrypted frames into discrete size classes, or "buckets."
The protocol defines four specific bucket sizes: 256 bytes, 512 bytes, 1 KiB, and 2 KiB. The MessagePadding.optimalBlockSize(for:) method computes the smallest bucket capable of containing the encrypted payload plus its mandatory 16-byte AES-GCM authentication tag【MessagePadding.swift†L55-L63】. This deterministic sizing ensures that no padded frame exceeds the next standard tier, collapsing variance into four observable categories.
PKCS#7 Implementation in MessagePadding.swift
The core padding logic resides in MessagePadding.swift, handling both padding creation and cryptographically secure validation.
Padding Logic and Byte Pattern
The MessagePadding.pad(_:toSize:) function implements classic PKCS#7 padding through three precise steps:
- It verifies the payload fits within the target bucket.
- It calculates
paddingNeeded = targetSize - data.count. - It appends
paddingNeededcopies of the byteUInt8(paddingNeeded)to the data【MessagePadding.swift†L26-L38】.
This produces a padding block where every byte value equals the total padding length. For instance, 10 bytes of padding adds ten sequential 0x0A bytes to the payload, creating a recognizable pattern that supports deterministic removal.
Unpadding and Tamper Detection
After decryption, MessagePadding.unpad(_:) reads the final byte to determine the claimed padding length. It then validates that the last N bytes all match this value before stripping them【MessagePadding.swift†L40-L51】. Mismatched bytes trigger immediate rejection, protecting against padding oracle attacks and malformed packet injection.
Protocol Constraints and Trade-offs
The BitchatProtocol.swift header explicitly documents that "PKCS#7-style padding for privacy" applies only to Noise frames, while other packet types travel at natural length【BitchatProtocol.swift†L29-L31】【WHITEPAPER.md†L49-L53】.
A critical limitation stems from PKCS#7's single-byte length encoding: the maximum padding is 255 bytes. If a payload requires more than 255 bytes to reach the next bucket, the frame is transmitted unpadded. This caps bucket sizes at predefined intervals and represents a deliberate bandwidth-versus-privacy trade-off.
Practical Implementation Example
The following Swift code demonstrates the complete padding workflow from bucket selection to transmission:
import Foundation
import BitFoundation
// 1️⃣ Determine the optimal bucket for an encrypted payload
let payload = Data([0x01, 0x02, 0x03]) // Example encrypted data
let bucket = MessagePadding.optimalBlockSize(for: payload.count) // Returns 256
// 2️⃣ Apply PKCS#7 padding to reach the exact bucket size
let padded = MessagePadding.pad(payload, toSize: bucket)
// 3️⃣ Transmit the padded frame via the Noise-encrypted packet type
// (BitchatProtocol encoder handles the packet wrapping)
// 4️⃣ On reception, remove padding after decryption
let received = padded
let unpadded = MessagePadding.unpad(received) // Returns original payload
Summary
- Fixed buckets: Noise frames are rounded to 256 B, 512 B, 1 KiB, or 2 KiB using PKCS#7 padding.
- Single-byte encoding: Each pad byte contains the length value, limiting maximum padding to 255 bytes.
- Selective application: Only end-to-end encrypted Noise frames are padded; other traffic uses natural lengths.
- Validation: The
unpadmethod verifies padding consistency to detect tampering. - Trade-off: Payloads requiring >255 bytes of padding transmit unpadded to avoid excessive overhead.
Frequently Asked Questions
What is PKCS#7 padding and how does it differ from other padding schemes?
PKCS#7 is a cryptographic padding scheme where every added byte contains the same value—the number of padding bytes appended. Unlike zero-padding or random padding, PKCS#7 allows deterministic removal by reading the last byte as an unambiguous length indicator, ensuring reliable recovery of the original message without separator bytes.
Why does Bitchat only pad Noise-encrypted frames and not other packet types?
The protocol intentionally limits padding to Noise-encrypted frames—the packets carrying end-to-end encrypted payloads—because these contain the sensitive content requiring privacy protection. Control packets and handshake messages travel at natural length to minimize bandwidth overhead while keeping the encryption layer's metadata obfuscated【WHITEPAPER.md†L49-L53】.
What happens if a payload requires more than 255 bytes of padding?
If the gap between the encrypted payload (plus the 16-byte tag) and the next bucket exceeds 255 bytes, the frame is emitted unpadded. This occurs because PKCS#7 uses a single byte to encode padding length, capping the value at 0xFF (255). This represents a protocol trade-off that prioritizes reasonable overhead over perfect privacy for payloads near bucket boundaries.
How effective is PKCS#7 padding against traffic analysis attacks?
The padding reduces granularity by collapsing all payload sizes into four discrete buckets, forcing observers to distinguish between coarse size classes rather than exact byte counts. While effective against precise length inference, sophisticated adversaries may still analyze timing patterns or frequency distributions within these buckets to infer communication metadata.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →