Fix SHA corruption due to concurrent use of static instance

This commit is contained in:
Wizou 2021-09-29 04:38:39 +02:00
parent 8610f7e809
commit 52fb2a7831
3 changed files with 97 additions and 81 deletions

View file

@ -48,6 +48,13 @@ namespace WTelegram
private Task _connecting;
private CancellationTokenSource _cts;
private int _reactorReconnects = 0;
#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
/// <summary>Welcome to WTelegramClient! 😀</summary>
/// <param name="configProvider">Config callback, is queried for: api_id, api_hash, session_pathname</param>
@ -367,7 +374,11 @@ namespace WTelegram
}
else
{
byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, dataLen - 24), false, _dcSession.AuthKey, data, 8);
#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), 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);
@ -393,14 +404,14 @@ namespace WTelegram
return null;
#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)))
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.Initialize();
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)))
_sha256Recv.Initialize();
_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 SHA1");
#endif
var ctorNb = reader.ReadUInt32();
@ -497,13 +508,14 @@ 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.ComputeHash(clearBuffer, 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)
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);
var msgKeyLarge = _sha256.ComputeHash(clearBuffer, 0, prepend + 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(prepend, clearLength + padding), true, _dcSession.AuthKey, msgKeyLarge, msgKeyOffset);
writer.Write(_dcSession.AuthKeyID); // int64 auth_key_id
writer.Write(msgKeyLarge, msgKeyOffset, 16); // int128 msg_key

View file

