diff --git a/EXAMPLES.md b/EXAMPLES.md index b866590..256601f 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -14,6 +14,8 @@ and add at least these variables with adequate value: **api_id, api_hash, phone_ Remember that these are just simple example codes that you should adjust to your needs. In real production code, you might want to properly test the success of each operation or handle exceptions. +More examples can also be found in answers to [StackOverflow questions](https://stackoverflow.com/questions/tagged/wtelegramclient). + ### Send a message to someone by @username ```csharp diff --git a/README.md b/README.md index c919c83..f0b6cc4 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ static async Task Main(string[] _) } ``` When run, this will prompt you interactively for your App **api_hash** and **api_id** (that you obtain through Telegram's [API development tools](https://my.telegram.org/apps) page) and try to connect to Telegram servers. +Those api hash/id represent your application and one can be used for handling many user accounts. Then it will attempt to sign-in *(login)* as a user for which you must enter the **phone_number** and the **verification_code** that will be sent to this user (for example through SMS or another Telegram client app the user is connected to). diff --git a/src/Client.cs b/src/Client.cs index 1a5ad7c..f3bc232 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -27,9 +27,9 @@ namespace WTelegram /// This event will be called when an unsollicited update/message is sent by Telegram servers /// See Examples/Program_ListenUpdate.cs for how to use this public event Action Update; - public delegate Task TcpFactory(string host, int port); /// Used to create a TcpClient connected to the given address/port, or throw an exception on failure public TcpFactory TcpHandler { get; set; } = DefaultTcpHandler; + public delegate Task TcpFactory(string host, int port); /// Url for using a MTProxy. https://t.me/proxy?server=... public string MTProxyUrl { get; set; } /// Telegram configuration, obtained at connection time @@ -71,13 +71,8 @@ namespace WTelegram private const int FilePartSize = 512 * 1024; private const string ConnectionShutDown = "Could not read payload length : Connection shut down"; private readonly SemaphoreSlim _parallelTransfers = new(10); // max parallel part uploads/downloads -#if MTPROTO1 - private readonly SHA1 _sha1 = SHA1.Create(); - private readonly SHA1 _sha1Recv = SHA1.Create(); -#else private readonly SHA256 _sha256 = SHA256.Create(); private readonly SHA256 _sha256Recv = SHA256.Create(); -#endif #if OBFUSCATION private AesCtr _sendCtr, _recvCtr; #endif @@ -575,11 +570,7 @@ namespace WTelegram } else { -#if MTPROTO1 - byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, dataLen - 24), false, _dcSession.AuthKey, data, 8, _sha1Recv); -#else byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, (dataLen - 24) & ~0xF), false, _dcSession.AuthKey, data, 8, _sha256Recv); -#endif if (decrypted_data.Length < 36) // header below+ctorNb throw new ApplicationException($"Decrypted packet too small: {decrypted_data.Length}"); using var reader = new TL.BinaryReader(new MemoryStream(decrypted_data), this); @@ -603,18 +594,13 @@ namespace WTelegram if (sessionId != _dcSession.Id) throw new ApplicationException($"Unexpected session ID {sessionId} != {_dcSession.Id}"); if ((msgId & 1) == 0) throw new ApplicationException($"Invalid server msgId {msgId}"); if ((seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msgId); -#if MTPROTO1 - if (decrypted_data.Length - 32 - length is < 0 or > 15) throw new ApplicationException($"Unexpected decrypted message_data_length {length} / {decrypted_data.Length - 32}"); - if (!data.AsSpan(8, 16).SequenceEqual(_sha1Recv.ComputeHash(decrypted_data, 0, 32 + length).AsSpan(4))) - throw new ApplicationException($"Mismatch between MsgKey & decrypted SHA1"); -#else if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new ApplicationException($"Unexpected decrypted message_data_length {length} / {decrypted_data.Length - 32}"); _sha256Recv.TransformBlock(_dcSession.AuthKey, 96, 32, null, 0); _sha256Recv.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length); if (!data.AsSpan(8, 16).SequenceEqual(_sha256Recv.Hash.AsSpan(8, 16))) throw new ApplicationException($"Mismatch between MsgKey & decrypted SHA256"); _sha256Recv.Initialize(); -#endif + var ctorNb = reader.ReadUInt32(); if (ctorNb != Layer.BadMsgCtor && (msgStamp - DateTime.UtcNow).Ticks / TimeSpan.TicksPerSecond is > 30 or < -300) { // msg_id values that belong over 30 seconds in the future or over 300 seconds in the past are to be ignored. @@ -677,12 +663,7 @@ namespace WTelegram { using var clearStream = new MemoryStream(1024); using var clearWriter = new BinaryWriter(clearStream, Encoding.UTF8); -#if MTPROTO1 - const int prepend = 0; -#else - const int prepend = 32; - clearWriter.Write(_dcSession.AuthKey, 88, prepend); -#endif + clearWriter.Write(_dcSession.AuthKey, 88, 32); clearWriter.Write(_dcSession.Salt); // int64 salt clearWriter.Write(_dcSession.Id); // int64 session_id clearWriter.Write(msgId); // int64 message_id @@ -693,24 +674,16 @@ namespace WTelegram else Helpers.Log(1, $"{_dcSession.DcID}>Sending {msg.GetType().Name.TrimEnd('_'),-40} {MsgIdToStamp(msgId):u} (svc)"); clearWriter.WriteTLObject(msg); // bytes message_data - int clearLength = (int)clearStream.Length - prepend; // length before padding (= 32 + message_data_length) + int clearLength = (int)clearStream.Length - 32; // length before padding (= 32 + message_data_length) int padding = (0x7FFFFFF0 - clearLength) % 16; -#if !MTPROTO1 padding += _random.Next(1, 64) * 16; // MTProto 2.0 padding must be between 12..1024 with total length divisible by 16 -#endif - clearStream.SetLength(prepend + clearLength + padding); + clearStream.SetLength(32 + clearLength + padding); byte[] clearBuffer = clearStream.GetBuffer(); - BinaryPrimitives.WriteInt32LittleEndian(clearBuffer.AsSpan(prepend + 28), clearLength - 32); // patch message_data_length - RNG.GetBytes(clearBuffer, prepend + clearLength, padding); -#if MTPROTO1 - var msgKeyLarge = _sha1.ComputeHash(clearBuffer, 0, clearLength); // padding excluded from computation! - const int msgKeyOffset = 4; // msg_key = low 128-bits of SHA1(plaintext) - byte[] encrypted_data = EncryptDecryptMessage(clearBuffer.AsSpan(prepend, clearLength + padding), true, _dcSession.AuthKey, msgKeyLarge, msgKeyOffset, _sha1); -#else - var msgKeyLarge = _sha256.ComputeHash(clearBuffer, 0, prepend + clearLength + padding); + BinaryPrimitives.WriteInt32LittleEndian(clearBuffer.AsSpan(60), clearLength - 32); // patch message_data_length + RNG.GetBytes(clearBuffer, 32 + clearLength, padding); + var msgKeyLarge = _sha256.ComputeHash(clearBuffer, 0, 32 + clearLength + padding); const int msgKeyOffset = 8; // msg_key = middle 128-bits of SHA256(authkey_part+plaintext+padding) - byte[] encrypted_data = EncryptDecryptMessage(clearBuffer.AsSpan(prepend, clearLength + padding), true, _dcSession.AuthKey, msgKeyLarge, msgKeyOffset, _sha256); -#endif + byte[] encrypted_data = EncryptDecryptMessage(clearBuffer.AsSpan(32, clearLength + padding), true, _dcSession.AuthKey, msgKeyLarge, msgKeyOffset, _sha256); writer.Write(_dcSession.AuthKeyID); // int64 auth_key_id writer.Write(msgKeyLarge, msgKeyOffset, 16); // int128 msg_key diff --git a/src/Encryption.cs b/src/Encryption.cs index d341aca..448eff6 100644 --- a/src/Encryption.cs +++ b/src/Encryption.cs @@ -33,11 +33,7 @@ namespace WTelegram //1) var nonce = new Int128(RNG); -#if MTPROTO1 - var resPQ = await client.ReqPQ(nonce); -#else var resPQ = await client.ReqPqMulti(nonce); -#endif //2) if (resPQ.nonce != nonce) throw new ApplicationException("Nonce mismatch"); var fingerprint = resPQ.server_public_key_fingerprints.FirstOrDefault(PublicKeys.ContainsKey); @@ -62,19 +58,6 @@ namespace WTelegram }; byte[] encrypted_data = null; { -#if OLDKEY - using var clearStream = new MemoryStream(255); - clearStream.Position = 20; // skip SHA1 area (to be patched) - using var writer = new BinaryWriter(clearStream, Encoding.UTF8); - writer.WriteTLObject(pqInnerData); - int clearLength = (int)clearStream.Length; // length before padding (= 20 + message_data_length) - if (clearLength > 255) throw new ApplicationException("PQInnerData too big"); - byte[] clearBuffer = clearStream.GetBuffer(); - RNG.GetBytes(clearBuffer, clearLength, 255 - clearLength); - sha1.ComputeHash(clearBuffer, 20, clearLength - 20).CopyTo(clearBuffer, 0); // patch with SHA1 - encrypted_data = BigInteger.ModPow(BigEndianInteger(clearBuffer), // encrypt with RSA key - BigEndianInteger(publicKey.e), BigEndianInteger(publicKey.n)).ToByteArray(true, true); -#else //4.1) RSA_PAD(data, server_public_key) using var clearStream = new MemoryStream(256); using var writer = new BinaryWriter(clearStream, Encoding.UTF8); @@ -101,7 +84,6 @@ namespace WTelegram if (x < n) // if good result, encrypt with RSA key: encrypted_data = BigInteger.ModPow(x, BigEndianInteger(publicKey.e), n).To256Bytes(); } // otherwise, repeat the steps -#endif } var serverDHparams = await client.ReqDHParams(pqInnerData.nonce, pqInnerData.server_nonce, pqInnerData.p, pqInnerData.q, fingerprint, encrypted_data); //5) @@ -250,17 +232,6 @@ namespace WTelegram private static void LoadDefaultPublicKeys() { -#if OLDKEY - // Old Public Key (C3B42B026CE86B21) - LoadPublicKey(@"-----BEGIN RSA PUBLIC KEY----- -MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6 -lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS -an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw -Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+ -8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n -Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB ------END RSA PUBLIC KEY-----"); -#else // Production Public Key (D09D1D85DE64FD85) LoadPublicKey(@"-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEA6LszBcC1LGzyr992NzE0ieY+BSaOW622Aa9Bd4ZHLl+TuFQ4lo4g @@ -279,42 +250,8 @@ j25sIWeYPHYeOrFp/eXaqhISP6G+q2IeTaWTXpwZj4LzXq5YOpk4bYEQ6mvRq7D1 aHWfYmlEGepfaYR8Q0YqvvhYtMte3ITnuSJs171+GDqpdKcSwHnd6FudwGO4pcCO j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB -----END RSA PUBLIC KEY-----"); -#endif } -#if MTPROTO1 - internal static byte[] EncryptDecryptMessage(Span input, bool encrypt, byte[] authKey, byte[] msgKey, int msgKeyOffset, SHA1 sha1) - { - // first, construct AES key & IV - byte[] aes_key = new byte[32], aes_iv = new byte[32]; - int x = encrypt ? 0 : 8; - sha1.TransformBlock(msgKey, msgKeyOffset, 16, null, 0); // msgKey - sha1.TransformFinalBlock(authKey, x, 32); // authKey[x:32] - var sha1_a = sha1.Hash; - sha1.Initialize(); - sha1.TransformBlock(authKey, 32 + x, 16, null, 0); // authKey[32+x:16] - sha1.TransformBlock(msgKey, msgKeyOffset, 16, null, 0); // msgKey - sha1.TransformFinalBlock(authKey, 48 + x, 16); // authKey[48+x:16] - var sha1_b = sha1.Hash; - sha1.Initialize(); - sha1.TransformBlock(authKey, 64 + x, 32, null, 0); // authKey[64+x:32] - sha1.TransformFinalBlock(msgKey, msgKeyOffset, 16); // msgKey - var sha1_c = sha1.Hash; - sha1.Initialize(); - sha1.TransformBlock(msgKey, msgKeyOffset, 16, null, 0); // msgKey - sha1.TransformFinalBlock(authKey, 96 + x, 32); // authKey[96+x:32] - var sha1_d = sha1.Hash; - sha1.Initialize(); - Array.Copy(sha1_a, 0, aes_key, 0, 8); - Array.Copy(sha1_b, 8, aes_key, 8, 12); - Array.Copy(sha1_c, 4, aes_key, 20, 12); - Array.Copy(sha1_a, 8, aes_iv, 0, 12); - Array.Copy(sha1_b, 0, aes_iv, 12, 8); - Array.Copy(sha1_c, 16, aes_iv, 20, 4); - Array.Copy(sha1_d, 0, aes_iv, 24, 8); - return AES_IGE_EncryptDecrypt(input, aes_key, aes_iv, encrypt); - } -#else internal static byte[] EncryptDecryptMessage(Span input, bool encrypt, byte[] authKey, byte[] msgKey, int msgKeyOffset, SHA256 sha256) { // first, construct AES key & IV @@ -336,7 +273,6 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB Array.Copy(sha256_b, 24, aes_iv, 24, 8); return AES_IGE_EncryptDecrypt(input, aes_key, aes_iv, encrypt); } -#endif private static byte[] AES_IGE_EncryptDecrypt(Span input, byte[] aes_key, byte[] aes_iv, bool encrypt) { @@ -346,20 +282,17 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB var output = new byte[input.Length]; var xPrev = aes_iv.AsSpan(encrypt ? 16 : 0, 16); var yPrev = aes_iv.AsSpan(encrypt ? 0 : 16, 16); - var aesCrypto = encrypt ? AesECB.CreateEncryptor(aes_key, null) : AesECB.CreateDecryptor(aes_key, null); - using (aesCrypto) + using var aesCrypto = encrypt ? AesECB.CreateEncryptor(aes_key, null) : AesECB.CreateDecryptor(aes_key, null); + byte[] yXOR = new byte[16]; + for (int i = 0; i < input.Length; i += 16) { - byte[] yXOR = new byte[16]; - for (int i = 0; i < input.Length; i += 16) - { - for (int j = 0; j < 16; j++) - yXOR[j] = (byte)(input[i + j] ^ yPrev[j]); - aesCrypto.TransformBlock(yXOR, 0, 16, output, i); - for (int j = 0; j < 16; j++) - output[i + j] ^= xPrev[j]; - xPrev = input.Slice(i, 16); - yPrev = output.AsSpan(i, 16); - } + for (int j = 0; j < 16; j++) + yXOR[j] = (byte)(input[i + j] ^ yPrev[j]); + aesCrypto.TransformBlock(yXOR, 0, 16, output, i); + for (int j = 0; j < 16; j++) + output[i + j] ^= xPrev[j]; + xPrev = input.Slice(i, 16); + yPrev = output.AsSpan(i, 16); } return output; }