108 lines
2.6 KiB
Go
108 lines
2.6 KiB
Go
package mtproxy
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
crand "crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// ObfuscateTagRandomizedIntermediate matches Telethon RandomizedIntermediatePacketCodec.
|
|
var ObfuscateTagRandomizedIntermediate = []byte{0xdd, 0xdd, 0xdd, 0xdd}
|
|
|
|
// randSource is injectable for tests.
|
|
var randSource io.Reader = crand.Reader
|
|
|
|
// InitHeader returns the 64-byte first payload (MTProxyIO.init_header) and CTR streams.
|
|
func InitHeader(secret16 []byte, dcID int16) (header64 []byte, enc cipher.Stream, dec cipher.Stream, err error) {
|
|
if len(secret16) != 16 {
|
|
return nil, nil, nil, fmt.Errorf("mtproxy secret must be 16 bytes")
|
|
}
|
|
sec := secret16
|
|
|
|
keywords := [][]byte{
|
|
[]byte("PVrG"),
|
|
[]byte("GET "),
|
|
[]byte("POST"),
|
|
{0xee, 0xee, 0xee, 0xee},
|
|
}
|
|
var rnd [64]byte
|
|
for {
|
|
if _, err := io.ReadFull(randSource, rnd[:]); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if rnd[0] == 0xef {
|
|
continue
|
|
}
|
|
bad := false
|
|
for _, k := range keywords {
|
|
if len(k) <= 4 && bytes.Equal(rnd[:len(k)], k) {
|
|
bad = true
|
|
break
|
|
}
|
|
}
|
|
if bad {
|
|
continue
|
|
}
|
|
if rnd[4] == 0 && rnd[5] == 0 && rnd[6] == 0 && rnd[7] == 0 {
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
random := rnd[:]
|
|
rev := make([]byte, 48)
|
|
for i := 0; i < 48; i++ {
|
|
rev[i] = random[55-i]
|
|
}
|
|
|
|
encKey := sha256Sum(append(append([]byte{}, random[8:40]...), sec...))
|
|
encIV := random[40:56]
|
|
decKey := sha256Sum(append(append([]byte{}, rev[:32]...), sec...))
|
|
decIV := rev[32:48]
|
|
|
|
enc, err = newCTR(encKey, encIV)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
dec, err = newCTR(decKey, decIV)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
buf := make([]byte, 64)
|
|
copy(buf, random)
|
|
copy(buf[56:60], ObfuscateTagRandomizedIntermediate)
|
|
dcBytes := make([]byte, 2)
|
|
binary.LittleEndian.PutUint16(dcBytes, uint16(dcID))
|
|
copy(buf[60:62], dcBytes)
|
|
|
|
// Как в Telethon MTProxyIO.init_header: encrypt(bytes(random)) на всём 64-байтном буфере,
|
|
// на провод уходит только random[0:56] как есть и random[56:64] = out[56:64].
|
|
// CTR enc должен продвинуться на 64 байта — иначе req_pq и чтение DC расходятся с прокси.
|
|
work := make([]byte, 64)
|
|
copy(work, buf)
|
|
out := make([]byte, 64)
|
|
enc.XORKeyStream(out, work)
|
|
copy(buf[56:64], out[56:64])
|
|
|
|
return buf, enc, dec, nil
|
|
}
|
|
|
|
func sha256Sum(b []byte) []byte {
|
|
h := sha256.Sum256(b)
|
|
return h[:]
|
|
}
|
|
|
|
func newCTR(key, iv []byte) (cipher.Stream, error) {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cipher.NewCTR(block, iv), nil
|
|
}
|