@ -15,18 +15,13 @@ 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<long, RSAPublicKey> PublicKeys = new();
internal static async Task CreateAuthorizationKey(Client client, Session.DCSession session)
{
if (PublicKeys.Count == 0) LoadDefaultPublicKeys();
var sha1 = SHA1.Create();
var sha256 = SHA256.Create();
//1)
var nonce = new Int128(RNG);
@ -68,7 +63,7 @@ namespace WTelegram
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
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
@ -87,10 +82,10 @@ namespace WTelegram
if (clearLength > 144) throw new ApplicationException("PQInnerData too big");
byte[] clearBuffer = clearStream.GetBuffer();
RNG.GetBytes(clearBuffer, 32 + clearLength, 192 - clearLength);
Sha256.ComputeHash(clearBuffer, 0, 32 + 192).CopyTo(clearBuffer, 224); // append Sha256
sha256.ComputeHash(clearBuffer, 0, 32 + 192).CopyTo(clearBuffer, 224); // append Sha256
Array.Reverse(clearBuffer, 32, 192);
var aes_encrypted = AES_IGE_EncryptDecrypt(clearBuffer.AsSpan(32, 224), aes_key, zero_iv, true);
var hash_aes = Sha256.ComputeHash(aes_encrypted);
var hash_aes = sha256.ComputeHash(aes_encrypted);
for (int i = 0; i < 32; i++) // prefix aes_encrypted with temp_key_xor
clearBuffer[i] = (byte)(aes_key[i] ^ hash_aes[i]);
aes_encrypted.CopyTo(clearBuffer, 32);
@ -115,7 +110,7 @@ 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.ComputeHash(answer, 20, answer.Length - (int)padding - 20), 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");
@ -147,7 +142,7 @@ namespace WTelegram
clearStream.SetLength(clearLength + paddingToAdd);
byte[] clearBuffer = clearStream.GetBuffer();
RNG.GetBytes(clearBuffer, clearLength, paddingToAdd);
Sha1.ComputeHash(clearBuffer, 20, clearLength - 20).CopyTo(clearBuffer, 0);
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);
}
@ -156,7 +151,7 @@ namespace WTelegram
var gab = BigInteger.ModPow(g_a, b, dh_prime);
var authKey = gab.ToByteArray(true, true);
//8)
var authKeyHash = Sha1.ComputeHash(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");
@ -166,29 +161,29 @@ 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.ComputeHash(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));
session.AuthKey = authKey;
session.Salt = BinaryPrimitives.ReadInt64LittleEndian(pqInnerData.new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw);
static (byte[] key, byte[] iv) ConstructTmpAESKeyIV(Int128 server_nonce, Int256 new_nonce)
(byte[] key, byte[] iv) ConstructTmpAESKeyIV(Int128 server_nonce, Int256 new_nonce)
{
byte[] tmp_aes_key = new byte[32], tmp_aes_iv = new byte[32];
Sha1.Initialize();
Sha1.TransformBlock(new_nonce, 0, 32, null, 0);
Sha1.TransformFinalBlock(server_nonce, 0, 16);
Sha1.Hash.CopyTo(tmp_aes_key, 0); // tmp_aes_key := SHA1(new_nonce + server_nonce)
Sha1.Initialize();
Sha1.TransformBlock(server_nonce, 0, 16, null, 0);
Sha1.TransformFinalBlock(new_nonce, 0, 32);
Array.Copy(Sha1.Hash, 0, tmp_aes_key, 20, 12); // + SHA1(server_nonce, new_nonce)[0:12]
Array.Copy(Sha1.Hash, 12, tmp_aes_iv, 0, 8); // tmp_aes_iv != SHA1(server_nonce, new_nonce)[12:8]
Sha1.Initialize();
Sha1.TransformBlock(new_nonce, 0, 32, null, 0);
Sha1.TransformFinalBlock(new_nonce, 0, 32);
Sha1.Hash.CopyTo(tmp_aes_iv, 8); // + SHA(new_nonce + new_nonce)
sha1.Initialize();
sha1.TransformBlock(new_nonce, 0, 32, null, 0);
sha1.TransformFinalBlock(server_nonce, 0, 16);
sha1.Hash.CopyTo(tmp_aes_key, 0); // tmp_aes_key := SHA1(new_nonce + server_nonce)
sha1.Initialize();
sha1.TransformBlock(server_nonce, 0, 16, null, 0);
sha1.TransformFinalBlock(new_nonce, 0, 32);
Array.Copy(sha1.Hash, 0, tmp_aes_key, 20, 12); // + SHA1(server_nonce, new_nonce)[0:12]
Array.Copy(sha1.Hash, 12, tmp_aes_iv, 0, 8); // tmp_aes_iv != SHA1(server_nonce, new_nonce)[12:8]
sha1.Initialize();
sha1.TransformBlock(new_nonce, 0, 32, null, 0);
sha1.TransformFinalBlock(new_nonce, 0, 32);
sha1.Hash.CopyTo(tmp_aes_iv, 8); // + SHA(new_nonce + new_nonce)
Array.Copy(new_nonce, 0, tmp_aes_iv, 28, 4); // + new_nonce[0:4]
return (tmp_aes_key, tmp_aes_iv);
}
@ -233,11 +228,12 @@ namespace WTelegram
public static void LoadPublicKey(string pem)
{
using var rsa = RSA.Create();
using var sha1 = SHA1.Create();
rsa.ImportFromPem(pem);
var rsaParam = rsa.ExportParameters(false);
var publicKey = new RSAPublicKey { n = rsaParam.Modulus, e = rsaParam.Exponent };
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
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}");
}
@ -276,13 +272,12 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
#endif
}
internal static byte[] EncryptDecryptMessage(Span<byte> input, bool encrypt, byte[] authKey, byte[] msgKey, int msgKeyOffset)
#if MTPROTO1
internal static byte[] EncryptDecryptMessage(Span<byte> 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;
#if MTPROTO1
var sha1 = encrypt ? Sha1 : Sha1Recv;
sha1.Initialize();
sha1.TransformBlock(msgKey, msgKeyOffset, 16, null, 0); // msgKey
sha1.TransformFinalBlock(authKey, x, 32); // authKey[x:32]
@ -307,8 +302,14 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
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
var sha256 = encrypt ? Sha256 : Sha256Recv;
internal static byte[] EncryptDecryptMessage(Span<byte> input, bool encrypt, byte[] authKey, byte[] msgKey, int msgKeyOffset, SHA256 sha256)
{
// first, construct AES key & IV
byte[] aes_key = new byte[32], aes_iv = new byte[32];
int x = encrypt ? 0 : 8;
sha256.Initialize();
sha256.TransformBlock(msgKey, msgKeyOffset, 16, null, 0); // msgKey
sha256.TransformFinalBlock(authKey, x, 36); // authKey[x:36]
@ -323,9 +324,9 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
Array.Copy(sha256_b, 0, aes_iv, 0, 8);
Array.Copy(sha256_a, 8, aes_iv, 8, 16);
Array.Copy(sha256_b, 24, aes_iv, 24, 8);
#endif
return AES_IGE_EncryptDecrypt(input, aes_key, aes_iv, encrypt);
}
#endif
private static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
{
@ -372,32 +373,33 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
var g_256 = g.To256Bytes();
ValidityChecks(p, algo.g);
Sha256.Initialize();
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.Initialize();
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;
using var sha256 = SHA256.Create();
sha256.Initialize();
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.Initialize();
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
using var derive = new Rfc2898DeriveBytes(hash, algo.salt1, 100000, HashAlgorithmName.SHA512);
var pbkdf2 = derive.GetBytes(64);
#else
var pbkdf2 = PBKDF2_SHA512(hash, algo.salt1, 100000, 64);
#endif
Sha256.Initialize();
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.Initialize();
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.Initialize();
Sha256.TransformBlock(algo.p, 0, 256, null, 0);
Sha256.TransformFinalBlock(g_256, 0, 256);
var k = BigEndianInteger(Sha256.Hash);
sha256.Initialize();
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;
@ -405,29 +407,29 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
var g_a = BigInteger.ModPow(g, a, p);
var g_a_256 = g_a.To256Bytes();
Sha256.Initialize();
Sha256.TransformBlock(g_a_256, 0, 256, null, 0);
Sha256.TransformFinalBlock(g_b_256, 0, 256);
var u = BigEndianInteger(Sha256.Hash);
sha256.Initialize();
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.ComputeHash(s_a.To256Bytes());
var k_a = sha256.ComputeHash(s_a.To256Bytes());
hash = Sha256.ComputeHash(algo.p);
var h2 = Sha256.ComputeHash(g_256);
hash = sha256.ComputeHash(algo.p);
var h2 = sha256.ComputeHash(g_256);
for (int i = 0; i < 32; i++) hash[i] ^= h2[i];
var hs1 = Sha256.ComputeHash(algo.salt1);
var hs2 = Sha256.ComputeHash(algo.salt2);
Sha256.Initialize();
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;
var hs1 = sha256.ComputeHash(algo.salt1);
var hs2 = sha256.ComputeHash(algo.salt2);
sha256.Initialize();
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 };
}

View file

@ -35,6 +35,7 @@ namespace WTelegram
public DateTime SessionStart => _sessionStart;
public readonly SemaphoreSlim _sem = new(1);
private readonly DateTime _sessionStart = DateTime.UtcNow;
private readonly SHA256 _sha256 = SHA256.Create();
private string _pathname;
private byte[] _apiHash; // used as AES key for encryption of session file
@ -69,10 +70,11 @@ namespace WTelegram
internal static Session Load(string pathname, byte[] apiHash)
{
var input = File.ReadAllBytes(pathname);
using var sha256 = SHA256.Create();
using var aes = Aes.Create();
using var decryptor = aes.CreateDecryptor(apiHash, input[0..16]);
var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16);
if (!Encryption.Sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32]))
if (!sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32]))
throw new ApplicationException("Integrity check failed in session loading");
return JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), JsonOptions);
}
@ -85,7 +87,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(Encryption.Sha256.ComputeHash(utf8Json), 0, 32, output, 16);
encryptor.TransformBlock(_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));