Ctrl K
ComunidadGuías…

internal/mtprotoedge/decrypt.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package mtprotoedge

import (
	"crypto/aes"
	"encoding/binary"
	"errors"
	"fmt"

	"github.com/gotd/ige"

	"github.com/iamxvbaba/td/bin"
	"github.com/iamxvbaba/td/crypto"
)

// clientFrame is the decrypted view of a single client message frame.
// data/plaintext reference the caller-owned reused plaintext buffer and are only
// valid until the next frame is decrypted; bytes that must survive across frames
// have to be copied (dispatch already does b.Copy() for the RPC body, and
// container/gzip are consumed synchronously within the current frame).
type clientFrame struct {
	salt      int64
	sessionID int64
	messageID int64
	seqNo     int32
	// data is the message body after stripping the 32-byte header and padding.
	data []byte
	// plaintext is the full plaintext (header + data + padding); the quick ack
	// token is SHA256'd directly over it, avoiding the copy on the gotd path
	// where the whole decrypted frame is re-encoded just to compute the token.
	plaintext []byte
}

// encryptedFrameHeaderLen is the MTProto 2.0 plaintext header length: salt(8)+session_id(8)+msg_id(8)+seq_no(4)+len(4).
const encryptedFrameHeaderLen = 32

// decryptClientFrame decrypts one client encrypted message into the plain reuse
// buffer (telesrv-owned, symmetric with the outbound encryptOutboundFrame). Its
// validation set matches gotd Cipher.Decrypt line by line: auth_key_id match,
// 16-byte ciphertext block alignment, msg_key (SHA256, client side x=0),
// plaintext header length, message_data_len non-negative / 4-byte aligned / not
// out of bounds, padding ≤ 1024. The only difference is plaintext buffer reuse
// and avoiding struct heap allocation—gotd DecryptFromBuffer makes the whole
// plaintext frame every time (upload frames can reach 512KiB+), which is the
// largest steady-state allocation point on the inbound Dispatch path.
func decryptClientFrame(key crypto.AuthKey, b *bin.Buffer, plain *bin.Buffer) (clientFrame, error) {
	buf := b.Buf
	if len(buf) < 24 {
		return clientFrame{}, errors.New("encrypted message is too short")
	}
	var authKeyID [8]byte
	copy(authKeyID[:], buf[:8])
	if authKeyID != key.ID {
		return clientFrame{}, errors.New("unknown auth key id")
	}
	var msgKey bin.Int128
	copy(msgKey[:], buf[8:24])
	encrypted := buf[24:]
	if len(encrypted) == 0 || len(encrypted)%16 != 0 {
		return clientFrame{}, errors.New("invalid encrypted data padding")
	}

	aesKey, iv := crypto.Keys(key.Value, msgKey, crypto.Client)
	aesBlock, err := aes.NewCipher(aesKey[:])
	if err != nil {
		return clientFrame{}, err
	}
	ensureBinBufferLen(plain, len(encrypted))
	ige.DecryptBlocks(aesBlock, iv[:], plain.Buf, encrypted)
	plaintext := plain.Buf

	if crypto.MessageKey(key.Value, plaintext, crypto.Client) != msgKey {
		return clientFrame{}, errors.New("msg_key is invalid")
	}
	if len(plaintext) < encryptedFrameHeaderLen {
		return clientFrame{}, errors.New("message data is too short")
	}

	dataLen := int(int32(binary.LittleEndian.Uint32(plaintext[28:32])))
	withPadding := plaintext[encryptedFrameHeaderLen:]
	switch {
	case dataLen < 0:
		return clientFrame{}, fmt.Errorf("message length is invalid: %d less than zero", dataLen)
	case dataLen%4 != 0:
		return clientFrame{}, fmt.Errorf("message length is invalid: %d is not divisible by 4", dataLen)
	case dataLen > len(withPadding):
		return clientFrame{}, fmt.Errorf("message length %d is bigger than data length %d", dataLen, len(withPadding))
	case len(withPadding)-dataLen > 1024:
		return clientFrame{}, fmt.Errorf("padding %d of message is too big", len(withPadding)-dataLen)
	}

	return clientFrame{
		salt:      int64(binary.LittleEndian.Uint64(plaintext[0:8])),
		sessionID: int64(binary.LittleEndian.Uint64(plaintext[8:16])),
		messageID: int64(binary.LittleEndian.Uint64(plaintext[16:24])),
		seqNo:     int32(binary.LittleEndian.Uint32(plaintext[24:28])),
		data:      withPadding[:dataLen],
		plaintext: plaintext,
	}, nil
}