diff --git a/src/Client.cs b/src/Client.cs index 2bb97d9..5fa27df 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -32,7 +32,6 @@ namespace WTelegram private long _lastRecvMsgId; private readonly List _msgsToAck = new(); private readonly Random _random = new(); - private readonly SHA256 _sha256 = SHA256.Create(); private int _unexpectedSaltChange; private Task _reactorTask; private long _bareRequest; @@ -103,7 +102,7 @@ namespace WTelegram { if (_reactorTask != null) throw new ApplicationException("Already connected!"); - var endpoint = _session.DataCenter == null ? IPEndPoint.Parse(Config("server_address")) + var endpoint = _session.DataCenter == null ? Compat.IPEndPoint_Parse(Config("server_address")) : new IPEndPoint(IPAddress.Parse(_session.DataCenter.ip_address), _session.DataCenter.port); Helpers.Log(2, $"Connecting to {endpoint}..."); //TODO: maintain different connections/sessions to different DCs for main API/file download/file upload? @@ -162,6 +161,7 @@ namespace WTelegram return msg.GetType().Name; }; +#pragma warning disable CA1835 // necessary for .NET Standard 2.0 compilation private async Task SendAsync(ITLFunction func, bool isContent) { if (_session.AuthKeyID != 0 && isContent && CheckMsgsToAck() is MsgsAck msgsAck) @@ -219,10 +219,10 @@ namespace WTelegram BinaryPrimitives.WriteInt32LittleEndian(clearBuffer.AsSpan(prepend + 28), clearLength - 32); // patch message_data_length RNG.GetBytes(clearBuffer, prepend + clearLength, padding); #if MTPROTO1 - var msgKeyLarge = SHA1.HashData(clearBuffer.AsSpan(0, clearLength)); // padding excluded from computation! + var msgKeyLarge = Sha1.ComputeHash(clearBuffer, 0, clearLength); // padding excluded from computation! const int msgKeyOffset = 4; // msg_key = low 128-bits of SHA1(plaintext) #else - var msgKeyLarge = SHA256.HashData(clearBuffer.AsSpan(0, prepend + clearLength + padding)); + var msgKeyLarge = Sha256.ComputeHash(clearBuffer, 0, prepend + clearLength + padding); const int msgKeyOffset = 8; // msg_key = middle 128-bits of SHA256(authkey_part+plaintext+padding) #endif byte[] encrypted_data = EncryptDecryptMessage(clearBuffer.AsSpan(prepend, clearLength + padding), true, _session.AuthKey, msgKeyLarge); @@ -237,10 +237,9 @@ namespace WTelegram BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength + 4); // patch frame_len with correct value uint crc = Force.Crc32.Crc32Algorithm.Compute(buffer, 0, frameLength); writer.Write(crc); // int32 frame_crc - var frame = memStream.GetBuffer().AsMemory(0, frameLength + 4); //TODO: support Transport obfuscation? - await _networkStream.WriteAsync(frame); + await _networkStream.WriteAsync(memStream.GetBuffer(), 0, frameLength + 4); _lastSentMsg = func; } finally @@ -254,12 +253,13 @@ namespace WTelegram { for (int offset = 0; offset != length;) { - var read = await stream.ReadAsync(buffer.AsMemory(offset, length - offset), ct); + var read = await stream.ReadAsync(buffer, offset, length - offset, ct); if (read == 0) return offset; offset += read; } return length; } +#pragma warning restore CA1835 private async Task RecvFrameAsync(CancellationToken ct) { @@ -343,13 +343,13 @@ namespace WTelegram 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(SHA1.HashData(decrypted_data.AsSpan(0, 32 + length)).AsSpan(4))) + 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}"); - _sha256.TransformBlock(_session.AuthKey, 96, 32, null, 0); - _sha256.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length); - if (!data.AsSpan(8, 16).SequenceEqual(_sha256.Hash.AsSpan(8, 16))) + Sha256Recv.TransformBlock(_session.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 SHA1"); #endif var ctorNb = reader.ReadUInt32(); @@ -587,7 +587,7 @@ namespace WTelegram if (_bareRequest != 0) { var (type, tcs) = PullPendingRequest(_bareRequest); - if (obj.GetType().IsAssignableTo(type)) + if (type.IsAssignableFrom(obj.GetType())) { _bareRequest = 0; _ = Task.Run(() => tcs.SetResult(obj)); diff --git a/src/Compat.cs b/src/Compat.cs new file mode 100644 index 0000000..23bdb6c --- /dev/null +++ b/src/Compat.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Formats.Asn1; +using System.Net; +using System.Numerics; +using System.Security.Cryptography; + +namespace WTelegram +{ + static class Compat + { +#if NETCOREAPP2_1_OR_GREATER + internal static IPEndPoint IPEndPoint_Parse(string addr) + => IPEndPoint.Parse(addr); + internal static BigInteger BigEndianInteger(byte[] value) + => new(value, true, true); +#else + internal static BigInteger BigEndianInteger(byte[] value) + { + var data = new byte[value.Length + 1]; + value.CopyTo(data, 1); + Array.Reverse(data); + return new BigInteger(data); + } + + internal static byte[] ToByteArray(this BigInteger bigInteger, bool isUnsigned = false, bool isBigEndian = false) + { + if (!isBigEndian || !isUnsigned) throw new ArgumentException("Unexpected parameters to BigInteger"); + var result = bigInteger.ToByteArray(); + if (result[^1] == 0) result = result[0..^1]; + Array.Reverse(result); + return result; + } + + internal static int GetBitLength(this BigInteger bigInteger) + { + var bytes = bigInteger.ToByteArray(); + var length = bytes.Length * 8; + int lastByte = bytes[^1]; + while ((lastByte & 0x80) == 0) { length--; lastByte = (lastByte << 1) + 1; } + return length; + } + + public static V GetValueOrDefault(this Dictionary dictionary, K key, V defaultValue = default) + => dictionary.TryGetValue(key, out V value) ? value : defaultValue; + + public static void Deconstruct(this KeyValuePair kvp, out K key, out V value) + { + key = kvp.Key; + value = kvp.Value; + } + + internal static IPEndPoint IPEndPoint_Parse(string addr) + { + int colon = addr.IndexOf(':'); + return new IPEndPoint(IPAddress.Parse(addr[0..colon]), int.Parse(addr[(colon + 1)..])); + } + + internal static void ImportFromPem(this RSA rsa, string pem) + { + var header = pem.IndexOf("-----BEGIN RSA PUBLIC KEY-----"); + var footer = pem.IndexOf("-----END RSA PUBLIC KEY-----"); + if (header == -1 || footer <= header) throw new ArgumentException("Invalid RSA Public Key"); + byte[] bytes = System.Convert.FromBase64String(pem[(header+30)..footer]); + var reader = new AsnReader(bytes, AsnEncodingRules.BER); + reader = reader.ReadSequence(Asn1Tag.Sequence); + var m = reader.ReadIntegerBytes(); + var e = reader.ReadIntegerBytes(); + rsa.ImportParameters(new RSAParameters { Modulus = m.ToArray(), Exponent = e.ToArray() }); + } + } + + static class Convert + { + internal static byte[] FromHexString(string hex) + { + int NumberChars = hex.Length; + byte[] bytes = new byte[NumberChars / 2]; + for (int i = 0; i < NumberChars; i += 2) + bytes[i / 2] = System.Convert.ToByte(hex.Substring(i, 2), 16); + return bytes; + } +#endif + } +} diff --git a/src/Encryption.cs b/src/Encryption.cs index e43a4a1..ced363d 100644 --- a/src/Encryption.cs +++ b/src/Encryption.cs @@ -8,12 +8,20 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using TL; +using static WTelegram.Compat; namespace WTelegram { internal static class Encryption { internal static readonly RNGCryptoServiceProvider RNG = new(); + internal static readonly SHA1 Sha1 = SHA1.Create(); + internal static readonly SHA256 Sha256 = SHA256.Create(); +#if MTPROTO1 + internal static readonly SHA1 Sha1Recv = SHA1.Create(); +#else + internal static readonly SHA256 Sha256Recv = SHA256.Create(); +#endif private static readonly Dictionary PublicKeys = new(); internal static async Task CreateAuthorizationKey(Client client, Session session) @@ -59,9 +67,9 @@ namespace WTelegram if (clearLength > 255) throw new ApplicationException("PQInnerData too big"); byte[] clearBuffer = clearStream.GetBuffer(); RNG.GetBytes(clearBuffer, clearLength, 255 - clearLength); - SHA1.HashData(clearBuffer.AsSpan(20..clearLength), clearBuffer); // patch with SHA1 - encrypted_data = BigInteger.ModPow(new BigInteger(clearBuffer, true, true), // encrypt with RSA key - new BigInteger(publicKey.e, true, true), new BigInteger(publicKey.n, true, true)).ToByteArray(true, true); + 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); } var serverDHparams = await client.ReqDHParams(pqInnerData.nonce, pqInnerData.server_nonce, pqInnerData.p, pqInnerData.q, fingerprint, encrypted_data); //5) @@ -78,19 +86,19 @@ namespace WTelegram if (answerObj is not ServerDHInnerData serverDHinnerData) throw new ApplicationException("not server_DH_inner_data"); long padding = encryptedReader.BaseStream.Length - encryptedReader.BaseStream.Position; if (padding >= 16) throw new ApplicationException("Too much pad"); - if (!Enumerable.SequenceEqual(SHA1.HashData(answer.AsSpan(20..^(int)padding)), answerHash)) + if (!Enumerable.SequenceEqual(Sha1.ComputeHash(answer, 20, answer.Length - (int)padding - 20), answerHash)) throw new ApplicationException("Answer SHA1 mismatch"); if (serverDHinnerData.nonce != nonce) throw new ApplicationException("Nonce mismatch"); if (serverDHinnerData.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch"); - var g_a = new BigInteger(serverDHinnerData.g_a, true, true); - var dh_prime = new BigInteger(serverDHinnerData.dh_prime, true, true); + var g_a = BigEndianInteger(serverDHinnerData.g_a); + var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime); ValidityChecks(dh_prime, serverDHinnerData.g); Helpers.Log(1, $"Server time: {serverDHinnerData.server_time} UTC"); session.ServerTicksOffset = (serverDHinnerData.server_time - localTime).Ticks; //6) var bData = new byte[256]; RNG.GetBytes(bData); - var b = new BigInteger(bData, true, true); + var b = BigEndianInteger(bData); var g_b = BigInteger.ModPow(serverDHinnerData.g, b, dh_prime); var clientDHinnerData = new ClientDHInnerData { @@ -109,7 +117,7 @@ namespace WTelegram clearStream.SetLength(clearLength + paddingToAdd); byte[] clearBuffer = clearStream.GetBuffer(); RNG.GetBytes(clearBuffer, clearLength, paddingToAdd); - SHA1.HashData(clearBuffer.AsSpan(20..clearLength), clearBuffer); + Sha1.ComputeHash(clearBuffer, 20, clearLength - 20).CopyTo(clearBuffer, 0); encrypted_data = AES_IGE_EncryptDecrypt(clearBuffer.AsSpan(0, clearLength + paddingToAdd), tmp_aes_key, tmp_aes_iv, true); } @@ -118,7 +126,7 @@ namespace WTelegram var gab = BigInteger.ModPow(g_a, b, dh_prime); var authKey = gab.ToByteArray(true, true); //8) - var authKeyHash = SHA1.HashData(authKey); + var authKeyHash = Sha1.ComputeHash(authKey); retry_id = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash); // (auth_key_aux_hash) //9) if (setClientDHparamsAnswer is not DhGenOk dhGenOk) throw new ApplicationException("not dh_gen_ok"); @@ -128,7 +136,7 @@ namespace WTelegram pqInnerData.new_nonce.raw.CopyTo(expected_new_nonceN, 0); expected_new_nonceN[32] = 1; Array.Copy(authKeyHash, 0, expected_new_nonceN, 33, 8); // (auth_key_aux_hash) - if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, SHA1.HashData(expected_new_nonceN).Skip(4))) + if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, Sha1.ComputeHash(expected_new_nonceN).Skip(4))) throw new ApplicationException("setClientDHparamsAnswer.new_nonce_hashN mismatch"); session.AuthKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12)); @@ -186,8 +194,8 @@ namespace WTelegram rsa.ImportFromPem(pem); var rsaParam = rsa.ExportParameters(false); var publicKey = new RSAPublicKey { n = rsaParam.Modulus, e = rsaParam.Exponent }; - var bareData = publicKey.Serialize().AsSpan(4); // bare serialization - var fingerprint = BinaryPrimitives.ReadInt64LittleEndian(SHA1.HashData(bareData).AsSpan(12)); // 64 lower-order bits of SHA1 + var bareData = publicKey.Serialize(); // bare serialization + var fingerprint = BinaryPrimitives.ReadInt64LittleEndian(Sha1.ComputeHash(bareData, 4, bareData.Length - 4).AsSpan(12)); // 64 lower-order bits of SHA1 PublicKeys[fingerprint] = publicKey; Helpers.Log(1, $"Loaded a public key with fingerprint {fingerprint:X}"); } @@ -210,7 +218,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB int x = encrypt ? 0 : 8; byte[] aes_key = new byte[32], aes_iv = new byte[32]; #if MTPROTO1 - using var sha1 = SHA1.Create(); + var sha1 = encrypt ? Sha1 : Sha1Recv; sha1.TransformBlock(msgKeyLarge, 4, 16, null, 0); // msgKey sha1.TransformFinalBlock(authKey, x, 32); // authKey[x:32] var sha1_a = sha1.Hash; @@ -232,7 +240,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB Array.Copy(sha1_c, 16, aes_iv, 20, 4); Array.Copy(sha1_d, 0, aes_iv, 24, 8); #else - using var sha256 = SHA256.Create(); + var sha256 = encrypt ? Sha256 : Sha256Recv; sha256.TransformBlock(msgKeyLarge, 8, 16, null, 0); // msgKey sha256.TransformFinalBlock(authKey, x, 36); // authKey[x:36] var sha256_a = sha256.Hash; @@ -289,58 +297,114 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB var passwordBytes = Encoding.UTF8.GetBytes(password); var g = new BigInteger(algo.g); - var p = new BigInteger(algo.p, true, true); - var g_b = new BigInteger(accountPassword.srp_B, true, true); + var p = BigEndianInteger(algo.p); + var g_b = BigEndianInteger(accountPassword.srp_B); var g_b_256 = g_b.To256Bytes(); var g_256 = g.To256Bytes(); ValidityChecks(p, algo.g); - var sha256 = SHA256.Create(); - sha256.TransformBlock(algo.salt1, 0, algo.salt1.Length, null, 0); - sha256.TransformBlock(passwordBytes, 0, passwordBytes.Length, null, 0); - sha256.TransformFinalBlock(algo.salt1, 0, algo.salt1.Length); - var hash = sha256.Hash; - sha256.TransformBlock(algo.salt2, 0, algo.salt2.Length, null, 0); - sha256.TransformBlock(hash, 0, 32, null, 0); - sha256.TransformFinalBlock(algo.salt2, 0, algo.salt2.Length); - hash = sha256.Hash; + Sha256.TransformBlock(algo.salt1, 0, algo.salt1.Length, null, 0); + Sha256.TransformBlock(passwordBytes, 0, passwordBytes.Length, null, 0); + Sha256.TransformFinalBlock(algo.salt1, 0, algo.salt1.Length); + var hash = Sha256.Hash; + Sha256.TransformBlock(algo.salt2, 0, algo.salt2.Length, null, 0); + Sha256.TransformBlock(hash, 0, 32, null, 0); + Sha256.TransformFinalBlock(algo.salt2, 0, algo.salt2.Length); + hash = Sha256.Hash; +#if NETCOREAPP2_0_OR_GREATER var pbkdf2 = new Rfc2898DeriveBytes(hash, algo.salt1, 100000, HashAlgorithmName.SHA512).GetBytes(64); - sha256.TransformBlock(algo.salt2, 0, algo.salt2.Length, null, 0); - sha256.TransformBlock(pbkdf2, 0, 64, null, 0); - sha256.TransformFinalBlock(algo.salt2, 0, algo.salt2.Length); - var x = new BigInteger(sha256.Hash, true, true); +#else + var pbkdf2 = PBKDF2_SHA512(hash, algo.salt1, 100000, 64); +#endif + Sha256.TransformBlock(algo.salt2, 0, algo.salt2.Length, null, 0); + Sha256.TransformBlock(pbkdf2, 0, 64, null, 0); + Sha256.TransformFinalBlock(algo.salt2, 0, algo.salt2.Length); + var x = BigEndianInteger(Sha256.Hash); - sha256.TransformBlock(algo.p, 0, 256, null, 0); - sha256.TransformFinalBlock(g_256, 0, 256); - var k = new BigInteger(sha256.Hash, true, true); + Sha256.TransformBlock(algo.p, 0, 256, null, 0); + Sha256.TransformFinalBlock(g_256, 0, 256); + var k = BigEndianInteger(Sha256.Hash); var v = BigInteger.ModPow(g, x, p); var k_v = (k * v) % p; - var a = new BigInteger(new Int256(RNG).raw, true, true); + var a = BigEndianInteger(new Int256(RNG).raw); var g_a = BigInteger.ModPow(g, a, p); var g_a_256 = g_a.To256Bytes(); - sha256.TransformBlock(g_a_256, 0, 256, null, 0); - sha256.TransformFinalBlock(g_b_256, 0, 256); - var u = new BigInteger(sha256.Hash, true, true); + Sha256.TransformBlock(g_a_256, 0, 256, null, 0); + Sha256.TransformFinalBlock(g_b_256, 0, 256); + var u = BigEndianInteger(Sha256.Hash); var t = (g_b - k_v) % p; //(positive modulo, if the result is negative increment by p) if (t.Sign < 0) t += p; var s_a = BigInteger.ModPow(t, a + u * x, p); - var k_a = SHA256.HashData(s_a.To256Bytes()); + var k_a = Sha256.ComputeHash(s_a.To256Bytes()); - hash = SHA256.HashData(algo.p); - var h2 = SHA256.HashData(g_256); + hash = Sha256.ComputeHash(algo.p); + var h2 = Sha256.ComputeHash(g_256); for (int i = 0; i < 32; i++) hash[i] ^= h2[i]; - sha256.TransformBlock(hash, 0, 32, null, 0); - sha256.TransformBlock(SHA256.HashData(algo.salt1), 0, 32, null, 0); - sha256.TransformBlock(SHA256.HashData(algo.salt2), 0, 32, null, 0); - sha256.TransformBlock(g_a_256, 0, 256, null, 0); - sha256.TransformBlock(g_b_256, 0, 256, null, 0); - sha256.TransformFinalBlock(k_a, 0, 32); - var m1 = sha256.Hash; + var hs1 = Sha256.ComputeHash(algo.salt1); + var hs2 = Sha256.ComputeHash(algo.salt2); + Sha256.TransformBlock(hash, 0, 32, null, 0); + Sha256.TransformBlock(hs1, 0, 32, null, 0); + Sha256.TransformBlock(hs2, 0, 32, null, 0); + Sha256.TransformBlock(g_a_256, 0, 256, null, 0); + Sha256.TransformBlock(g_b_256, 0, 256, null, 0); + Sha256.TransformFinalBlock(k_a, 0, 32); + var m1 = Sha256.Hash; return new InputCheckPasswordSRP { A = g_a_256, M1 = m1, srp_id = accountPassword.srp_id }; } + +#if !NETCOREAPP2_0_OR_GREATER + // adapted from https://github.com/dotnet/aspnetcore/blob/main/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/ManagedPbkdf2Provider.cs + public static byte[] PBKDF2_SHA512(byte[] password, byte[] salt, int iterationCount, int numBytesRequested) + { + // PBKDF2 is defined in NIST SP800-132, Sec. 5.3. + // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf + + byte[] retVal = new byte[numBytesRequested]; + int numBytesWritten = 0; + int numBytesRemaining = numBytesRequested; + + // For each block index, U_0 := Salt || block_index + byte[] saltWithBlockIndex = new byte[checked(salt.Length + sizeof(uint))]; + Buffer.BlockCopy(salt, 0, saltWithBlockIndex, 0, salt.Length); + + using (var hashAlgorithm = new HMACSHA512(password)) + { + for (uint blockIndex = 1; numBytesRemaining > 0; blockIndex++) + { + // write the block index out as big-endian + saltWithBlockIndex[^4] = (byte)(blockIndex >> 24); + saltWithBlockIndex[^3] = (byte)(blockIndex >> 16); + saltWithBlockIndex[^2] = (byte)(blockIndex >> 8); + saltWithBlockIndex[^1] = (byte)blockIndex; + + // U_1 = PRF(U_0) = PRF(Salt || block_index) + // T_blockIndex = U_1 + byte[] U_iter = hashAlgorithm.ComputeHash(saltWithBlockIndex); // this is U_1 + byte[] T_blockIndex = U_iter; + + for (int iter = 1; iter < iterationCount; iter++) + { + U_iter = hashAlgorithm.ComputeHash(U_iter); + for (int j = U_iter.Length - 1; j >= 0; j--) + T_blockIndex[j] ^= U_iter[j]; + // At this point, the 'U_iter' variable actually contains U_{iter+1} (due to indexing differences). + } + + // At this point, we're done iterating on this block, so copy the transformed block into retVal. + int numBytesToCopy = Math.Min(numBytesRemaining, T_blockIndex.Length); + Buffer.BlockCopy(T_blockIndex, 0, retVal, numBytesWritten, numBytesToCopy); + numBytesWritten += numBytesToCopy; + numBytesRemaining -= numBytesToCopy; + } + } + + // retVal := T_1 || T_2 || ... || T_n, where T_n may be truncated to meet the desired output length + return retVal; + } +#endif } } diff --git a/src/Generator.cs b/src/Generator.cs index 13390f9..850f100 100644 --- a/src/Generator.cs +++ b/src/Generator.cs @@ -413,7 +413,7 @@ namespace WTelegram parmType = parmType[(qm + 1)..]; sw.WriteLine($"{tabIndent}\tif ({parmName} != null)"); sw.Write('\t'); - if (MapOptionalType(parmType, parm.name).EndsWith('?')) + if (MapOptionalType(parmType, parm.name).EndsWith("?")) parmName += ".Value"; } switch (parmType) diff --git a/src/Helpers.cs b/src/Helpers.cs index 27592e5..de4bc50 100644 --- a/src/Helpers.cs +++ b/src/Helpers.cs @@ -24,9 +24,15 @@ namespace WTelegram public static long RandomLong() { +#if NETCOREAPP2_1_OR_GREATER Span span = stackalloc long[1]; System.Security.Cryptography.RandomNumberGenerator.Fill(System.Runtime.InteropServices.MemoryMarshal.AsBytes(span)); return span[0]; +#else + var span = new byte[8]; + Encryption.RNG.GetBytes(span); + return BitConverter.ToInt64(span, 0); +#endif } public static byte[] ToBigEndian(ulong value) // variable-size buffer diff --git a/src/Session.cs b/src/Session.cs index f286610..811e2f8 100644 --- a/src/Session.cs +++ b/src/Session.cs @@ -49,7 +49,7 @@ namespace WTelegram using var aes = Aes.Create(); using var decryptor = aes.CreateDecryptor(apiHash, input[0..16]); var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16); - if (!SHA256.HashData(utf8Json.AsSpan(32)).SequenceEqual(utf8Json[0..32])) + if (!Encryption.Sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32])) throw new ApplicationException("Integrity check failed in session loading"); return JsonSerializer.Deserialize(utf8Json.AsSpan(32), Helpers.JsonOptions); } @@ -62,7 +62,7 @@ namespace WTelegram Encryption.RNG.GetBytes(output, 0, 16); using var aes = Aes.Create(); using var encryptor = aes.CreateEncryptor(_apiHash, output[0..16]); - encryptor.TransformBlock(SHA256.HashData(utf8Json), 0, 32, output, 16); + encryptor.TransformBlock(Encryption.Sha256.ComputeHash(utf8Json), 0, 32, output, 16); encryptor.TransformBlock(utf8Json, 0, utf8Json.Length & ~15, output, 48); utf8Json.AsSpan(utf8Json.Length & ~15).CopyTo(finalBlock); encryptor.TransformFinalBlock(finalBlock, 0, utf8Json.Length & 15).CopyTo(output.AsMemory(48 + utf8Json.Length & ~15)); diff --git a/src/WTelegramClient.csproj b/src/WTelegramClient.csproj index 963e370..e72e8f7 100644 --- a/src/WTelegramClient.csproj +++ b/src/WTelegramClient.csproj @@ -2,7 +2,8 @@ Library - net5.0 + netstandard2.0;net5.0 + latest WTelegram true True @@ -10,7 +11,7 @@ snupkg true WTelegramClient - Telegram client library written 100% in C# and .NET Core + Telegram client library written 100% in C# and .NET Standard Wizou Copyright © Olivier Marcoux 2021 Telegram;Client;Api;UserBot;MTProto @@ -21,8 +22,8 @@ - - 1701;1702;1573;1591;0419 + + IDE0079;1701;1702;1573;1591;0419 @@ -35,5 +36,17 @@ + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + +