2021-08-04 00:40:09 +02:00
|
|
|
|
using System;
|
2021-08-06 01:54:29 +02:00
|
|
|
|
using System.Buffers.Binary;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Numerics;
|
2022-10-07 03:00:57 +02:00
|
|
|
|
using System.Runtime.InteropServices;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using TL;
|
2021-08-16 22:30:45 +02:00
|
|
|
|
using static WTelegram.Compat;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
|
|
|
|
|
namespace WTelegram
|
|
|
|
|
|
{
|
|
|
|
|
|
internal static class Encryption
|
|
|
|
|
|
{
|
2024-03-08 11:52:30 +01:00
|
|
|
|
private static readonly Dictionary<long, RSAPublicKey> PublicKeys = [];
|
2022-10-07 03:00:57 +02:00
|
|
|
|
internal static readonly RNGCryptoServiceProvider RNG = new();
|
|
|
|
|
|
internal static readonly Aes AesECB = Aes.Create();
|
2022-01-07 01:14:16 +01:00
|
|
|
|
|
|
|
|
|
|
static Encryption()
|
|
|
|
|
|
{
|
|
|
|
|
|
AesECB.Mode = CipherMode.ECB;
|
|
|
|
|
|
AesECB.Padding = PaddingMode.Zeros;
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (AesECB.BlockSize != 128) throw new WTException("AES Blocksize is not 16 bytes");
|
2022-01-07 01:14:16 +01:00
|
|
|
|
}
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
2021-09-23 09:27:52 +02:00
|
|
|
|
internal static async Task CreateAuthorizationKey(Client client, Session.DCSession session)
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2021-08-19 09:28:56 +02:00
|
|
|
|
if (PublicKeys.Count == 0) LoadDefaultPublicKeys();
|
2021-09-29 04:38:39 +02:00
|
|
|
|
var sha1 = SHA1.Create();
|
|
|
|
|
|
var sha256 = SHA256.Create();
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
|
|
|
|
|
//1)
|
2021-08-09 11:41:50 +02:00
|
|
|
|
var nonce = new Int128(RNG);
|
2022-10-01 13:56:43 +02:00
|
|
|
|
var resPQ = await client.ReqPqMulti(nonce);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
//2)
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (resPQ.nonce != nonce) throw new WTException("Nonce mismatch");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
var fingerprint = resPQ.server_public_key_fingerprints.FirstOrDefault(PublicKeys.ContainsKey);
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (fingerprint == 0) throw new WTException("Couldn't match any server_public_key_fingerprints");
|
2021-08-09 11:41:50 +02:00
|
|
|
|
var publicKey = PublicKeys[fingerprint];
|
2021-08-04 10:11:07 +02:00
|
|
|
|
Helpers.Log(2, $"Selected public key with fingerprint {fingerprint:X}");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
//3)
|
|
|
|
|
|
long retry_id = 0;
|
|
|
|
|
|
ulong pq = Helpers.FromBigEndian(resPQ.pq);
|
|
|
|
|
|
ulong p = Helpers.PQFactorize(pq);
|
|
|
|
|
|
ulong q = pq / p;
|
|
|
|
|
|
//4)
|
2021-08-10 14:40:41 +02:00
|
|
|
|
var pqInnerData = new PQInnerDataDc
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
|
|
|
|
|
pq = resPQ.pq,
|
|
|
|
|
|
p = Helpers.ToBigEndian(p),
|
|
|
|
|
|
q = Helpers.ToBigEndian(q),
|
2021-08-09 11:41:50 +02:00
|
|
|
|
nonce = nonce,
|
2021-08-04 00:40:09 +02:00
|
|
|
|
server_nonce = resPQ.server_nonce,
|
2021-08-09 11:41:50 +02:00
|
|
|
|
new_nonce = new Int256(RNG),
|
2021-08-10 14:40:41 +02:00
|
|
|
|
dc = session.DataCenter?.id ?? 0
|
2021-08-09 11:41:50 +02:00
|
|
|
|
};
|
2022-09-11 15:34:38 +02:00
|
|
|
|
if (client.TLConfig?.test_mode == true) pqInnerData.dc += 10000;
|
2022-06-19 18:08:19 +02:00
|
|
|
|
if (session.DataCenter?.flags.HasFlag(DcOption.Flags.media_only) == true) pqInnerData.dc = -pqInnerData.dc;
|
2021-08-19 09:28:56 +02:00
|
|
|
|
byte[] encrypted_data = null;
|
|
|
|
|
|
{
|
|
|
|
|
|
//4.1) RSA_PAD(data, server_public_key)
|
|
|
|
|
|
using var clearStream = new MemoryStream(256);
|
2022-10-04 00:52:44 +02:00
|
|
|
|
using var writer = new BinaryWriter(clearStream);
|
2021-08-19 09:28:56 +02:00
|
|
|
|
byte[] aes_key = new byte[32], zero_iv = new byte[32];
|
|
|
|
|
|
var n = BigEndianInteger(publicKey.n);
|
2022-08-13 01:20:53 +02:00
|
|
|
|
do
|
2021-08-19 09:28:56 +02:00
|
|
|
|
{
|
|
|
|
|
|
RNG.GetBytes(aes_key);
|
|
|
|
|
|
clearStream.Position = 0;
|
|
|
|
|
|
clearStream.Write(aes_key, 0, 32); // write aes_key as prefix for initial Sha256 computation
|
|
|
|
|
|
writer.WriteTLObject(pqInnerData);
|
|
|
|
|
|
int clearLength = (int)clearStream.Position - 32; // length before padding
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (clearLength > 144) throw new WTException("PQInnerData too big");
|
2021-08-19 09:28:56 +02:00
|
|
|
|
byte[] clearBuffer = clearStream.GetBuffer();
|
|
|
|
|
|
RNG.GetBytes(clearBuffer, 32 + clearLength, 192 - clearLength);
|
2021-09-29 04:38:39 +02:00
|
|
|
|
sha256.ComputeHash(clearBuffer, 0, 32 + 192).CopyTo(clearBuffer, 224); // append Sha256
|
2021-08-19 09:28:56 +02:00
|
|
|
|
Array.Reverse(clearBuffer, 32, 192);
|
|
|
|
|
|
var aes_encrypted = AES_IGE_EncryptDecrypt(clearBuffer.AsSpan(32, 224), aes_key, zero_iv, true);
|
2021-09-29 04:38:39 +02:00
|
|
|
|
var hash_aes = sha256.ComputeHash(aes_encrypted);
|
2021-08-19 09:28:56 +02:00
|
|
|
|
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);
|
|
|
|
|
|
var x = BigEndianInteger(clearBuffer);
|
|
|
|
|
|
if (x < n) // if good result, encrypt with RSA key:
|
|
|
|
|
|
encrypted_data = BigInteger.ModPow(x, BigEndianInteger(publicKey.e), n).To256Bytes();
|
2022-08-13 01:20:53 +02:00
|
|
|
|
} while (encrypted_data == null); // otherwise, repeat the steps
|
2021-08-09 11:41:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
var serverDHparams = await client.ReqDHParams(pqInnerData.nonce, pqInnerData.server_nonce, pqInnerData.p, pqInnerData.q, fingerprint, encrypted_data);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
//5)
|
|
|
|
|
|
var localTime = DateTimeOffset.UtcNow;
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new WTException("not server_DH_params_ok");
|
|
|
|
|
|
if (serverDHparamsOk.nonce != nonce) throw new WTException("Nonce mismatch");
|
|
|
|
|
|
if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch");
|
2021-08-09 11:41:50 +02:00
|
|
|
|
var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(resPQ.server_nonce, pqInnerData.new_nonce);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
var answer = AES_IGE_EncryptDecrypt(serverDHparamsOk.encrypted_answer, tmp_aes_key, tmp_aes_iv, false);
|
|
|
|
|
|
|
2023-02-14 11:14:17 +01:00
|
|
|
|
using var answerReader = new BinaryReader(new MemoryStream(answer));
|
2022-08-13 01:20:53 +02:00
|
|
|
|
var answerHash = answerReader.ReadBytes(20);
|
|
|
|
|
|
var answerObj = answerReader.ReadTLObject();
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (answerObj is not ServerDHInnerData serverDHinnerData) throw new WTException("not server_DH_inner_data");
|
2022-08-13 01:20:53 +02:00
|
|
|
|
long padding = answerReader.BaseStream.Length - answerReader.BaseStream.Position;
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (padding >= 16) throw new WTException("Too much pad");
|
2021-09-29 04:38:39 +02:00
|
|
|
|
if (!Enumerable.SequenceEqual(sha1.ComputeHash(answer, 20, answer.Length - (int)padding - 20), answerHash))
|
2023-04-02 13:44:23 +02:00
|
|
|
|
throw new WTException("Answer SHA1 mismatch");
|
|
|
|
|
|
if (serverDHinnerData.nonce != nonce) throw new WTException("Nonce mismatch");
|
|
|
|
|
|
if (serverDHinnerData.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch");
|
2021-08-16 22:30:45 +02:00
|
|
|
|
var g_a = BigEndianInteger(serverDHinnerData.g_a);
|
|
|
|
|
|
var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime);
|
2022-10-04 00:52:44 +02:00
|
|
|
|
CheckGoodPrime(dh_prime, serverDHinnerData.g);
|
2021-11-02 01:47:14 +01:00
|
|
|
|
session.LastSentMsgId = 0;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
session.ServerTicksOffset = (serverDHinnerData.server_time - localTime).Ticks;
|
2021-11-01 15:44:40 +01:00
|
|
|
|
Helpers.Log(1, $"Time offset: {session.ServerTicksOffset} | Server: {serverDHinnerData.server_time.TimeOfDay} UTC | Local: {localTime.TimeOfDay} UTC");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
//6)
|
2022-10-04 00:52:44 +02:00
|
|
|
|
var salt = new byte[256];
|
|
|
|
|
|
RNG.GetBytes(salt);
|
|
|
|
|
|
var b = BigEndianInteger(salt);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
var g_b = BigInteger.ModPow(serverDHinnerData.g, b, dh_prime);
|
2022-10-04 00:52:44 +02:00
|
|
|
|
CheckGoodGaAndGb(g_a, dh_prime);
|
|
|
|
|
|
CheckGoodGaAndGb(g_b, dh_prime);
|
2021-08-09 11:41:50 +02:00
|
|
|
|
var clientDHinnerData = new ClientDHInnerData
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2021-08-09 11:41:50 +02:00
|
|
|
|
nonce = nonce,
|
2021-08-04 00:40:09 +02:00
|
|
|
|
server_nonce = resPQ.server_nonce,
|
|
|
|
|
|
retry_id = retry_id,
|
|
|
|
|
|
g_b = g_b.ToByteArray(true, true)
|
2021-08-09 11:41:50 +02:00
|
|
|
|
};
|
2021-08-19 09:28:56 +02:00
|
|
|
|
{
|
2021-08-09 11:41:50 +02:00
|
|
|
|
using var clearStream = new MemoryStream(384);
|
|
|
|
|
|
clearStream.Position = 20; // skip SHA1 area (to be patched)
|
2022-10-04 00:52:44 +02:00
|
|
|
|
using var writer = new BinaryWriter(clearStream);
|
2021-08-09 11:41:50 +02:00
|
|
|
|
writer.WriteTLObject(clientDHinnerData);
|
|
|
|
|
|
int clearLength = (int)clearStream.Length; // length before padding (= 20 + message_data_length)
|
|
|
|
|
|
int paddingToAdd = (0x7FFFFFF0 - clearLength) % 16;
|
|
|
|
|
|
clearStream.SetLength(clearLength + paddingToAdd);
|
|
|
|
|
|
byte[] clearBuffer = clearStream.GetBuffer();
|
|
|
|
|
|
RNG.GetBytes(clearBuffer, clearLength, paddingToAdd);
|
2021-09-29 04:38:39 +02:00
|
|
|
|
sha1.ComputeHash(clearBuffer, 20, clearLength - 20).CopyTo(clearBuffer, 0);
|
2021-08-09 11:41:50 +02:00
|
|
|
|
|
|
|
|
|
|
encrypted_data = AES_IGE_EncryptDecrypt(clearBuffer.AsSpan(0, clearLength + paddingToAdd), tmp_aes_key, tmp_aes_iv, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
var setClientDHparamsAnswer = await client.SetClientDHParams(clientDHinnerData.nonce, clientDHinnerData.server_nonce, encrypted_data);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
//7)
|
|
|
|
|
|
var gab = BigInteger.ModPow(g_a, b, dh_prime);
|
|
|
|
|
|
var authKey = gab.ToByteArray(true, true);
|
|
|
|
|
|
//8)
|
2021-09-29 04:38:39 +02:00
|
|
|
|
var authKeyHash = sha1.ComputeHash(authKey);
|
2021-08-06 01:54:29 +02:00
|
|
|
|
retry_id = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash); // (auth_key_aux_hash)
|
|
|
|
|
|
//9)
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (setClientDHparamsAnswer is not DhGenOk dhGenOk) throw new WTException("not dh_gen_ok");
|
|
|
|
|
|
if (dhGenOk.nonce != nonce) throw new WTException("Nonce mismatch");
|
|
|
|
|
|
if (dhGenOk.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
var expected_new_nonceN = new byte[32 + 1 + 8];
|
2021-08-09 11:41:50 +02:00
|
|
|
|
pqInnerData.new_nonce.raw.CopyTo(expected_new_nonceN, 0);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
expected_new_nonceN[32] = 1;
|
|
|
|
|
|
Array.Copy(authKeyHash, 0, expected_new_nonceN, 33, 8); // (auth_key_aux_hash)
|
2021-09-29 04:38:39 +02:00
|
|
|
|
if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, sha1.ComputeHash(expected_new_nonceN).Skip(4)))
|
2023-04-02 13:44:23 +02:00
|
|
|
|
throw new WTException("setClientDHparamsAnswer.new_nonce_hashN mismatch");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
2021-08-06 01:54:29 +02:00
|
|
|
|
session.AuthKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12));
|
2021-08-04 00:40:09 +02:00
|
|
|
|
session.AuthKey = authKey;
|
2021-08-09 11:41:50 +02:00
|
|
|
|
session.Salt = BinaryPrimitives.ReadInt64LittleEndian(pqInnerData.new_nonce.raw) ^ BinaryPrimitives.ReadInt64LittleEndian(resPQ.server_nonce.raw);
|
2023-12-18 00:01:07 +01:00
|
|
|
|
session.OldSalt = session.Salt;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
2021-09-29 04:38:39 +02:00
|
|
|
|
(byte[] key, byte[] iv) ConstructTmpAESKeyIV(Int128 server_nonce, Int256 new_nonce)
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
|
|
|
|
|
byte[] tmp_aes_key = new byte[32], tmp_aes_iv = new byte[32];
|
2021-09-29 04:38:39 +02:00
|
|
|
|
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)
|
2021-08-04 00:40:09 +02:00
|
|
|
|
Array.Copy(new_nonce, 0, tmp_aes_iv, 28, 4); // + new_nonce[0:4]
|
2021-10-17 23:35:14 +02:00
|
|
|
|
sha1.Initialize();
|
2021-08-04 00:40:09 +02:00
|
|
|
|
return (tmp_aes_key, tmp_aes_iv);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-10-04 00:52:44 +02:00
|
|
|
|
internal static void CheckGoodPrime(BigInteger p, int g)
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2021-09-28 16:12:20 +02:00
|
|
|
|
Helpers.Log(2, "Verifying encryption key safety... (this should happen only once per DC)");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
// check that 2^2047 <= p < 2^2048
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (p.GetBitLength() != 2048) throw new WTException("p is not 2048-bit number");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
// check that g generates a cyclic subgroup of prime order (p - 1) / 2, i.e. is a quadratic residue mod p.
|
|
|
|
|
|
if (g switch
|
|
|
|
|
|
{
|
|
|
|
|
|
2 => p % 8 != 7,
|
|
|
|
|
|
3 => p % 3 != 2,
|
|
|
|
|
|
4 => false,
|
2022-10-04 00:52:44 +02:00
|
|
|
|
5 => (int)(p % 5) is not 1 and not 4,
|
|
|
|
|
|
6 => (int)(p % 24) is not 19 and not 23,
|
|
|
|
|
|
7 => (int)(p % 7) is not 3 and not 5 and not 6,
|
2021-08-04 00:40:09 +02:00
|
|
|
|
_ => true,
|
|
|
|
|
|
})
|
2023-04-02 13:44:23 +02:00
|
|
|
|
throw new WTException("Bad prime mod 4g");
|
2021-08-20 02:13:58 +02:00
|
|
|
|
// check whether p is a safe prime (meaning that both p and (p - 1) / 2 are prime)
|
2022-10-01 13:56:43 +02:00
|
|
|
|
if (SafePrimes.Contains(p)) return;
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (!p.IsProbablePrime()) throw new WTException("p is not a prime number");
|
|
|
|
|
|
if (!((p - 1) / 2).IsProbablePrime()) throw new WTException("(p - 1) / 2 is not a prime number");
|
2022-10-01 13:56:43 +02:00
|
|
|
|
SafePrimes.Add(p);
|
2021-08-20 02:13:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-19 11:48:45 +01:00
|
|
|
|
private static readonly HashSet<BigInteger> SafePrimes = [ new( // C71CAEB9C6B1C904...
|
|
|
|
|
|
[
|
2022-10-01 13:56:43 +02:00
|
|
|
|
0x5B, 0xCC, 0x2F, 0xB9, 0xE3, 0xD8, 0x9C, 0x11, 0x03, 0x04, 0xB1, 0x34, 0xF0, 0xAD, 0x4F, 0x6F,
|
|
|
|
|
|
0xBF, 0x54, 0x24, 0x4B, 0xD0, 0x15, 0x4E, 0x2E, 0xEE, 0x05, 0xB1, 0x35, 0xF6, 0x15, 0x81, 0x0D,
|
|
|
|
|
|
0x1F, 0x85, 0x29, 0xE9, 0x0C, 0x85, 0x56, 0xD9, 0x59, 0xF9, 0x7B, 0xF4, 0x49, 0x28, 0xED, 0x0D,
|
|
|
|
|
|
0x05, 0x70, 0xED, 0x5E, 0xFF, 0xA9, 0x7F, 0xF8, 0xA0, 0xBE, 0x3E, 0xE8, 0x15, 0xFC, 0x18, 0xE4,
|
|
|
|
|
|
0xE4, 0x9A, 0x5B, 0xEF, 0x8F, 0x92, 0xA3, 0x9C, 0xFF, 0xD6, 0xB0, 0x65, 0xC4, 0x6B, 0x9C, 0x16,
|
|
|
|
|
|
0x8D, 0x17, 0xB1, 0x2D, 0x58, 0x46, 0xDD, 0xB9, 0xB4, 0x65, 0x59, 0x0D, 0x95, 0xED, 0x17, 0xFD,
|
|
|
|
|
|
0x54, 0x47, 0x28, 0xF1, 0x0E, 0x4E, 0x14, 0xB3, 0x14, 0x2A, 0x4B, 0xA8, 0xD8, 0x74, 0xBA, 0x0D,
|
|
|
|
|
|
0x41, 0x6B, 0x0F, 0x6B, 0xB5, 0x53, 0x27, 0x16, 0x7E, 0x90, 0x51, 0x10, 0x81, 0x95, 0xA6, 0xA4,
|
|
|
|
|
|
0xA4, 0xF9, 0x7C, 0xE6, 0xBE, 0x60, 0x90, 0x3A, 0x4F, 0x3C, 0x8E, 0x37, 0x9B, 0xFA, 0x08, 0x07,
|
|
|
|
|
|
0x88, 0x49, 0xCC, 0xC8, 0x4A, 0x1D, 0xCD, 0x5B, 0x1D, 0x94, 0x2A, 0xBB, 0x96, 0xFE, 0x77, 0x24,
|
|
|
|
|
|
0x64, 0x5F, 0x59, 0x8C, 0xAF, 0x8F, 0xF1, 0x54, 0x84, 0x32, 0x69, 0x29, 0x51, 0x46, 0x97, 0xDC,
|
|
|
|
|
|
0xAB, 0x13, 0x6B, 0x6B, 0xFE, 0xD4, 0x8C, 0xC6, 0x5A, 0x70, 0x58, 0x94, 0xF6, 0x51, 0xFD, 0x20,
|
|
|
|
|
|
0x37, 0x7C, 0xCE, 0x4C, 0xD4, 0xAE, 0x43, 0x95, 0x13, 0x25, 0xC9, 0x0A, 0x6E, 0x6F, 0x33, 0xFA,
|
|
|
|
|
|
0xDB, 0xF4, 0x30, 0x25, 0xD2, 0x93, 0x94, 0x22, 0x58, 0x40, 0xC1, 0xA7, 0x0A, 0x8A, 0x19, 0x48,
|
|
|
|
|
|
0x0F, 0x93, 0x3D, 0x56, 0x37, 0xD0, 0x34, 0x49, 0xC1, 0x21, 0x3E, 0x8E, 0x23, 0x40, 0x0D, 0x98,
|
|
|
|
|
|
0x73, 0x3F, 0xF1, 0x70, 0x2F, 0x52, 0x6C, 0x8E, 0x04, 0xC9, 0xB1, 0xC6, 0xB9, 0xAE, 0x1C, 0xC7, 0x00
|
2024-03-19 11:48:45 +01:00
|
|
|
|
])];
|
2022-10-01 13:56:43 +02:00
|
|
|
|
|
2022-10-04 00:52:44 +02:00
|
|
|
|
internal static void CheckGoodGaAndGb(BigInteger g, BigInteger dh_prime)
|
2021-08-20 02:13:58 +02:00
|
|
|
|
{
|
|
|
|
|
|
// check that g, g_a and g_b are greater than 1 and less than dh_prime - 1.
|
2021-08-04 00:40:09 +02:00
|
|
|
|
// We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well.
|
2022-10-04 00:52:44 +02:00
|
|
|
|
if (g.GetBitLength() < 2048 - 64 || (dh_prime - g).GetBitLength() < 2048 - 64)
|
2023-04-02 13:44:23 +02:00
|
|
|
|
throw new WTException("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void LoadPublicKey(string pem)
|
|
|
|
|
|
{
|
|
|
|
|
|
using var rsa = RSA.Create();
|
2021-09-29 04:38:39 +02:00
|
|
|
|
using var sha1 = SHA1.Create();
|
2021-08-04 00:40:09 +02:00
|
|
|
|
rsa.ImportFromPem(pem);
|
|
|
|
|
|
var rsaParam = rsa.ExportParameters(false);
|
2021-10-17 23:35:14 +02:00
|
|
|
|
if (rsaParam.Modulus[0] == 0) rsaParam.Modulus = rsaParam.Modulus[1..];
|
2021-08-04 00:40:09 +02:00
|
|
|
|
var publicKey = new RSAPublicKey { n = rsaParam.Modulus, e = rsaParam.Exponent };
|
2022-02-11 18:05:12 +01:00
|
|
|
|
using var memStream = new MemoryStream(280);
|
|
|
|
|
|
using (var writer = new BinaryWriter(memStream))
|
|
|
|
|
|
writer.WriteTLObject(publicKey);
|
|
|
|
|
|
var bareData = memStream.ToArray();
|
2021-09-29 04:38:39 +02:00
|
|
|
|
var fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(bareData, 4, bareData.Length - 4).AsSpan(12)); // 64 lower-order bits of SHA1
|
2021-08-04 00:40:09 +02:00
|
|
|
|
PublicKeys[fingerprint] = publicKey;
|
2021-08-04 10:11:07 +02:00
|
|
|
|
Helpers.Log(1, $"Loaded a public key with fingerprint {fingerprint:X}");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-10-01 13:56:43 +02:00
|
|
|
|
private static void LoadDefaultPublicKeys()
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2021-08-19 09:28:56 +02:00
|
|
|
|
// Production Public Key (D09D1D85DE64FD85)
|
|
|
|
|
|
LoadPublicKey(@"-----BEGIN RSA PUBLIC KEY-----
|
|
|
|
|
|
MIIBCgKCAQEA6LszBcC1LGzyr992NzE0ieY+BSaOW622Aa9Bd4ZHLl+TuFQ4lo4g
|
|
|
|
|
|
5nKaMBwK/BIb9xUfg0Q29/2mgIR6Zr9krM7HjuIcCzFvDtr+L0GQjae9H0pRB2OO
|
|
|
|
|
|
62cECs5HKhT5DZ98K33vmWiLowc621dQuwKWSQKjWf50XYFw42h21P2KXUGyp2y/
|
|
|
|
|
|
+aEyZ+uVgLLQbRA1dEjSDZ2iGRy12Mk5gpYc397aYp438fsJoHIgJ2lgMv5h7WY9
|
|
|
|
|
|
t6N/byY9Nw9p21Og3AoXSL2q/2IJ1WRUhebgAdGVMlV1fkuOQoEzR7EdpqtQD9Cs
|
|
|
|
|
|
5+bfo3Nhmcyvk5ftB0WkJ9z6bNZ7yxrP8wIDAQAB
|
|
|
|
|
|
-----END RSA PUBLIC KEY-----");
|
|
|
|
|
|
// Test Public Key (B25898DF208D2603)
|
|
|
|
|
|
LoadPublicKey(@"-----BEGIN RSA PUBLIC KEY-----
|
|
|
|
|
|
MIIBCgKCAQEAyMEdY1aR+sCR3ZSJrtztKTKqigvO/vBfqACJLZtS7QMgCGXJ6XIR
|
|
|
|
|
|
yy7mx66W0/sOFa7/1mAZtEoIokDP3ShoqF4fVNb6XeqgQfaUHd8wJpDWHcR2OFwv
|
|
|
|
|
|
plUUI1PLTktZ9uW2WE23b+ixNwJjJGwBDJPQEQFBE+vfmH0JP503wr5INS1poWg/
|
|
|
|
|
|
j25sIWeYPHYeOrFp/eXaqhISP6G+q2IeTaWTXpwZj4LzXq5YOpk4bYEQ6mvRq7D1
|
|
|
|
|
|
aHWfYmlEGepfaYR8Q0YqvvhYtMte3ITnuSJs171+GDqpdKcSwHnd6FudwGO4pcCO
|
|
|
|
|
|
j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
|
|
|
|
|
|
-----END RSA PUBLIC KEY-----");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-10-01 13:56:43 +02:00
|
|
|
|
internal static byte[] EncryptDecryptMessage(Span<byte> input, bool encrypt, int x, byte[] authKey, byte[] msgKey, int msgKeyOffset, SHA256 sha256)
|
2021-09-29 04:38:39 +02:00
|
|
|
|
{
|
|
|
|
|
|
// first, construct AES key & IV
|
|
|
|
|
|
byte[] aes_key = new byte[32], aes_iv = new byte[32];
|
2021-09-23 05:37:00 +02:00
|
|
|
|
sha256.TransformBlock(msgKey, msgKeyOffset, 16, null, 0); // msgKey
|
|
|
|
|
|
sha256.TransformFinalBlock(authKey, x, 36); // authKey[x:36]
|
2021-08-10 14:40:41 +02:00
|
|
|
|
var sha256_a = sha256.Hash;
|
2021-09-02 00:00:54 +02:00
|
|
|
|
sha256.Initialize();
|
2021-09-23 05:37:00 +02:00
|
|
|
|
sha256.TransformBlock(authKey, 40 + x, 36, null, 0); // authKey[40+x:36]
|
|
|
|
|
|
sha256.TransformFinalBlock(msgKey, msgKeyOffset, 16); // msgKey
|
2021-08-10 14:40:41 +02:00
|
|
|
|
var sha256_b = sha256.Hash;
|
2021-10-17 23:35:14 +02:00
|
|
|
|
sha256.Initialize();
|
2021-08-10 14:40:41 +02:00
|
|
|
|
Array.Copy(sha256_a, 0, aes_key, 0, 8);
|
|
|
|
|
|
Array.Copy(sha256_b, 8, aes_key, 8, 16);
|
|
|
|
|
|
Array.Copy(sha256_a, 24, aes_key, 24, 8);
|
|
|
|
|
|
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);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
return AES_IGE_EncryptDecrypt(input, aes_key, aes_iv, encrypt);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-10-07 03:00:57 +02:00
|
|
|
|
internal static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2023-04-02 13:44:23 +02:00
|
|
|
|
if (input.Length % 16 != 0) throw new WTException("AES_IGE input size not divisible by 16");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
2022-02-11 02:43:48 +01:00
|
|
|
|
using var aesCrypto = encrypt ? AesECB.CreateEncryptor(aes_key, null) : AesECB.CreateDecryptor(aes_key, null);
|
2022-10-07 03:00:57 +02:00
|
|
|
|
var output = new byte[input.Length];
|
|
|
|
|
|
var prevBytes = (byte[])aes_iv.Clone();
|
|
|
|
|
|
var span = MemoryMarshal.Cast<byte, long>(input);
|
|
|
|
|
|
var sout = MemoryMarshal.Cast<byte, long>(output);
|
|
|
|
|
|
var prev = MemoryMarshal.Cast<byte, long>(prevBytes);
|
|
|
|
|
|
if (!encrypt) { (prev[2], prev[0]) = (prev[0], prev[2]); (prev[3], prev[1]) = (prev[1], prev[3]); }
|
|
|
|
|
|
for (int i = 0, count = input.Length / 8; i < count;)
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2022-10-07 03:00:57 +02:00
|
|
|
|
sout[i] = span[i] ^ prev[0]; sout[i + 1] = span[i + 1] ^ prev[1];
|
|
|
|
|
|
aesCrypto.TransformBlock(output, i * 8, 16, output, i * 8);
|
|
|
|
|
|
prev[0] = sout[i] ^= prev[2]; prev[1] = sout[i + 1] ^= prev[3];
|
|
|
|
|
|
prev[2] = span[i++]; prev[3] = span[i++];
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
return output;
|
|
|
|
|
|
}
|
2021-08-10 08:25:37 +02:00
|
|
|
|
|
2022-01-03 18:15:32 +01:00
|
|
|
|
#if OBFUSCATION
|
2024-03-08 12:07:37 +01:00
|
|
|
|
internal class AesCtr(byte[] key, byte[] ivec) : IDisposable
|
2022-01-03 18:15:32 +01:00
|
|
|
|
{
|
2024-03-08 12:07:37 +01:00
|
|
|
|
readonly ICryptoTransform _encryptor = AesECB.CreateEncryptor(key, null);
|
2023-07-08 01:34:31 +02:00
|
|
|
|
readonly byte[] _ecount = new byte[16];
|
|
|
|
|
|
int _num;
|
2022-01-03 18:15:32 +01:00
|
|
|
|
|
2023-07-08 01:34:31 +02:00
|
|
|
|
public void Dispose() => _encryptor.Dispose();
|
2022-01-03 18:15:32 +01:00
|
|
|
|
|
|
|
|
|
|
public void EncryptDecrypt(byte[] buffer, int length)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < length; i++)
|
|
|
|
|
|
{
|
2023-07-08 01:34:31 +02:00
|
|
|
|
if (_num == 0)
|
2022-01-03 18:15:32 +01:00
|
|
|
|
{
|
2024-03-08 12:07:37 +01:00
|
|
|
|
_encryptor.TransformBlock(ivec, 0, 16, _ecount, 0);
|
2022-01-03 18:15:32 +01:00
|
|
|
|
for (int n = 15; n >= 0; n--) // increment big-endian counter
|
2024-03-08 12:07:37 +01:00
|
|
|
|
if (++ivec[n] != 0) break;
|
2022-01-03 18:15:32 +01:00
|
|
|
|
}
|
2023-07-08 01:34:31 +02:00
|
|
|
|
buffer[i] ^= _ecount[_num];
|
|
|
|
|
|
_num = (_num + 1) % 16;
|
2022-01-03 18:15:32 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// see https://core.telegram.org/mtproto/mtproto-transports#transport-obfuscation
|
|
|
|
|
|
internal static (AesCtr, AesCtr, byte[]) InitObfuscation(byte[] secret, byte protocolId, int dcId)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] preamble = new byte[64];
|
|
|
|
|
|
do
|
|
|
|
|
|
RNG.GetBytes(preamble, 0, 58);
|
|
|
|
|
|
while (preamble[0] == 0xef ||
|
|
|
|
|
|
BinaryPrimitives.ReadUInt32LittleEndian(preamble) is 0x44414548 or 0x54534f50 or 0x20544547 or 0x4954504f or 0x02010316 or 0xdddddddd or 0xeeeeeeee ||
|
|
|
|
|
|
BinaryPrimitives.ReadInt32LittleEndian(preamble.AsSpan(4)) == 0);
|
|
|
|
|
|
preamble[62] = preamble[56]; preamble[63] = preamble[57];
|
|
|
|
|
|
preamble[56] = preamble[57] = preamble[58] = preamble[59] = protocolId;
|
|
|
|
|
|
preamble[60] = (byte)dcId; preamble[61] = (byte)(dcId >> 8);
|
|
|
|
|
|
|
|
|
|
|
|
byte[] recvKey = preamble[8..40], recvIV = preamble[40..56];
|
|
|
|
|
|
Array.Reverse(preamble, 8, 48);
|
|
|
|
|
|
byte[] sendKey = preamble[8..40], sendIV = preamble[40..56];
|
|
|
|
|
|
if (secret != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
using var sha256 = SHA256.Create();
|
|
|
|
|
|
sha256.TransformBlock(sendKey, 0, 32, null, 0);
|
|
|
|
|
|
sha256.TransformFinalBlock(secret, 0, 16);
|
|
|
|
|
|
sendKey = sha256.Hash;
|
|
|
|
|
|
sha256.Initialize();
|
|
|
|
|
|
sha256.TransformBlock(recvKey, 0, 32, null, 0);
|
|
|
|
|
|
sha256.TransformFinalBlock(secret, 0, 16);
|
|
|
|
|
|
recvKey = sha256.Hash;
|
|
|
|
|
|
}
|
2022-01-07 01:14:16 +01:00
|
|
|
|
var sendCtr = new AesCtr(sendKey, sendIV);
|
|
|
|
|
|
var recvCtr = new AesCtr(recvKey, recvIV);
|
2022-01-03 18:15:32 +01:00
|
|
|
|
var encrypted = (byte[])preamble.Clone();
|
|
|
|
|
|
sendCtr.EncryptDecrypt(encrypted, 64);
|
|
|
|
|
|
for (int i = 56; i < 64; i++)
|
|
|
|
|
|
preamble[i] = encrypted[i];
|
|
|
|
|
|
return (sendCtr, recvCtr, preamble);
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2021-12-05 11:47:52 +01:00
|
|
|
|
internal static async Task<InputCheckPasswordSRP> Check2FA(Account_Password accountPassword, Func<Task<string>> getPassword)
|
2021-08-10 08:25:37 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (accountPassword.current_algo is not PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow algo)
|
2021-12-22 18:25:38 +01:00
|
|
|
|
if (accountPassword.current_algo == null && (algo = accountPassword.new_algo as PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow) != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
int salt1len = algo.salt1.Length;
|
|
|
|
|
|
Array.Resize(ref algo.salt1, salt1len + 32);
|
|
|
|
|
|
RNG.GetBytes(algo.salt1, salt1len, 32);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2023-04-02 13:44:23 +02:00
|
|
|
|
throw new WTException("2FA authentication uses an unsupported algo: " + accountPassword.current_algo?.GetType().Name);
|
2021-08-10 08:25:37 +02:00
|
|
|
|
|
|
|
|
|
|
var g = new BigInteger(algo.g);
|
2021-08-16 22:30:45 +02:00
|
|
|
|
var p = BigEndianInteger(algo.p);
|
2022-10-04 00:52:44 +02:00
|
|
|
|
var validTask = Task.Run(() => CheckGoodPrime(p, algo.g));
|
2021-12-01 15:50:35 +01:00
|
|
|
|
|
|
|
|
|
|
System.Threading.Thread.Sleep(100);
|
|
|
|
|
|
Helpers.Log(3, $"This account has enabled 2FA. A password is needed. {accountPassword.hint}");
|
2021-12-05 11:47:52 +01:00
|
|
|
|
var passwordBytes = Encoding.UTF8.GetBytes(await getPassword());
|
2021-08-10 08:25:37 +02:00
|
|
|
|
|
2021-09-29 04:38:39 +02:00
|
|
|
|
using 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.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;
|
2021-08-16 22:30:45 +02:00
|
|
|
|
#if NETCOREAPP2_0_OR_GREATER
|
2021-08-18 00:12:33 +02:00
|
|
|
|
using var derive = new Rfc2898DeriveBytes(hash, algo.salt1, 100000, HashAlgorithmName.SHA512);
|
|
|
|
|
|
var pbkdf2 = derive.GetBytes(64);
|
2021-08-16 22:30:45 +02:00
|
|
|
|
#else
|
|
|
|
|
|
var pbkdf2 = PBKDF2_SHA512(hash, algo.salt1, 100000, 64);
|
|
|
|
|
|
#endif
|
2021-09-29 04:38:39 +02:00
|
|
|
|
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);
|
2021-08-10 08:25:37 +02:00
|
|
|
|
|
2021-12-22 18:25:38 +01:00
|
|
|
|
var v = BigInteger.ModPow(g, x, p);
|
2022-12-12 10:07:31 +01:00
|
|
|
|
if (accountPassword.current_algo == null) // we're computing a new password
|
2021-12-22 18:25:38 +01:00
|
|
|
|
{
|
|
|
|
|
|
await validTask;
|
|
|
|
|
|
return new InputCheckPasswordSRP { A = v.To256Bytes() };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var g_b = BigEndianInteger(accountPassword.srp_B);
|
|
|
|
|
|
var g_b_256 = g_b.To256Bytes();
|
|
|
|
|
|
var g_256 = g.To256Bytes();
|
|
|
|
|
|
|
2021-09-29 04:38:39 +02:00
|
|
|
|
sha256.Initialize();
|
|
|
|
|
|
sha256.TransformBlock(algo.p, 0, 256, null, 0);
|
|
|
|
|
|
sha256.TransformFinalBlock(g_256, 0, 256);
|
|
|
|
|
|
var k = BigEndianInteger(sha256.Hash);
|
2021-08-10 08:25:37 +02:00
|
|
|
|
|
|
|
|
|
|
var k_v = (k * v) % p;
|
2021-08-16 22:30:45 +02:00
|
|
|
|
var a = BigEndianInteger(new Int256(RNG).raw);
|
2021-08-10 08:25:37 +02:00
|
|
|
|
var g_a = BigInteger.ModPow(g, a, p);
|
|
|
|
|
|
var g_a_256 = g_a.To256Bytes();
|
|
|
|
|
|
|
2021-09-29 04:38:39 +02:00
|
|
|
|
sha256.Initialize();
|
|
|
|
|
|
sha256.TransformBlock(g_a_256, 0, 256, null, 0);
|
|
|
|
|
|
sha256.TransformFinalBlock(g_b_256, 0, 256);
|
|
|
|
|
|
var u = BigEndianInteger(sha256.Hash);
|
2021-08-10 08:25:37 +02:00
|
|
|
|
|
|
|
|
|
|
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);
|
2021-12-01 15:50:35 +01:00
|
|
|
|
sha256.Initialize();
|
2021-09-29 04:38:39 +02:00
|
|
|
|
var k_a = sha256.ComputeHash(s_a.To256Bytes());
|
2021-08-10 08:25:37 +02:00
|
|
|
|
|
2021-09-29 04:38:39 +02:00
|
|
|
|
hash = sha256.ComputeHash(algo.p);
|
|
|
|
|
|
var h2 = sha256.ComputeHash(g_256);
|
2021-08-10 08:25:37 +02:00
|
|
|
|
for (int i = 0; i < 32; i++) hash[i] ^= h2[i];
|
2021-09-29 04:38:39 +02:00
|
|
|
|
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;
|
2021-08-10 08:25:37 +02:00
|
|
|
|
|
2021-12-05 11:47:52 +01:00
|
|
|
|
await validTask;
|
2021-08-10 08:25:37 +02:00
|
|
|
|
return new InputCheckPasswordSRP { A = g_a_256, M1 = m1, srp_id = accountPassword.srp_id };
|
|
|
|
|
|
}
|
2021-08-16 22:30:45 +02:00
|
|
|
|
|
|
|
|
|
|
#if !NETCOREAPP2_0_OR_GREATER
|
|
|
|
|
|
// adapted from https://github.com/dotnet/aspnetcore/blob/main/src/DataProtection/Cryptography.KeyDerivation/src/PBKDF2/ManagedPbkdf2Provider.cs
|
2021-10-31 02:40:10 +01:00
|
|
|
|
private static byte[] PBKDF2_SHA512(byte[] password, byte[] salt, int iterationCount, int numBytesRequested)
|
2021-08-16 22:30:45 +02:00
|
|
|
|
{
|
2021-08-18 00:12:33 +02:00
|
|
|
|
// PBKDF2 is defined in NIST SP800-132, Sec. 5.3: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
|
2021-08-16 22:30:45 +02:00
|
|
|
|
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);
|
|
|
|
|
|
|
2021-08-18 00:12:33 +02:00
|
|
|
|
using var hashAlgorithm = new HMACSHA512(password);
|
|
|
|
|
|
for (uint blockIndex = 1; numBytesRemaining > 0; blockIndex++)
|
2021-08-16 22:30:45 +02:00
|
|
|
|
{
|
2021-08-18 00:12:33 +02:00
|
|
|
|
// 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++)
|
2021-08-16 22:30:45 +02:00
|
|
|
|
{
|
2021-08-18 00:12:33 +02:00
|
|
|
|
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).
|
2021-08-16 22:30:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-08-18 00:12:33 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
return retVal; // retVal := T_1 || T_2 || ... || T_n, where T_n may be truncated to meet the desired output length
|
2021-08-16 22:30:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
2022-10-07 03:00:57 +02:00
|
|
|
|
|
2022-10-20 23:12:43 +02:00
|
|
|
|
internal class AES_IGE_Stream : Helpers.IndirectStream
|
2022-10-07 03:00:57 +02:00
|
|
|
|
{
|
2023-07-08 01:34:31 +02:00
|
|
|
|
private readonly ICryptoTransform _aesCrypto;
|
|
|
|
|
|
private readonly byte[] _prevBytes;
|
2022-10-07 03:00:57 +02:00
|
|
|
|
|
2022-11-11 00:33:29 +01:00
|
|
|
|
public AES_IGE_Stream(Stream stream, long size, byte[] key, byte[] iv) : this(stream, key, iv, false) { ContentLength = size; }
|
2022-10-20 23:12:43 +02:00
|
|
|
|
public AES_IGE_Stream(Stream stream, byte[] key, byte[] iv, bool encrypt) : base(stream)
|
2022-10-07 03:00:57 +02:00
|
|
|
|
{
|
2023-07-08 01:34:31 +02:00
|
|
|
|
_aesCrypto = encrypt ? Encryption.AesECB.CreateEncryptor(key, null) : Encryption.AesECB.CreateDecryptor(key, null);
|
|
|
|
|
|
if (encrypt) _prevBytes = (byte[])iv.Clone();
|
|
|
|
|
|
else { _prevBytes = new byte[32]; Array.Copy(iv, 0, _prevBytes, 16, 16); Array.Copy(iv, 16, _prevBytes, 0, 16); }
|
2022-10-07 03:00:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-10-26 17:33:06 +02:00
|
|
|
|
public override long Length => base.Length + 15 & ~15;
|
2022-10-07 11:24:08 +02:00
|
|
|
|
public override bool CanSeek => false;
|
|
|
|
|
|
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
|
|
|
|
|
|
2022-10-07 03:00:57 +02:00
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
|
|
|
|
{
|
|
|
|
|
|
count = _innerStream.Read(buffer, offset, count);
|
|
|
|
|
|
if (count == 0) return 0;
|
|
|
|
|
|
Process(buffer, offset, count);
|
|
|
|
|
|
if (ContentLength.HasValue && _innerStream.Position == _innerStream.Length)
|
|
|
|
|
|
return count - (int)(_innerStream.Position - ContentLength.Value);
|
2022-10-26 17:33:06 +02:00
|
|
|
|
return count + 15 & ~15;
|
2022-10-07 03:00:57 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
|
|
|
|
{
|
|
|
|
|
|
Process(buffer, offset, count);
|
|
|
|
|
|
if (ContentLength.HasValue && _innerStream.Position + count > ContentLength)
|
|
|
|
|
|
count -= (int)(_innerStream.Position + count - ContentLength.Value);
|
|
|
|
|
|
_innerStream.Write(buffer, offset, count);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Process(byte[] buffer, int offset, int count)
|
|
|
|
|
|
{
|
2022-10-26 17:33:06 +02:00
|
|
|
|
count = count + 15 & ~15;
|
2022-10-07 03:00:57 +02:00
|
|
|
|
var span = MemoryMarshal.Cast<byte, long>(buffer.AsSpan(offset, count));
|
2023-07-08 01:34:31 +02:00
|
|
|
|
var prev = MemoryMarshal.Cast<byte, long>(_prevBytes);
|
2022-10-07 03:00:57 +02:00
|
|
|
|
for (offset = 0, count /= 8; offset < count;)
|
|
|
|
|
|
{
|
|
|
|
|
|
prev[0] ^= span[offset]; prev[1] ^= span[offset + 1];
|
2023-07-08 01:34:31 +02:00
|
|
|
|
_aesCrypto.TransformBlock(_prevBytes, 0, 16, _prevBytes, 0);
|
2022-10-07 03:00:57 +02:00
|
|
|
|
prev[0] ^= prev[2]; prev[1] ^= prev[3];
|
|
|
|
|
|
prev[2] = span[offset]; prev[3] = span[offset + 1];
|
|
|
|
|
|
span[offset++] = prev[0]; span[offset++] = prev[1];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|