mirror of
https://github.com/ezhevita/YandexKeyExtractor
synced 2025-08-16 19:40:48 +07:00
Initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
bin/
|
||||
obj/
|
||||
.idea
|
||||
16
YandexKeyExtractor.sln
Normal file
16
YandexKeyExtractor.sln
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YandexKeyExtractor", "YandexKeyExtractor\YandexKeyExtractor.csproj", "{D059A2F0-8723-4479-89AE-59BC5F09B27D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D059A2F0-8723-4479-89AE-59BC5F09B27D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D059A2F0-8723-4479-89AE-59BC5F09B27D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D059A2F0-8723-4479-89AE-59BC5F09B27D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D059A2F0-8723-4479-89AE-59BC5F09B27D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
42
YandexKeyExtractor/Decryptor.cs
Normal file
42
YandexKeyExtractor/Decryptor.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using CryptSharp.Utility;
|
||||
using NaCl;
|
||||
|
||||
namespace YandexKeyExtractor {
|
||||
public static class Decryptor {
|
||||
public static string? Decrypt(string encryptedText, string password) {
|
||||
string base64Text = NormalizeBase64(encryptedText);
|
||||
|
||||
Span<byte> textBytes = Convert.FromBase64String(base64Text).AsSpan();
|
||||
|
||||
const byte saltLength = 16;
|
||||
Span<byte> textData = textBytes[..^saltLength];
|
||||
Span<byte> textSalt = textBytes[^saltLength..];
|
||||
|
||||
byte[]? generatedPassword = SCrypt.ComputeDerivedKey(Encoding.UTF8.GetBytes(password), textSalt.ToArray(), 32768, 20, 1, null, 32);
|
||||
|
||||
using XSalsa20Poly1305 secureBox = new(generatedPassword);
|
||||
|
||||
const byte nonceLength = 24;
|
||||
Span<byte> nonce = textData[..nonceLength];
|
||||
Span<byte> dataWithMac = textData[nonceLength..];
|
||||
|
||||
Span<byte> message = stackalloc byte[dataWithMac.Length];
|
||||
|
||||
const byte macLength = 16;
|
||||
Span<byte> data = dataWithMac[macLength..];
|
||||
Span<byte> mac = dataWithMac[..macLength];
|
||||
|
||||
return secureBox.TryDecrypt(message, data, mac, nonce) ? new string(Encoding.UTF8.GetString(message).TrimEnd('\0')) : null;
|
||||
}
|
||||
|
||||
private static string NormalizeBase64(string encryptedText) {
|
||||
return encryptedText.Replace('-', '+').Replace('_', '/') + (encryptedText.Length % 4) switch {
|
||||
2 => "==",
|
||||
3 => "=",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
14
YandexKeyExtractor/Models/BackupInfoResponse.cs
Normal file
14
YandexKeyExtractor/Models/BackupInfoResponse.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace YandexKeyExtractor.Models {
|
||||
public class BackupInfoResponse : StatusResponse {
|
||||
[JsonPropertyName("backup_info")]
|
||||
public BackupInfo? Info { get; set; }
|
||||
|
||||
public class BackupInfo {
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||
[JsonPropertyName("updated")]
|
||||
public uint Updated { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
8
YandexKeyExtractor/Models/BackupResponse.cs
Normal file
8
YandexKeyExtractor/Models/BackupResponse.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace YandexKeyExtractor.Models {
|
||||
public class BackupResponse : BackupInfoResponse {
|
||||
[JsonPropertyName("backup")]
|
||||
public string? Backup { get; set; }
|
||||
}
|
||||
}
|
||||
8
YandexKeyExtractor/Models/CountryResponse.cs
Normal file
8
YandexKeyExtractor/Models/CountryResponse.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace YandexKeyExtractor.Models {
|
||||
public class CountryResponse : StatusResponse {
|
||||
[JsonPropertyName("country")]
|
||||
public string[]? Country { get; set; }
|
||||
}
|
||||
}
|
||||
13
YandexKeyExtractor/Models/PhoneNumberResponse.cs
Normal file
13
YandexKeyExtractor/Models/PhoneNumberResponse.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace YandexKeyExtractor.Models {
|
||||
public class PhoneNumberResponse : StatusResponse {
|
||||
[JsonPropertyName("number")]
|
||||
public PhoneNumberInfo? PhoneNumber { get; set; }
|
||||
|
||||
public class PhoneNumberInfo {
|
||||
[JsonPropertyName("e164")]
|
||||
public string? StandardizedNumber { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
13
YandexKeyExtractor/Models/StatusResponse.cs
Normal file
13
YandexKeyExtractor/Models/StatusResponse.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace YandexKeyExtractor.Models {
|
||||
public class StatusResponse {
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; set; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public string[]? Errors { get; set; }
|
||||
|
||||
public bool IsSuccess => Status == "ok";
|
||||
}
|
||||
}
|
||||
8
YandexKeyExtractor/Models/TrackResponse.cs
Normal file
8
YandexKeyExtractor/Models/TrackResponse.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace YandexKeyExtractor.Models {
|
||||
public class TrackResponse : StatusResponse {
|
||||
[JsonPropertyName("track_id")]
|
||||
public string? TrackID { get; set; }
|
||||
}
|
||||
}
|
||||
65
YandexKeyExtractor/Program.cs
Normal file
65
YandexKeyExtractor/Program.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace YandexKeyExtractor {
|
||||
internal static class Program {
|
||||
private static async Task Main() {
|
||||
Console.WriteLine("Initializing...");
|
||||
using WebHandler handler = WebHandler.Create();
|
||||
|
||||
string country = await handler.TryGetCountry().ConfigureAwait(false);
|
||||
|
||||
PromptInput(out string phoneNumber, nameof(phoneNumber));
|
||||
|
||||
phoneNumber = phoneNumber.TrimStart('+');
|
||||
string phone = await handler.GetPhoneNumberInfo(phoneNumber, country).ConfigureAwait(false);
|
||||
|
||||
string? trackID = await handler.SendSMSCodeAndGetTrackID(phone, country).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(trackID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PromptInput(out string smsCode, nameof(smsCode));
|
||||
|
||||
if (!await handler.CheckCode(smsCode, trackID).ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await handler.ValidateBackupInfo(phone, trackID, country).ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
string? backup = await handler.GetBackupData(phone, trackID).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(backup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PromptInput(out string backupPassword, nameof(backupPassword));
|
||||
|
||||
Console.WriteLine("Decrypting...");
|
||||
string? message = Decryptor.Decrypt(backup, backupPassword);
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
Console.WriteLine("Decryption failed!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Successfully decrypted!");
|
||||
await File.WriteAllTextAsync("result.txt", message).ConfigureAwait(false);
|
||||
Console.WriteLine($"Written {message.Split('\n').Length} authenticators to result file");
|
||||
}
|
||||
|
||||
private static void PromptInput(out string result, [CallerArgumentExpression("result")] string argumentName = "") {
|
||||
Console.WriteLine($"Enter {argumentName}:");
|
||||
string? input = Console.ReadLine();
|
||||
while (string.IsNullOrEmpty(input)) {
|
||||
Console.WriteLine($"{argumentName} is invalid, try again:");
|
||||
input = Console.ReadLine();
|
||||
}
|
||||
|
||||
result = input;
|
||||
}
|
||||
}
|
||||
}
|
||||
177
YandexKeyExtractor/WebHandler.cs
Normal file
177
YandexKeyExtractor/WebHandler.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Flurl.Http;
|
||||
using Flurl.Serialization.TextJson;
|
||||
using YandexKeyExtractor.Models;
|
||||
|
||||
namespace YandexKeyExtractor {
|
||||
public sealed class WebHandler : IDisposable {
|
||||
private WebHandler(IFlurlClient client) => Client = client;
|
||||
private IFlurlClient Client { get; }
|
||||
|
||||
public void Dispose() {
|
||||
Client.Dispose();
|
||||
}
|
||||
|
||||
public async Task<bool> CheckCode(string? smsCode, string? trackID) {
|
||||
StatusResponse? checkCodeResponse = await Client.Request("/bundle/yakey_backup/check_code/")
|
||||
.PostUrlEncodedAsync(
|
||||
new {
|
||||
code = smsCode,
|
||||
track_id = trackID
|
||||
}
|
||||
)
|
||||
.ReceiveJson<StatusResponse?>()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return ValidateResponse(checkCodeResponse, nameof(checkCodeResponse));
|
||||
}
|
||||
|
||||
public static WebHandler Create() {
|
||||
JsonSerializerOptions jsonSettings = new() {
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
IFlurlClient? client = new FlurlClient(
|
||||
new HttpClient {
|
||||
DefaultRequestHeaders = { UserAgent = { new ProductInfoHeaderValue("okhttp", "2.7.5") } },
|
||||
BaseAddress = new Uri("https://registrator.mobile.yandex.net/1/")
|
||||
}
|
||||
).Configure(settings => settings.WithTextJsonSerializer(jsonSettings));
|
||||
|
||||
return new WebHandler(client);
|
||||
}
|
||||
|
||||
public async Task<string?> GetBackupData(string phone, string? trackID) {
|
||||
BackupResponse? backupResponse = await Client.Request("/bundle/yakey_backup/download")
|
||||
.PostUrlEncodedAsync(
|
||||
new {
|
||||
number = phone,
|
||||
track_id = trackID
|
||||
}
|
||||
)
|
||||
.ReceiveJson<BackupResponse?>()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!ValidateResponse(backupResponse, nameof(backupResponse))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(backupResponse.Backup)) {
|
||||
Console.WriteLine("Fatal error - Couldn't find valid backup!");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return backupResponse.Backup;
|
||||
}
|
||||
|
||||
public async Task<string> GetPhoneNumberInfo(string? phoneNumber, string country) {
|
||||
PhoneNumberResponse? phoneNumberResponse = await Client.Request("/bundle/validate/phone_number/")
|
||||
.PostUrlEncodedAsync(
|
||||
new {
|
||||
phone_number = phoneNumber,
|
||||
country
|
||||
}
|
||||
).ReceiveJson<PhoneNumberResponse?>()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ValidateResponse(phoneNumberResponse, nameof(phoneNumberResponse));
|
||||
|
||||
string phone = phoneNumberResponse?.PhoneNumber?.StandardizedNumber ?? '+' + phoneNumber;
|
||||
|
||||
return phone;
|
||||
}
|
||||
|
||||
public async Task<string?> SendSMSCodeAndGetTrackID(string phone, string country) {
|
||||
TrackResponse? trackResponse = await Client.Request("/bundle/yakey_backup/send_code/")
|
||||
.PostUrlEncodedAsync(
|
||||
new {
|
||||
display_language = "en",
|
||||
number = phone,
|
||||
country
|
||||
}
|
||||
)
|
||||
.ReceiveJson<TrackResponse?>()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!ValidateResponse(trackResponse, nameof(trackResponse))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string? trackID = trackResponse.TrackID;
|
||||
if (string.IsNullOrEmpty(trackID)) {
|
||||
Console.WriteLine("Track ID is empty!");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return trackID;
|
||||
}
|
||||
|
||||
public async Task<string> TryGetCountry() {
|
||||
CountryResponse? countryResponse = await Client.Request("/suggest/country")
|
||||
.GetAsync()
|
||||
.ReceiveJson<CountryResponse?>()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ValidateResponse(countryResponse, nameof(countryResponse));
|
||||
|
||||
string country = countryResponse?.Country?.FirstOrDefault() ?? "ru";
|
||||
|
||||
return country;
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateBackupInfo(string phone, string? trackID, string country) {
|
||||
BackupInfoResponse? backupInfoResponse = await Client.Request("/bundle/yakey_backup/info/")
|
||||
.PostUrlEncodedAsync(
|
||||
new {
|
||||
number = phone,
|
||||
track_id = trackID,
|
||||
country
|
||||
}
|
||||
)
|
||||
.ReceiveJson<BackupInfoResponse?>()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!ValidateResponse(backupInfoResponse, nameof(backupInfoResponse))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (backupInfoResponse.Info?.Updated == null) {
|
||||
Console.WriteLine("Fatal error - Couldn't find valid backup!");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private static bool ValidateResponse<T>([NotNullWhen(true)] T? response, [CallerArgumentExpression("response")] string responseName = "") where T : StatusResponse {
|
||||
if (response == null) {
|
||||
Console.WriteLine(responseName + " failed!");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!response.IsSuccess) {
|
||||
Console.WriteLine(responseName + $" failed with error {response.Status}!");
|
||||
if (response.Errors != null) {
|
||||
Console.WriteLine("Errors: " + string.Join(',', response.Errors));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
YandexKeyExtractor/YandexKeyExtractor.csproj
Normal file
21
YandexKeyExtractor/YandexKeyExtractor.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CryptSharpStandard" Version="1.0.0" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.0" />
|
||||
<PackageReference Include="Flurl.Serialization.TextJson" Version="3.0.0" />
|
||||
<PackageReference Include="NaCl.Net" Version="0.1.13" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user