mse

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 8, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package mse implements Message Stream Encryption (MSE) / Protocol Encryption (PE) as specified in BEP 8 (https://www.bittorrent.org/beps/bep_0008.html).

MSE wraps a TCP connection with a Diffie-Hellman key exchange followed by optional RC4 stream encryption. It provides obfuscation (not strong security) to defeat ISP deep-packet inspection that throttles or blocks BitTorrent traffic.

Wire format (step numbers match BEP 8 §3)

  1. A→B: Ya (96 bytes DH public key) || PadA (0–512 random bytes)
  2. B→A: Yb (96 bytes DH public key) || PadB (0–512 random bytes)
  3. A→B: HASH('req1',S) [20 bytes, plaintext] HASH('req2',SKEY) XOR HASH('req3',S) [20 bytes, plaintext] ENCRYPT_A(VC || crypto_provide || len(PadC) || PadC || len(IA)) ENCRYPT_A(IA)
  4. B→A: ENCRYPT_B(VC || crypto_select || len(PadD) || PadD) ENCRYPT_B(payload stream)
  5. A→B: ENCRYPT_A(payload stream)

Where:

  • S = DH shared secret (Yb^xa mod P = Xa^yb mod P)
  • SKEY = info hash (20 bytes) identifying the torrent
  • ENCRYPT_A uses RC4(SHA1("keyA" || S || SKEY)) — A encrypts, B decrypts
  • ENCRYPT_B uses RC4(SHA1("keyB" || S || SKEY)) — B encrypts, A decrypts
  • VC = 8 zero bytes (verification constant)
  • IA = initial application data (typically the BEP 3 handshake bytes)

Synchronisation

Because PadA and PadB have unknown length, both sides scan the incoming stream for a known pattern to find where the padding ends:

  • Responder B scans for HASH('req1',S) in plaintext to skip PadA.
  • Initiator A scans the RC4-decrypted stream for the 8-byte VC to skip PadB.

Crypto methods

CryptoPlaintext (0x01): no RC4 after handshake — just key exchange for obfuscation
CryptoRC4       (0x02): RC4 stream encryption for payload

Index

Constants

View Source
const (
	// KeySize is the DH public/private key size in bytes (768 bits).
	KeySize = 96

	// VCLength is the length of the verification constant (8 zero bytes).
	VCLength = 8

	// HandshakeTimeout is the wall-clock budget for the entire MSE handshake.
	HandshakeTimeout = 30 * time.Second
)
View Source
const (
	CryptoPlaintext uint32 = 0x01 // No RC4; key exchange only
	CryptoRC4       uint32 = 0x02 // RC4 stream encryption
)

Variables

View Source
var (
	// P is the 768-bit safe prime defined in BEP 8.
	P, _ = new(big.Int).SetString(
		"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"+
			"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"+
			"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"+
			"E485B576625E7EC6F44C42E9A63A36210000000000090563",
		16,
	)
	// G is the generator (2).
	G = big.NewInt(2)
)
View Source
var (
	// ErrNoCryptoMethod is returned when no mutually supported crypto method exists.
	ErrNoCryptoMethod = errors.New("mse: no mutually supported crypto method")

	// ErrVCMismatch is returned when the VC sync scan exhausts its search budget.
	ErrVCMismatch = errors.New("mse: verification constant not found (sync failed)")

	// ErrUnknownInfoHash is returned by the responder when req2 does not match any
	// known torrent info hash.
	ErrUnknownInfoHash = errors.New("mse: unknown info hash")
)

Functions

func ObfuscateInfoHash

func ObfuscateInfoHash(infoHash dht.Key) dht.Key

ObfuscateInfoHash returns SHA1("req2", infoHash) — the value that should be compared against the req2⊕req3 value received from the initiator when implementing lookupInfoHash in the responder.

Example usage:

obf := mse.ObfuscateInfoHash(knownHash)
lookup := func(candidate dht.Key) (dht.Key, bool) {
    for _, h := range myTorrents {
        if mse.ObfuscateInfoHash(h) == candidate { return h, true }
    }
    return [20]byte{}, false
}

Types

type Conn

type Conn struct {
	net.Conn
	// contains filtered or unexported fields
}

Conn is an MSE-wrapped net.Conn. After a successful Handshake call, all reads and writes are transparently encrypted (RC4) or passed through (plaintext).

func InitiatorHandshake

func InitiatorHandshake(conn net.Conn, infoHash dht.Key, provide uint32) (*Conn, error)

InitiatorHandshake performs the MSE handshake as the connection initiator (A).

infoHash identifies the torrent we want to download. provide is a bitmask of the crypto methods we accept (CryptoPlaintext | CryptoRC4).

On success the returned *Conn is ready for BEP 3 handshake data.

func ResponderHandshake

func ResponderHandshake(conn net.Conn, lookupInfoHash func(dht.Key) (dht.Key, bool), accept uint32) (*Conn, error)

ResponderHandshake performs the MSE handshake as the connection responder (B).

lookupInfoHash is called with SHA1("req2", SKEY) — the caller must check each known info hash h whether SHA1("req2", h) equals the provided value and, if so, return h. It returns ErrUnknownInfoHash when no match is found.

accept is a bitmask of crypto methods we are willing to use. The method chosen is the highest-priority bit that appears in both provide and accept (RC4 > plaintext).

func (*Conn) Method

func (c *Conn) Method() uint32

Method returns the negotiated crypto method.

func (*Conn) Read

func (c *Conn) Read(b []byte) (int, error)

Read decrypts incoming bytes when RC4 is active.

func (*Conn) Write

func (c *Conn) Write(b []byte) (int, error)

Write encrypts outgoing bytes when RC4 is active.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL