From d0065d35c25228ff05cfccbedab06dd0b6e933a0 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Fri, 20 Feb 2026 16:32:31 +0100 Subject: [PATCH] Add new obfuscation package --- mtglib/internal/obfuscation/conn.go | 34 ++++++ mtglib/internal/obfuscation/conn_test.go | 102 ++++++++++++++++ .../internal/obfuscation/handshake_frame.go | 111 ++++++++++++++++++ .../obfuscation/handshake_frame_fuzz_test.go | 33 ++++++ .../obfuscation/handshake_frame_test.go | 66 +++++++++++ mtglib/internal/obfuscation/init_test.go | 79 +++++++++++++ mtglib/internal/obfuscation/obfuscator.go | 87 ++++++++++++++ .../obfuscation/obfuscator_fuzz_test.go | 63 ++++++++++ .../internal/obfuscation/obfuscator_test.go | 94 +++++++++++++++ ...t-handshake-snapshot-4529d55776e2d427.json | 13 ++ ...t-handshake-snapshot-585c944d672f60a2.json | 13 ++ 11 files changed, 695 insertions(+) create mode 100644 mtglib/internal/obfuscation/conn.go create mode 100644 mtglib/internal/obfuscation/conn_test.go create mode 100644 mtglib/internal/obfuscation/handshake_frame.go create mode 100644 mtglib/internal/obfuscation/handshake_frame_fuzz_test.go create mode 100644 mtglib/internal/obfuscation/handshake_frame_test.go create mode 100644 mtglib/internal/obfuscation/init_test.go create mode 100644 mtglib/internal/obfuscation/obfuscator.go create mode 100644 mtglib/internal/obfuscation/obfuscator_fuzz_test.go create mode 100644 mtglib/internal/obfuscation/obfuscator_test.go create mode 100644 mtglib/internal/obfuscation/testdata/client-handshake-snapshot-4529d55776e2d427.json create mode 100644 mtglib/internal/obfuscation/testdata/client-handshake-snapshot-585c944d672f60a2.json diff --git a/mtglib/internal/obfuscation/conn.go b/mtglib/internal/obfuscation/conn.go new file mode 100644 index 0000000..0c07207 --- /dev/null +++ b/mtglib/internal/obfuscation/conn.go @@ -0,0 +1,34 @@ +package obfuscation + +import ( + "crypto/cipher" + + "github.com/9seconds/mtg/v2/essentials" +) + +type conn struct { + essentials.Conn + + sendCipher cipher.Stream + recvCipher cipher.Stream +} + +func (c conn) Read(p []byte) (int, error) { + n, err := c.Conn.Read(p) + if err != nil { + return n, err + } + + c.recvCipher.XORKeyStream(p, p[:n]) + + return n, nil +} + +func (c conn) Write(p []byte) (int, error) { + // yes, this is a bit violent and goes against a contract in io.Writer + // but we do it to avoid creating a new buffer just to perform this + // encryption. + c.sendCipher.XORKeyStream(p, p) + + return c.Conn.Write(p) +} diff --git a/mtglib/internal/obfuscation/conn_test.go b/mtglib/internal/obfuscation/conn_test.go new file mode 100644 index 0000000..0dfc1cd --- /dev/null +++ b/mtglib/internal/obfuscation/conn_test.go @@ -0,0 +1,102 @@ +package obfuscation + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/hex" + "testing" + + "github.com/9seconds/mtg/v2/essentials" + "github.com/9seconds/mtg/v2/internal/testlib" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +type ConnTestSuite struct { + suite.Suite + + secret []byte +} + +func (s *ConnTestSuite) SetupSuite() { + secret := [32]byte{} + s.secret = secret[:] +} + +func (s *ConnTestSuite) TestRead() { + testData := map[string]string{ + "data1": "b8f4b41993", + "": "", + "___": "83ca9f", + } + + for incoming, outgoing := range testData { + s.T().Run(incoming, func(t *testing.T) { + connMock := &testlib.EssentialsConnMock{} + testConn := s.makeConn(connMock) + data := make([]byte, len(incoming)) + + connMock.On("Read", make([]byte, len(incoming))).Return(len(incoming), nil).Run(func(args mock.Arguments) { + arg := args.Get(0).([]byte) + copy(arg, []byte(incoming)) + }) + + n, err := testConn.Read(data) + + assert.Equal(t, len(data), n) + assert.NoError(t, err) + assert.Equal(t, outgoing, hex.EncodeToString(data)) + + connMock.AssertExpectations(t) + }) + } +} + +func (s *ConnTestSuite) TestWrite() { + testData := map[string]string{ + "b8f4b41993": "data1", + "": "", + "83ca9f": "___", + } + + for incoming, outgoing := range testData { + s.T().Run(incoming, func(t *testing.T) { + connMock := &testlib.EssentialsConnMock{} + testConn := s.makeConn(connMock) + toWrite, _ := hex.DecodeString(incoming) + data := make([]byte, len(toWrite)) + + connMock.On("Write", []byte(outgoing)).Return(len(toWrite), nil) + + n, err := testConn.Write(toWrite) + assert.Equal(t, len(data), n) + assert.NoError(t, err) + + connMock.AssertExpectations(t) + }) + } +} + +func (s *ConnTestSuite) makeConn(rawConn *testlib.EssentialsConnMock) essentials.Conn { + rblock, err := aes.NewCipher(s.secret) + if err != nil { + panic(err) + } + + wblock, err := aes.NewCipher(s.secret) + if err != nil { + panic(err) + } + + return conn{ + Conn: rawConn, + sendCipher: cipher.NewCTR(wblock, s.secret[:aes.BlockSize]), + recvCipher: cipher.NewCTR(rblock, s.secret[:aes.BlockSize]), + } +} + +func TestConn(t *testing.T) { + t.Parallel() + suite.Run(t, &ConnTestSuite{}) +} diff --git a/mtglib/internal/obfuscation/handshake_frame.go b/mtglib/internal/obfuscation/handshake_frame.go new file mode 100644 index 0000000..6e1f0a8 --- /dev/null +++ b/mtglib/internal/obfuscation/handshake_frame.go @@ -0,0 +1,111 @@ +package obfuscation + +import ( + "crypto/rand" + "encoding/binary" + "slices" +) + +// https://core.telegram.org/mtproto/mtproto-transports#transport-obfuscation +const ( + // default DC is nothing is selected + defaultDC = 2 + + // the length of the handshake frame. Always 64 bytes + hfLen = 64 + + hfLenKey = 32 + hfLenIV = 16 + hfLenConnectionType = 4 + + // A structure of obfuscated handshake frame is following: + // + // [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd]. + // + // - 8 bytes of noise + // - 32 bytes of AES Key + // - 16 bytes of AES IV + // - 4 bytes of 'connection type' - this has some setting like a connection type + // - 2 bytes of 'DC'. DC is little endian int16 + // - 2 bytes of noise + hfOffsetKey = 8 + hfOffsetIV = hfOffsetKey + hfLenKey + hfOffsetConnectionType = hfOffsetIV + hfLenIV + hfOffsetDC = hfOffsetConnectionType + hfLenConnectionType +) + +// Connection-Type: Secure. We support only fake tls. +var hfConnectionType = [hfLenConnectionType]byte{0xdd, 0xdd, 0xdd, 0xdd} + +type handshakeFrame struct { + data [hfLen]byte +} + +func (h *handshakeFrame) key() []byte { + return h.data[hfOffsetKey : hfOffsetKey+hfLenKey] +} + +func (h *handshakeFrame) iv() []byte { + return h.data[hfOffsetIV : hfOffsetIV+hfLenIV] +} + +func (h *handshakeFrame) connectionType() []byte { + return h.data[hfOffsetConnectionType : hfOffsetConnectionType+hfLenConnectionType] +} + +func (h *handshakeFrame) dcSlice() []byte { + return h.data[hfOffsetDC : hfOffsetDC+2] +} + +func (h *handshakeFrame) dc() int { + idx := int16(binary.LittleEndian.Uint16(h.dcSlice())) + + switch { + case idx > 0: + return int(idx) + case idx < 0: + return -int(idx) + } + + return defaultDC +} + +func (h *handshakeFrame) revert() { + slices.Reverse(h.data[hfOffsetKey:hfOffsetConnectionType]) +} + +func generateHandshake(dc int) handshakeFrame { + frame := handshakeFrame{} + + for { + if _, err := rand.Read(frame.data[:]); err != nil { + panic(err) + } + + // https://github.com/tdlib/td/blob/master/td/mtproto/TcpTransport.cpp#L157-L158. + if frame.data[0] == 0xef { // abridged header + // https://core.telegram.org/mtproto/mtproto-transports#abridged + continue + } + + switch binary.LittleEndian.Uint32(frame.data[:4]) { + case 0x44414548, // HEAD + 0x54534f50, // POST + 0x20544547, // GET + 0x4954504f, // OPTI + 0x02010316, // ???? + 0xdddddddd, // PaddedIntermediate header + 0xeeeeeeee: // Intermediate header + continue + } + + if frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7] == 0 { + continue + } + + copy(frame.connectionType(), hfConnectionType[:]) + binary.LittleEndian.PutUint16(frame.dcSlice(), uint16(dc)) + + return frame + } +} diff --git a/mtglib/internal/obfuscation/handshake_frame_fuzz_test.go b/mtglib/internal/obfuscation/handshake_frame_fuzz_test.go new file mode 100644 index 0000000..71965ec --- /dev/null +++ b/mtglib/internal/obfuscation/handshake_frame_fuzz_test.go @@ -0,0 +1,33 @@ +package obfuscation + +import ( + "encoding/binary" + "testing" + + "github.com/stretchr/testify/assert" +) + +func FuzzGenerateHandshakeFrame(f *testing.F) { + f.Fuzz(func(t *testing.T, arg int16) { + frame := generateHandshake(int(arg)) + + assert.NotEqualValues(t, 0xef, frame.data[0]) + + firstBytes := binary.LittleEndian.Uint32(frame.data[:4]) + assert.NotEqualValues(t, 0x44414548, firstBytes) + assert.NotEqualValues(t, 0x54534f50, firstBytes) + assert.NotEqualValues(t, 0x20544547, firstBytes) + assert.NotEqualValues(t, 0x4954504f, firstBytes) + assert.NotEqualValues(t, 0x02010316, firstBytes) + assert.NotEqualValues(t, 0xeeeeeeee, firstBytes) + assert.NotEqualValues(t, 0xdddddddd, firstBytes) + + assert.NotEqualValues( + t, + 0, + frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7]) + + assert.Equal(t, hfConnectionType[:], frame.connectionType()) + assert.EqualValues(t, arg, frame.dc()) + }) +} diff --git a/mtglib/internal/obfuscation/handshake_frame_test.go b/mtglib/internal/obfuscation/handshake_frame_test.go new file mode 100644 index 0000000..3b25492 --- /dev/null +++ b/mtglib/internal/obfuscation/handshake_frame_test.go @@ -0,0 +1,66 @@ +package obfuscation + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type HandshakeFrameTestSuite struct { + suite.Suite + + frame handshakeFrame + reverted handshakeFrame +} + +func (h *HandshakeFrameTestSuite) SetupSuite() { + for i := range hfLen { + h.frame.data[i] = byte(i + 1) + h.reverted.data[i] = byte(hfLen - i) + } +} + +func (h *HandshakeFrameTestSuite) TestKey() { + key := h.frame.key() + h.EqualValues(8+1, key[0]) + h.EqualValues(8+hfLenKey, key[len(key)-1]) + h.Len(key, hfLenKey) +} + +func (h *HandshakeFrameTestSuite) TestIV() { + iv := h.frame.iv() + h.EqualValues(40+1, iv[0]) + h.EqualValues(40+hfLenIV, iv[len(iv)-1]) + h.Len(iv, hfLenIV) +} + +func (h *HandshakeFrameTestSuite) TestConnectionType() { + connectionType := h.frame.connectionType() + h.EqualValues(56+1, connectionType[0]) + h.EqualValues(56+hfLenConnectionType, connectionType[len(connectionType)-1]) + h.Len(connectionType, hfLenConnectionType) +} + +func (h *HandshakeFrameTestSuite) TestDCSlice() { + dcSlice := h.frame.dcSlice() + h.EqualValues(61, dcSlice[0]) + h.EqualValues(61+1, dcSlice[1]) + h.Len(dcSlice, 2) +} + +func (h *HandshakeFrameTestSuite) TestDC() { + h.Equal(15933, h.frame.dc()) +} + +func (h *HandshakeFrameTestSuite) TestRevert() { + fr := h.frame + fr.revert() + + h.Equal(h.reverted.key(), fr.key()) + h.Equal(h.reverted.iv(), fr.iv()) +} + +func TestHandshakeFrame(t *testing.T) { + t.Parallel() + suite.Run(t, &HandshakeFrameTestSuite{}) +} diff --git a/mtglib/internal/obfuscation/init_test.go b/mtglib/internal/obfuscation/init_test.go new file mode 100644 index 0000000..ff2ef75 --- /dev/null +++ b/mtglib/internal/obfuscation/init_test.go @@ -0,0 +1,79 @@ +package obfuscation_test + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type snapshotBytes struct { + data []byte +} + +func (s snapshotBytes) MarshalText() ([]byte, error) { + if len(s.data) == 0 { + return nil, nil + } + + return []byte(base64.RawStdEncoding.EncodeToString(s.data)), nil +} + +func (s *snapshotBytes) UnmarshalText(data []byte) error { + val, err := base64.RawStdEncoding.DecodeString(string(data)) + if err != nil { + return fmt.Errorf("cannot unmarshal %v: %w", len(val), err) + } + + s.data = val + + return nil +} + +type ObfuscatedSnapshot struct { + Secret snapshotBytes `json:"secret"` + Frame snapshotBytes `json:"frame"` + DC int16 `json:"dc"` + Encrypted struct { + Text snapshotBytes `json:"text"` + Cipher snapshotBytes `json:"cipher"` + } `json:"encrypted"` + Decrypted struct { + Text snapshotBytes `json:"text"` + Cipher snapshotBytes `json:"cipher"` + } `json:"decrypted"` +} + +type SnapshotTestSuite struct { + suite.Suite + + snapshots map[string]*ObfuscatedSnapshot +} + +func (s *SnapshotTestSuite) Setup(dirname, namePrefix string) { + s.snapshots = make(map[string]*ObfuscatedSnapshot) + + files, err := os.ReadDir("testdata") + require.NoError(s.T(), err) + + for _, v := range files { + if !strings.HasPrefix(v.Name(), namePrefix) { + continue + } + + filename := filepath.Join("testdata", v.Name()) + + contents, err := os.ReadFile(filename) + require.NoError(s.T(), err) + + value := &ObfuscatedSnapshot{} + require.NoError(s.T(), json.Unmarshal(contents, value)) + + s.snapshots[v.Name()] = value + } +} diff --git a/mtglib/internal/obfuscation/obfuscator.go b/mtglib/internal/obfuscation/obfuscator.go new file mode 100644 index 0000000..09e63f1 --- /dev/null +++ b/mtglib/internal/obfuscation/obfuscator.go @@ -0,0 +1,87 @@ +package obfuscation + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "crypto/subtle" + "encoding/hex" + "fmt" + "hash" + "io" + + "github.com/9seconds/mtg/v2/essentials" +) + +type Obfuscator struct { + Secret []byte +} + +func (o Obfuscator) ReadHandshake(r essentials.Conn) (int, essentials.Conn, error) { + frame := handshakeFrame{} + + if _, err := io.ReadFull(r, frame.data[:]); err != nil { + return 0, nil, fmt.Errorf("cannot read frame: %w", err) + } + + hasher := sha256.New() + recvCipher := o.getCipher(&frame, hasher) + + frame.revert() + hasher.Reset() + sendCipher := o.getCipher(&frame, hasher) + + recvCipher.XORKeyStream(frame.data[:], frame.data[:]) + + if val := frame.connectionType(); subtle.ConstantTimeCompare(val, hfConnectionType[:]) != 1 { + return 0, nil, fmt.Errorf("unsupported connection type: %s", hex.EncodeToString(val)) + } + + cn := conn{ + Conn: r, + recvCipher: recvCipher, + sendCipher: sendCipher, + } + + return frame.dc(), cn, nil +} + +func (o Obfuscator) SendHandshake(w essentials.Conn, dc int) (essentials.Conn, error) { + frame := generateHandshake(dc) + copyFrame := frame + hasher := sha256.New() + + sendCipher := o.getCipher(&frame, hasher) + + frame.revert() + hasher.Reset() + recvCipher := o.getCipher(&frame, hasher) + + sendCipher.XORKeyStream(frame.data[:], frame.data[:]) + copy(frame.key(), copyFrame.key()) + copy(frame.iv(), copyFrame.iv()) + + if _, err := w.Write(frame.data[:]); err != nil { + return nil, fmt.Errorf("cannot send a handshake: %w", err) + } + + return conn{ + Conn: w, + recvCipher: recvCipher, + sendCipher: sendCipher, + }, nil +} + +func (o Obfuscator) getCipher(f *handshakeFrame, hasher hash.Hash) cipher.Stream { + blockKey := f.key() + + if o.Secret != nil { + hasher.Write(blockKey) + hasher.Write(o.Secret) + blockKey = hasher.Sum(nil) + } + + block, _ := aes.NewCipher(blockKey) + + return cipher.NewCTR(block, f.iv()) +} diff --git a/mtglib/internal/obfuscation/obfuscator_fuzz_test.go b/mtglib/internal/obfuscation/obfuscator_fuzz_test.go new file mode 100644 index 0000000..1697c32 --- /dev/null +++ b/mtglib/internal/obfuscation/obfuscator_fuzz_test.go @@ -0,0 +1,63 @@ +package obfuscation_test + +import ( + "bytes" + "testing" + + "github.com/9seconds/mtg/v2/internal/testlib" + "github.com/9seconds/mtg/v2/mtglib" + "github.com/9seconds/mtg/v2/mtglib/internal/obfuscation" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func FuzzClientServerHandshakes(f *testing.F) { + f.Add(int16(1), make([]byte, mtglib.SecretKeyLength)) + + f.Fuzz(func(t *testing.T, dc int16, data []byte) { + if dc <= 0 { + dc = 1 + } + + client := obfuscation.Obfuscator{ + Secret: data, + } + server := client + + clientToServerBuf := &bytes.Buffer{} + + writeConnMock := &testlib.EssentialsConnMock{} + writeConnMock. + On("Write", mock.AnythingOfType("[]uint8")). + Once(). + Return(64, nil). + Run(func(args mock.Arguments) { + arg := args.Get(0).([]byte) + n, err := clientToServerBuf.Write(arg) + assert.Equal(t, 64, n) + assert.NoError(t, err) + }) + + readConnMock := &testlib.EssentialsConnMock{} + readConnMock. + On("Read", mock.AnythingOfType("[]uint8")). + Once(). + Return(64, nil). + Run(func(args mock.Arguments) { + arg := args.Get(0).([]byte) + n, err := clientToServerBuf.Read(arg) + assert.Equal(t, 64, n) + assert.NoError(t, err) + }) + + _, err := client.SendHandshake(writeConnMock, int(dc)) + assert.NoError(t, err) + + readDc, _, err := server.ReadHandshake(readConnMock) + assert.NoError(t, err) + assert.EqualValues(t, dc, readDc) + + writeConnMock.AssertExpectations(t) + readConnMock.AssertExpectations(t) + }) +} diff --git a/mtglib/internal/obfuscation/obfuscator_test.go b/mtglib/internal/obfuscation/obfuscator_test.go new file mode 100644 index 0000000..0280b26 --- /dev/null +++ b/mtglib/internal/obfuscation/obfuscator_test.go @@ -0,0 +1,94 @@ +package obfuscation_test + +import ( + "bytes" + "testing" + + "github.com/9seconds/mtg/v2/internal/testlib" + "github.com/9seconds/mtg/v2/mtglib" + "github.com/9seconds/mtg/v2/mtglib/internal/obfuscation" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type ObfuscatorTestSuite struct { + SnapshotTestSuite + + secret *mtglib.Secret +} + +func (s *ObfuscatorTestSuite) SetupSuite() { + s.SnapshotTestSuite.Setup("", "client-handshake") + + secret := mtglib.GenerateSecret("hostname.com") + s.secret = &secret +} + +func (s *ObfuscatorTestSuite) TestSnapshot() { + for name, snapshot := range s.snapshots { + s.T().Run(name, func(t *testing.T) { + obfs := obfuscation.Obfuscator{ + Secret: snapshot.Secret.data, + } + + connMock := &testlib.EssentialsConnMock{} + + connMockReadBuffer := &bytes.Buffer{} + connMockReadBuffer.Write(snapshot.Frame.data) + connMockReadBuffer.Write(snapshot.Decrypted.Cipher.data) + + connMockWriteBuffer := &bytes.Buffer{} + + connMock. + On("Read", mock.AnythingOfType("[]uint8")). + Return(64, nil). + Run(func(args mock.Arguments) { + arr := args.Get(0).([]byte) + _, err := connMockReadBuffer.Read(arr) + require.NoError(t, err) + }) + + dc, cn, err := obfs.ReadHandshake(connMock) + assert.EqualValues(t, 2, dc) + assert.NoError(t, err) + + connMock.Calls = []mock.Call{} + connMock.ExpectedCalls = []*mock.Call{} + + connMock. + On("Read", mock.AnythingOfType("[]uint8")). + Return(len(snapshot.Decrypted.Cipher.data), nil). + Run(func(args mock.Arguments) { + arr := args.Get(0).([]byte) + _, err := connMockReadBuffer.Read(arr) + require.NoError(t, err) + }) + connMock. + On("Write", mock.AnythingOfType("[]uint8")). + Return(len(snapshot.Encrypted.Cipher.data), nil). + Run(func(args mock.Arguments) { + arr := args.Get(0).([]byte) + _, err := connMockWriteBuffer.Write(arr) + require.NoError(t, err) + }) + + readBuf := make([]byte, len(snapshot.Decrypted.Text.data)) + _, err = cn.Read(readBuf) + assert.NoError(t, err) + assert.Equal(t, readBuf, snapshot.Decrypted.Text.data) + + _, err = cn.Write(snapshot.Encrypted.Text.data) + assert.NoError(t, err) + assert.Equal(t, connMockWriteBuffer.Bytes(), snapshot.Encrypted.Cipher.data) + + connMock.AssertExpectations(t) + }) + } +} + +func TestObfuscator(t *testing.T) { + t.Parallel() + suite.Run(t, &ObfuscatorTestSuite{}) +} diff --git a/mtglib/internal/obfuscation/testdata/client-handshake-snapshot-4529d55776e2d427.json b/mtglib/internal/obfuscation/testdata/client-handshake-snapshot-4529d55776e2d427.json new file mode 100644 index 0000000..f34dc15 --- /dev/null +++ b/mtglib/internal/obfuscation/testdata/client-handshake-snapshot-4529d55776e2d427.json @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "gDcXwaMY4RwlR+nJw+ILDr123UJHHjjE/U5pF4m/Y04AmH7lEpEL6UYRnIYDbDlOHSDxc1ToziPvNlJJh8RMow", + "dc": 2, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "wZV3TR39l9nRoQ" + }, + "decrypted": { + "text": "4wZj6mUUew", + "cipher": "YWJjZGVmZw" + } +} diff --git a/mtglib/internal/obfuscation/testdata/client-handshake-snapshot-585c944d672f60a2.json b/mtglib/internal/obfuscation/testdata/client-handshake-snapshot-585c944d672f60a2.json new file mode 100644 index 0000000..a59adb4 --- /dev/null +++ b/mtglib/internal/obfuscation/testdata/client-handshake-snapshot-585c944d672f60a2.json @@ -0,0 +1,13 @@ +{ + "secret": "NnoYmu4Y+jHBkAVO/UqOlQ", + "frame": "M2WyxeiwIQB+ZOFxNzSNHtu9OdESkfxv3JkKFimCxUoYA3BD/Ql9nXB/OIonCKLUKCcS0VzZ2P6/+5oQ9GI8YA", + "dc": 2, + "encrypted": { + "text": "AQIDBAUGBwgJCg", + "cipher": "tzAwrCz00odERg" + }, + "decrypted": { + "text": "QkIvwGQDgA", + "cipher": "YWJjZGVmZw" + } +}