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) ¶
- A→B: Ya (96 bytes DH public key) || PadA (0–512 random bytes)
- B→A: Yb (96 bytes DH public key) || PadB (0–512 random bytes)
- 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)
- B→A: ENCRYPT_B(VC || crypto_select || len(PadD) || PadD) ENCRYPT_B(payload stream)
- 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 ¶
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 )
const ( CryptoPlaintext uint32 = 0x01 // No RC4; key exchange only CryptoRC4 uint32 = 0x02 // RC4 stream encryption )
Variables ¶
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)
)
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 ¶
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 ¶
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 ¶
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).