How Bitchat Implements PKCS#7-Style Padding for Privacy Within Noise Frames

Bitchat pads encrypted Noise packets to fixed bucket sizes using PKCS#7-style padding, ensuring each added byte equals the total pad length to hide message sizes from network observers.

The Bitchat messenger (permissionlesstech/bitchat) uses the Noise protocol for encrypted transport, but raw ciphertext lengths can leak metadata about conversation patterns. To close this privacy side-channel, the implementation applies PKCS#7-style padding to Noise frames, rounding payloads up to predetermined bucket sizes so that all encrypted packets appear uniform on the wire.

How PKCS#7 Padding Works in Bitchat

The padding logic lives in MessagePadding.swift within the local BitFoundation package. The algorithm follows the classic PKCS#7 specification where every padding byte holds the value of the padding length.

Calculating and Applying Pad Bytes

When preparing a Noise frame for transmission, the MessagePadding.pad(to:targetSize:) function receives the current payload length and a target bucket size (e.g., 256 B, 512 B, 1024 B, or 2048 B). It computes padLen = targetSize - data.count. If the payload already matches the bucket exactly, no padding is added. Otherwise, the function creates a Data array containing UInt8(padLen) repeated padLen times and appends it to the original payload, producing a frame that exactly matches the bucket size.

Validation on Receipt

Upon receiving a padded frame, MessagePadding.unpad(_:) extracts the final byte to determine the expected padding length, then verifies that the last N bytes all equal N. If any byte deviates, the frame is rejected as malformed according to the comment "Verify PKCS#7: all last N bytes equal to pad length" in /localPackages/BitFoundation/Sources/BitFoundation/MessagePadding.swift.

Which Packets Get Padded

Not all traffic receives padding. According to the protocol specification in BitchatProtocol.swift, only encrypted Noise traffic is obscured:

  • noiseEncrypted packets are padded to the nearest bucket.
  • noiseHandshake packets are padded to the nearest bucket.
  • All other message types transmit at their natural length.

This selective approach balances privacy with bandwidth efficiency, ensuring that the high-value encrypted payloads blend into uniform size classes while control messages remain lightweight.

Bucket Sizes and the 255-Byte Limit

The whitepaper defines four privacy buckets: 256, 512, 1024, and 2048 bytes. However, PKCS#7 encodes the pad length in a single byte, limiting the maximum padding to 255 bytes. Consequently, Bitchat implements a hard rule: if a frame requires more than 255 bytes of padding to reach its target bucket, it is emitted unpadded. This prevents overflow in the length byte while still protecting the majority of short-to-medium messages.

Code Examples

Adding padding before sending a Noise frame

import BitFoundation

func paddedNoisePayload(_ payload: Data, bucketSize: Int) throws -> Data {
    // `MessagePadding.pad` adds PKCS‑7 padding to reach `bucketSize`.
    return try MessagePadding.pad(payload, to: bucketSize)
}

// Example – pad a 120‑byte payload to the 256‑byte bucket
let raw = Data(repeating: 0xA5, count: 120)
let padded = try paddedNoisePayload(raw, bucketSize: 256)
// `padded.count` is now 256, last 136 bytes are the value 0x88 (136)

Removing padding after receiving a Noise frame

import BitFoundation

func unpaddedNoisePayload(_ padded: Data) throws -> Data {
    // `MessagePadding.unpad` verifies PKCS‑7 padding and strips it.
    return try MessagePadding.unpad(padded)
}

// Example – strip the padding we added earlier
let original = try unpaddedNoisePayload(padded)
// `original` matches the initial 120‑byte payload

These utilities are invoked by the Noise transport layer in bitchat whenever assembling or disassembling encrypted packets.

Summary

  • Bitchat implements PKCS#7-style padding in MessagePadding.swift to hide ciphertext lengths within Noise frames.
  • Only noiseEncrypted and noiseHandshake packets are padded to fixed buckets (256, 512, 1024, or 2048 bytes).
  • The padding algorithm sets every pad byte equal to the total pad length, allowing deterministic removal by the receiver.
  • Frames requiring more than 255 bytes of padding are sent unpadded to respect the single-byte length limit inherent to PKCS#7.
  • Invalid padding is detected and rejected during the unpadding phase to prevent malformed frame processing.

Frequently Asked Questions

What is the purpose of PKCS#7 padding in Bitchat?

PKCS#7 padding obscures the true length of encrypted Noise messages by extending them to standardized bucket sizes. This prevents network observers from inferring message content or conversation patterns based on packet size metadata.

Where is the padding logic implemented in the Bitchat repository?

The core implementation resides in /localPackages/BitFoundation/Sources/BitFoundation/MessagePadding.swift, specifically within the MessagePadding.pad(to:targetSize:) and MessagePadding.unpad(_:) methods.

Why are some Noise frames sent without padding?

Bitchat omits padding when a payload would require more than 255 bytes to reach the next bucket size. Because PKCS#7 stores the length in a single byte, values exceeding 255 are impossible to encode, so the protocol falls back to unpadded transmission for very large payloads.

How does Bitchat prevent padding oracle attacks?

The MessagePadding.unpad(_:) function strictly validates that every padding byte matches the expected length value before stripping the block. If validation fails, the frame is rejected immediately, ensuring that malformed padding cannot be exploited to decrypt content.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →