diff --git a/internal/aes_ige/aes.go b/internal/aes_ige/aes.go index 80dfbb7..28abd6f 100644 --- a/internal/aes_ige/aes.go +++ b/internal/aes_ige/aes.go @@ -8,6 +8,8 @@ package ige import ( "bytes" "crypto/aes" + "crypto/rand" + "crypto/sha256" "math/big" "github.com/xelaj/go-dry" @@ -17,36 +19,73 @@ type AesBlock [aes.BlockSize]byte type AesKV [32]byte type AesIgeBlock [48]byte -func MessageKey(msg []byte) []byte { - return dry.Sha1(string(msg))[4:20] +func MessageKey(authKey, msgPadded []byte, decode bool) []byte { + var x int + if decode { + x = 8 + } else { + x = 0 + } + + // `msg_key_large = SHA256 (substr (auth_key, 88+x, 32) + plaintext + random_padding);` + var msgKeyLarge [sha256.Size]byte + { + h := sha256.New() + + substr := authKey[88+x:] + _, _ = h.Write(substr[:32]) + _, _ = h.Write(msgPadded) + + h.Sum(msgKeyLarge[:0]) + } + r := make([]byte, 16) + // `msg_key = substr (msg_key_large, 8, 16);` + copy(r, msgKeyLarge[8:8+16]) + return r } -func Encrypt(msg, key []byte) ([]byte, error) { - msgKey := MessageKey(msg) - aesKey, aesIV := generateAESIGE(msgKey, key, false) +func Encrypt(msg, authKey []byte) (out, msgKey []byte, _ error) { + return encrypt(msg, authKey, false) +} +func encrypt(msg, authKey []byte, decode bool) (out, msgKey []byte, _ error) { // СУДЯ ПО ВСЕМУ вообще не уверен, но это видимо паддинг для добива блока, чтоб он делился на 256 бит - data := make([]byte, len(msg)+((16-(len(msg)%16))&15)) - copy(data, msg) + padding := 16 + (16-(len(msg)%16))&15 + data := make([]byte, len(msg)+padding) + n := copy(data, msg) + + // Fill padding using secure PRNG. + // + // See https://core.telegram.org/mtproto/description#encrypted-message-encrypted-data. + if _, err := rand.Read(data[n:]); err != nil { + return nil, nil, err + } - c, err := NewCipher(aesKey, aesIV) + msgKey = MessageKey(authKey, data, decode) + aesKey, aesIV := aesKeys(msgKey[:], authKey, decode) + + c, err := NewCipher(aesKey[:], aesIV[:]) if err != nil { - return nil, err + return nil, nil, err } - out := make([]byte, len(data)) + out = make([]byte, len(data)) if err := c.doAES256IGEencrypt(data, out); err != nil { - return nil, err + return nil, nil, err } - return out, nil + return out, msgKey, nil } // checkData это msgkey в понятиях мтпрото, нужно что бы проверить, успешно ли прошла расшифровка -func Decrypt(msg, key, checkData []byte) ([]byte, error) { - aesKey, aesIV := generateAESIGE(checkData, key, true) +func Decrypt(msg, authKey, checkData []byte) ([]byte, error) { + return decrypt(msg, authKey, checkData, true) +} + +func decrypt(msg, authKey, msgKey []byte, decode bool) ([]byte, error) { + aesKey, aesIV := aesKeys(msgKey, authKey, decode) - c, err := NewCipher(aesKey, aesIV) + c, err := NewCipher(aesKey[:], aesIV[:]) if err != nil { return nil, err } diff --git a/internal/aes_ige/aes_test.go b/internal/aes_ige/aes_test.go index 17fb142..5ef8360 100644 --- a/internal/aes_ige/aes_test.go +++ b/internal/aes_ige/aes_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDoAES256IGEdecrypt(t *testing.T) { @@ -237,29 +238,29 @@ func TestMessageKey(t *testing.T) { }{ { name: "empty", - msg: Hexed(""), - // correct: http://craiccomputing.blogspot.com/2009/09/sha1-digest-of-empty-string.html - want: Hexed("5E6B4B0D3255BFEF95601890AFD80709"), + msg: []byte(""), + want: Hexed("c5fbe78b7c52674af6786e1b6d3b0a89"), }, { name: "zeros", msg: Hexed("00000000000000000000000000000000"), - want: Hexed("5103BC5CC44BCDF0A15E160D445066FF"), + want: Hexed("62166edf89ef5a29523bba266a5c36a9"), }, { name: "randomized", msg: Hexed("5103BC5CC44BCDF0A15E160D445066FF"), - want: Hexed("F9D401E298F3EEEC1C927312AEB6B412"), + want: Hexed("2149a1cc4926af846e7ec6a36c75a3ba"), }, { name: "any words", msg: []byte("some cool message"), - want: Hexed("4170ED208083FAFD2DFA8507FD4A75B6"), + want: Hexed("65f2304bf99854b770168ccd9563dead"), }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, MessageKey(tt.msg)) + assert.Equal(t, tt.want, MessageKey(testAuthKey[:], tt.msg, false)) }) } } @@ -297,11 +298,9 @@ func TestEncryptMessageWithTempKeys(t *testing.T) { func TestEncryptDecrypt(t *testing.T) { tests := []struct { - name string - msg []byte - key []byte - want []byte - wantErr assert.ErrorAssertionFunc + name string + msg []byte + key []byte }{ { name: "simple", @@ -325,32 +324,17 @@ func TestEncryptDecrypt(t *testing.T) { "5198EE022B2B81F388D281D5E5BC580107CA01A50665C32B552715F335FD7626" + "4FAD00DDD5AE45B94832AC79CE7C511D194BC42B70EFA850BB15C2012C5215CA" + "BFE97CE66B8D8734D0EE759A638AF013"), - want: Hexed("BABBC03F65F828E4B97DBC5A992394C2"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - wantErr := tt.wantErr - if wantErr == nil { - wantErr = assert.NoError - } - got, err := Encrypt(tt.msg, tt.key) - if !wantErr(t, err) { - return - } - - if !assert.Equal(t, tt.want, got) { - return - } + a := require.New(t) + got, msgKey, err := encrypt(tt.msg, tt.key, false) + a.NoError(err) - // TODO: почему-то расшифровка так легко не проходит - //msgkey := MessageKey(tt.msg) - //decrypted, err := Decrypt(got, tt.key, msgkey) - //if !wantErr(t, err) { - // return - //} - // - //assert.Equal(t, tt.msg, decrypted) + decrypted, err := decrypt(got, tt.key, msgKey, false) + a.NoError(err) + a.Equal(tt.msg, decrypted[:len(tt.msg)]) }) } } diff --git a/internal/aes_ige/ige_cipher.go b/internal/aes_ige/ige_cipher.go index b41dcae..1d54aed 100644 --- a/internal/aes_ige/ige_cipher.go +++ b/internal/aes_ige/ige_cipher.go @@ -8,6 +8,7 @@ package ige import ( "crypto/aes" "crypto/cipher" + "crypto/sha256" "fmt" "github.com/pkg/errors" @@ -87,6 +88,53 @@ func isCorrectData(data []byte) error { // -------------------------------------------------------------------------------------------------- +func aesKeys(msgKey, authKey []byte, decode bool) (aesKey, aesIv [32]byte) { + var x int + if decode { + x = 8 + } else { + x = 0 + } + + // aes_key = substr (sha256_a, 0, 8) + substr (sha256_b, 8, 16) + substr (sha256_a, 24, 8); + computeAesKey := func(sha256a, sha256b []byte) (v [32]byte) { + n := copy(v[:], sha256a[:8]) + n += copy(v[n:], sha256b[8:16+8]) + copy(v[n:], sha256a[24:24+8]) + return v + } + // aes_iv = substr (sha256_b, 0, 8) + substr (sha256_a, 8, 16) + substr (sha256_b, 24, 8); + computeAesIV := func(sha256b, sha256a []byte) (v [32]byte) { + n := copy(v[:], sha256a[:8]) + n += copy(v[n:], sha256b[8:16+8]) + copy(v[n:], sha256a[24:24+8]) + return v + } + + var sha256a, sha256b [256]byte + // sha256_a = SHA256 (msg_key + substr (auth_key, x, 36)); + { + h := sha256.New() + + _, _ = h.Write(msgKey) + _, _ = h.Write(authKey[x : x+36]) + + h.Sum(sha256a[:0]) + } + // sha256_b = SHA256 (substr (auth_key, 40+x, 36) + msg_key); + { + h := sha256.New() + + substr := authKey[40+x:] + _, _ = h.Write(substr[:36]) + _, _ = h.Write(msgKey) + + h.Sum(sha256b[:0]) + } + + return computeAesKey(sha256a[:], sha256b[:]), computeAesIV(sha256a[:], sha256b[:]) +} + // generateAESIGEv2 это переписанная функция generateAESIGE, которая выглядить чуточку более понятно. func generateAESIGEv2(msgKey, authKey []byte, decode bool) (aesKey, aesIv []byte) { //nolint:deadcode wait for it var ( diff --git a/internal/aes_ige/ige_cipher_test.go b/internal/aes_ige/ige_cipher_test.go index 2362e30..6dfc245 100644 --- a/internal/aes_ige/ige_cipher_test.go +++ b/internal/aes_ige/ige_cipher_test.go @@ -11,6 +11,25 @@ import ( "github.com/stretchr/testify/assert" ) +var testAuthKey = []byte{ + 0x2f, 0xdf, 0xc5, 0x42, 0xa0, 0x0c, 0x6b, 0x3f, 0xed, 0x76, 0xa6, 0x38, 0xb2, 0x9c, 0x9a, 0x77, + 0x51, 0x98, 0xac, 0xca, 0xa3, 0x14, 0x73, 0x36, 0x45, 0xa7, 0xe3, 0x44, 0x69, 0xc4, 0xe2, 0xcb, + 0x26, 0x4f, 0x8c, 0x0f, 0xe3, 0x23, 0xa2, 0x61, 0xf0, 0x09, 0xfc, 0xe2, 0xc3, 0xd3, 0x4c, 0x6e, + 0xd1, 0x64, 0xa5, 0x60, 0x9c, 0x35, 0xa5, 0x7d, 0x27, 0x93, 0xc0, 0x1f, 0x29, 0x65, 0x4f, 0xfe, + 0x56, 0x63, 0xf1, 0x10, 0x62, 0xf2, 0x8e, 0xef, 0x38, 0x22, 0x1d, 0x7e, 0xc8, 0x87, 0x24, 0x47, + 0x74, 0x05, 0x26, 0xe8, 0xf6, 0xf1, 0x56, 0x61, 0x8a, 0xc0, 0xa2, 0x20, 0x9a, 0x3d, 0x37, 0xe0, + 0xae, 0xdc, 0x90, 0x6d, 0xb2, 0x96, 0x7d, 0x5f, 0xd0, 0x6c, 0xbb, 0xea, 0x21, 0x05, 0x2b, 0xf0, + 0x73, 0x0e, 0x5b, 0x6f, 0xe3, 0x7d, 0xe1, 0x0a, 0x1d, 0x61, 0x8e, 0x1c, 0x28, 0xb0, 0x0c, 0xd0, + 0xaf, 0x8f, 0x2f, 0xff, 0x9b, 0x41, 0xfe, 0xfe, 0x2c, 0xa4, 0x4b, 0xaf, 0x07, 0xae, 0xa9, 0x83, + 0x70, 0x4c, 0xdb, 0x1a, 0xaa, 0xce, 0x3e, 0x67, 0x37, 0x4f, 0xe0, 0x97, 0x2e, 0x0d, 0x53, 0x40, + 0xf1, 0x76, 0x6b, 0x1f, 0xf4, 0x00, 0x50, 0xe2, 0xdb, 0xf0, 0x71, 0xdf, 0xba, 0x23, 0xf0, 0xb5, + 0xdc, 0x35, 0x52, 0xfe, 0xb8, 0xc6, 0x90, 0xd6, 0x6f, 0xfc, 0x1d, 0x60, 0xe9, 0xf8, 0xe4, 0x5b, + 0xde, 0x76, 0xca, 0x8a, 0xc7, 0x34, 0x8b, 0xa1, 0x04, 0x89, 0x11, 0xc6, 0x04, 0x33, 0xfe, 0x8d, + 0xcd, 0xf4, 0x67, 0x68, 0xa7, 0x64, 0x3c, 0x01, 0x2b, 0xba, 0xd5, 0x1f, 0x43, 0x4c, 0x7d, 0xd4, + 0x63, 0x15, 0xd1, 0xbd, 0xc9, 0x2e, 0x7d, 0xb9, 0x66, 0x13, 0x74, 0x15, 0xb4, 0x7d, 0x7d, 0x2f, + 0x37, 0xcb, 0xf3, 0x27, 0x7c, 0xbd, 0xc7, 0xd4, 0x74, 0x7e, 0x8f, 0x32, 0xf8, 0x2b, 0x2e, 0x57, +} + func TestCipher_isCorrectData(t *testing.T) { tests := []struct { name string @@ -44,3 +63,39 @@ func TestCipher_isCorrectData(t *testing.T) { }) } } + +func Test_aesKeys(t *testing.T) { + msgKey := []byte{ + 0x2b, 0xbe, 0x1e, 0xa0, 0xaa, 0x58, 0xdd, 0x4c, 0x37, 0x68, 0x23, 0x72, 0xe7, 0x51, 0xfd, 0xf7, + } + + tests := []struct { + name string + decode bool + key, iv [32]byte + }{ + {"Encode", false, [32]byte{ + 0x56, 0xdc, 0x47, 0xab, 0x93, 0x94, 0x71, 0xf7, 0xa1, 0x20, 0xb3, 0x00, 0x4b, 0xc6, 0x01, 0xa8, + 0x2a, 0x3b, 0x16, 0xe9, 0x2e, 0x62, 0x7d, 0x56, 0x1a, 0xdf, 0x23, 0xee, 0xae, 0xe5, 0x0b, 0x72, + }, [32]byte{ + 0x8d, 0xb5, 0xd5, 0x3d, 0x4b, 0x87, 0xc1, 0x8b, 0xf9, 0xf9, 0x9a, 0x5d, 0xfa, 0x38, 0x7a, 0xf9, + 0x9c, 0xa7, 0x34, 0xcc, 0x03, 0x16, 0x93, 0x41, 0xf3, 0xfa, 0xc4, 0xbf, 0x78, 0x25, 0x84, 0x75, + }}, + {"Decode", true, [32]byte{ + 0x60, 0xc7, 0xb2, 0x42, 0x4d, 0xa7, 0x98, 0x55, 0x5d, 0xea, 0x3b, 0xdc, 0xbf, 0x9b, 0x10, 0x61, + 0x4d, 0xaf, 0x05, 0x0b, 0xb1, 0x9b, 0x73, 0x7b, 0xc2, 0x09, 0xc9, 0x01, 0x87, 0x2a, 0x0e, 0xee, + }, [32]byte{ + 0x49, 0xdb, 0x6d, 0x4f, 0xa0, 0x05, 0x5b, 0x9c, 0xe8, 0x05, 0x90, 0xfd, 0x54, 0x9a, 0x7a, 0x17, + 0xf5, 0xe6, 0x64, 0xaa, 0xe6, 0xa7, 0x8e, 0xd9, 0xfd, 0x13, 0xa0, 0x2a, 0x55, 0x11, 0x2c, 0xc2, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := assert.New(t) + + key, iv := aesKeys(msgKey, testAuthKey, tt.decode) + a.Equal(tt.key, key) + a.Equal(tt.iv, iv) + }) + } +} diff --git a/internal/mtproto/messages/messages.go b/internal/mtproto/messages/messages.go index c4c9b75..f7cfe4d 100644 --- a/internal/mtproto/messages/messages.go +++ b/internal/mtproto/messages/messages.go @@ -14,8 +14,6 @@ import ( "fmt" "github.com/pkg/errors" - "github.com/xelaj/go-dry" - ige "github.com/xelaj/mtproto/internal/aes_ige" "github.com/xelaj/mtproto/internal/encoding/tl" "github.com/xelaj/mtproto/internal/utils" @@ -41,7 +39,7 @@ type Encrypted struct { func (msg *Encrypted) Serialize(client MessageInformator, requireToAck bool) ([]byte, error) { obj := serializePacket(client, msg.Msg, msg.MsgID, requireToAck) - encryptedData, err := ige.Encrypt(obj, client.GetAuthKey()) + encryptedData, msgKey, err := ige.Encrypt(obj, client.GetAuthKey()) if err != nil { return nil, errors.Wrap(err, "encrypting") } @@ -50,7 +48,7 @@ func (msg *Encrypted) Serialize(client MessageInformator, requireToAck bool) ([] e := tl.NewEncoder(buf) e.PutRawBytes(utils.AuthKeyHash(client.GetAuthKey())) - e.PutRawBytes(ige.MessageKey(obj)) + e.PutRawBytes(msgKey) e.PutRawBytes(encryptedData) return buf.Bytes(), nil @@ -96,8 +94,8 @@ func DeserializeEncrypted(data, authKey []byte) (*Encrypted, error) { } // этот кусок проверяет валидность данных по ключу - trimed := decrypted[0 : 32+messageLen] // суммарное сообщение, после расшифровки - if !bytes.Equal(dry.Sha1Byte(trimed)[4:20], msg.MsgKey) { + msgKey := ige.MessageKey(authKey, decrypted, true) + if !bytes.Equal(msgKey, msg.MsgKey) { return nil, errors.New("wrong message key, can't trust to sender") } msg.Msg = d.PopRawBytes(int(messageLen))