update to .net 8

This commit is contained in:
Vita Chumakova
2024-01-18 18:44:00 +04:00
parent 532fc0c4aa
commit 5aae0c893d
6 changed files with 267 additions and 240 deletions

6
.editorconfig Normal file
View File

@@ -0,0 +1,6 @@
[*.cs]
max_line_length = 130
charset = utf-8
insert_final_newline = true
indent_style = tab
trim_trailing_whitespace = true

View File

@@ -15,17 +15,17 @@ jobs:
with: with:
dotnet-version: ${{ env.DOTNET_SDK_VERSION }} dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
- name: Publish generic - name: Publish generic
run: dotnet publish -c Release -o out/generic -p:UseAppHost=false run: dotnet publish YandexKeyExtractor/YandexKeyExtractor.csproj -c Release -o out/generic -p:UseAppHost=false
- name: Upload generic artifacts - name: Upload generic artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ github.event.repository.name }} name: ${{ github.event.repository.name }}
path: out/generic path: out/generic
- name: Publish Windows version - name: Publish Windows version
run: dotnet publish -c Release -o out/win -p:PublishSingleFile=true -p:PublishTrimmed=true -r win-x64 run: dotnet publish YandexKeyExtractor/YandexKeyExtractor.csproj -c Release -o out/win -p:PublishSingleFile=true -p:PublishTrimmed=true -r win-x64
- name: Upload Windows artifacts - name: Upload Windows artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ github.event.repository.name }}-Windows name: ${{ github.event.repository.name }}-Windows
path: out/win path: out/win

View File

@@ -3,41 +3,49 @@ using System.Text;
using CryptSharp.Utility; using CryptSharp.Utility;
using NaCl; using NaCl;
namespace YandexKeyExtractor { namespace YandexKeyExtractor;
public static class Decryptor {
public static string? Decrypt(string encryptedText, string password) {
string base64Text = NormalizeBase64(encryptedText);
ReadOnlySpan<byte> textBytes = Convert.FromBase64String(base64Text).AsSpan(); public static class Decryptor
{
public static string? Decrypt(string encryptedText, string password)
{
var base64Text = NormalizeBase64(encryptedText);
const byte saltLength = 16; ReadOnlySpan<byte> textBytes = Convert.FromBase64String(base64Text).AsSpan();
ReadOnlySpan<byte> textData = textBytes[..^saltLength];
ReadOnlySpan<byte> textSalt = textBytes[^saltLength..];
byte[]? generatedPassword = SCrypt.ComputeDerivedKey(Encoding.UTF8.GetBytes(password), textSalt.ToArray(), 32768, 20, 1, null, 32); const byte SaltLength = 16;
var textData = textBytes[..^SaltLength];
var textSalt = textBytes[^SaltLength..];
using XSalsa20Poly1305 secureBox = new(generatedPassword); var generatedPassword = SCrypt.ComputeDerivedKey(
Encoding.UTF8.GetBytes(password), textSalt.ToArray(), 32768, 20, 1, null, 32
);
const byte nonceLength = 24; using XSalsa20Poly1305 secureBox = new(generatedPassword);
ReadOnlySpan<byte> nonce = textData[..nonceLength];
ReadOnlySpan<byte> dataWithMac = textData[nonceLength..];
const byte NonceLength = 24;
Span<byte> message = dataWithMac.Length <= 4096 ? stackalloc byte[dataWithMac.Length] : new byte[dataWithMac.Length]; var nonce = textData[..NonceLength];
var dataWithMac = textData[NonceLength..];
const byte macLength = 16;
ReadOnlySpan<byte> data = dataWithMac[macLength..];
ReadOnlySpan<byte> mac = dataWithMac[..macLength];
return secureBox.TryDecrypt(message, data, mac, nonce) ? new string(Encoding.UTF8.GetString(message).TrimEnd('\0')) : null; var message = dataWithMac.Length <= 4096 ? stackalloc byte[dataWithMac.Length] : new byte[dataWithMac.Length];
}
private static string NormalizeBase64(string encryptedText) { const byte MacLength = 16;
return encryptedText.Replace('-', '+').Replace('_', '/') + (encryptedText.Length % 4) switch { var data = dataWithMac[MacLength..];
2 => "==", var mac = dataWithMac[..MacLength];
3 => "=",
_ => "" 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 => "=",
_ => ""
};
} }
} }

View File

@@ -1,65 +1,61 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks; using YandexKeyExtractor;
namespace YandexKeyExtractor { Console.WriteLine("Initializing...");
internal static class Program { using var handler = WebHandler.Create();
private static async Task Main() {
Console.WriteLine("Initializing...");
using WebHandler handler = WebHandler.Create();
string country = await handler.TryGetCountry().ConfigureAwait(false); var country = await handler.TryGetCountry();
PromptInput(out string phoneNumber, nameof(phoneNumber)); PromptInput(out var phoneNumber);
phoneNumber = phoneNumber.TrimStart('+'); phoneNumber = phoneNumber.TrimStart('+');
string phone = await handler.GetPhoneNumberInfo(phoneNumber, country).ConfigureAwait(false); var phone = await handler.GetPhoneNumberInfo(phoneNumber, country);
string? trackID = await handler.SendSMSCodeAndGetTrackID(phone, country).ConfigureAwait(false); var trackID = await handler.SendSMSCodeAndGetTrackID(phone, country);
if (string.IsNullOrEmpty(trackID)) { if (string.IsNullOrEmpty(trackID)) {
return; return;
} }
PromptInput(out string smsCode, nameof(smsCode)); PromptInput(out var smsCode);
if (!await handler.CheckCode(smsCode, trackID).ConfigureAwait(false)) { if (!await handler.CheckCode(smsCode, trackID)) {
return; return;
} }
if (!await handler.ValidateBackupInfo(phone, trackID, country).ConfigureAwait(false)) { if (!await handler.ValidateBackupInfo(phone, trackID, country)) {
return; return;
} }
string? backup = await handler.GetBackupData(phone, trackID).ConfigureAwait(false); var backup = await handler.GetBackupData(phone, trackID);
if (string.IsNullOrEmpty(backup)) { if (string.IsNullOrEmpty(backup)) {
return; return;
} }
PromptInput(out string backupPassword, nameof(backupPassword)); PromptInput(out var backupPassword);
Console.WriteLine("Decrypting..."); Console.WriteLine("Decrypting...");
string? message = Decryptor.Decrypt(backup, backupPassword); var message = Decryptor.Decrypt(backup, backupPassword);
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
Console.WriteLine("Decryption failed!"); Console.WriteLine("Decryption failed!");
return; return;
} }
Console.WriteLine("Successfully decrypted!"); Console.WriteLine("Successfully decrypted!");
await File.WriteAllTextAsync("result.txt", message).ConfigureAwait(false); await File.WriteAllTextAsync("result.txt", message);
Console.WriteLine($"Written {message.Split('\n').Length} authenticators to result file"); Console.WriteLine($"Written {message.Split('\n').Length} authenticators to result file");
}
return;
private static void PromptInput(out string result, [CallerArgumentExpression("result")] string argumentName = "") {
Console.WriteLine($"Enter {argumentName}:"); static void PromptInput(out string result, [CallerArgumentExpression("result")] string argumentName = "") {
string? input = Console.ReadLine(); Console.WriteLine($"Enter {argumentName}:");
while (string.IsNullOrEmpty(input)) { var input = Console.ReadLine();
Console.WriteLine($"{argumentName} is invalid, try again:"); while (string.IsNullOrEmpty(input)) {
input = Console.ReadLine(); Console.WriteLine($"{argumentName} is invalid, try again:");
} input = Console.ReadLine();
}
result = input;
} result = input;
}
} }

View File

@@ -11,167 +11,188 @@ using Flurl.Http;
using Flurl.Serialization.TextJson; using Flurl.Serialization.TextJson;
using YandexKeyExtractor.Models; using YandexKeyExtractor.Models;
namespace YandexKeyExtractor { namespace YandexKeyExtractor;
public sealed class WebHandler : IDisposable {
private WebHandler(IFlurlClient client) => Client = client;
private IFlurlClient Client { get; }
public void Dispose() { public sealed class WebHandler : IDisposable
Client.Dispose(); {
} private WebHandler(IFlurlClient client) => Client = client;
private IFlurlClient Client { get; }
public async Task<bool> CheckCode(string? smsCode, string? trackID) { public void Dispose()
StatusResponse? checkCodeResponse = await Client.Request("/bundle/yakey_backup/check_code/") {
.PostUrlEncodedAsync( Client.Dispose();
new { }
code = smsCode,
track_id = trackID
}
)
.ReceiveJson<StatusResponse?>()
.ConfigureAwait(false);
return ValidateResponse(checkCodeResponse, nameof(checkCodeResponse)); public async Task<bool> CheckCode(string? smsCode, string? trackID)
} {
var checkCodeResponse = await Client.Request("/bundle/yakey_backup/check_code/")
public static WebHandler Create() { .PostUrlEncodedAsync(
JsonSerializerOptions jsonSettings = new() { new
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull {
}; code = smsCode,
track_id = trackID
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)); )
.ReceiveJson<StatusResponse?>();
return new WebHandler(client); return ValidateResponse(checkCodeResponse, nameof(checkCodeResponse));
} }
public async Task<string?> GetBackupData(string phone, string? trackID) { public static WebHandler Create()
BackupResponse? backupResponse = await Client.Request("/bundle/yakey_backup/download") {
.PostUrlEncodedAsync( JsonSerializerOptions jsonSettings = new()
new { {
number = phone, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
track_id = trackID };
}
)
.ReceiveJson<BackupResponse?>()
.ConfigureAwait(false);
if (!ValidateResponse(backupResponse, nameof(backupResponse))) { var client = new FlurlClient(
return null; new HttpClient
{
DefaultRequestHeaders = {UserAgent = {new ProductInfoHeaderValue("okhttp", "2.7.5")}},
BaseAddress = new Uri("https://registrator.mobile.yandex.net/1/")
} }
).Configure(settings => settings.WithTextJsonSerializer(jsonSettings));
if (string.IsNullOrEmpty(backupResponse.Backup)) { return new WebHandler(client);
Console.WriteLine("Fatal error - Couldn't find valid backup!"); }
return null; public async Task<string?> GetBackupData(string phone, string? trackID)
} {
var backupResponse = await Client.Request("/bundle/yakey_backup/download")
return backupResponse.Backup; .PostUrlEncodedAsync(
} new
{
public async Task<string> GetPhoneNumberInfo(string? phoneNumber, string country) { number = phone,
PhoneNumberResponse? phoneNumberResponse = await Client.Request("/bundle/validate/phone_number/") track_id = trackID
.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));
} }
)
.ReceiveJson<BackupResponse?>();
return 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)
{
var phoneNumberResponse = await Client.Request("/bundle/validate/phone_number/")
.PostUrlEncodedAsync(
new
{
phone_number = phoneNumber,
country
}
).ReceiveJson<PhoneNumberResponse?>();
ValidateResponse(phoneNumberResponse, nameof(phoneNumberResponse));
var phone = phoneNumberResponse?.PhoneNumber?.StandardizedNumber ?? '+' + phoneNumber;
return phone;
}
public async Task<string?> SendSMSCodeAndGetTrackID(string phone, string country)
{
var trackResponse = await Client.Request("/bundle/yakey_backup/send_code/")
.PostUrlEncodedAsync(
new
{
display_language = "en",
number = phone,
country
}
)
.ReceiveJson<TrackResponse?>();
if (!ValidateResponse(trackResponse, nameof(trackResponse)))
{
return null;
}
var trackID = trackResponse.TrackID;
if (string.IsNullOrEmpty(trackID))
{
Console.WriteLine("Track ID is empty!");
return null;
}
return trackID;
}
public async Task<string> TryGetCountry()
{
var countryResponse = await Client.Request("/suggest/country")
.GetAsync()
.ReceiveJson<CountryResponse?>();
ValidateResponse(countryResponse, nameof(countryResponse));
var country = countryResponse?.Country?.FirstOrDefault() ?? "ru";
return country;
}
public async Task<bool> ValidateBackupInfo(string phone, string? trackID, string country)
{
var backupInfoResponse = await Client.Request("/bundle/yakey_backup/info/")
.PostUrlEncodedAsync(
new
{
number = phone,
track_id = trackID,
country
}
)
.ReceiveJson<BackupInfoResponse?>();
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 true; return false;
} }
return true;
} }
} }

View File

@@ -5,9 +5,9 @@
<FileVersion>1.0.0.0</FileVersion> <FileVersion>1.0.0.0</FileVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<!-- Trimming features --> <!-- Trimming features -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'"> <PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebuggerSupport>false</DebuggerSupport> <DebuggerSupport>false</DebuggerSupport>
@@ -19,10 +19,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CryptSharpStandard" Version="1.0.0" /> <PackageReference Include="CryptSharpStandard" Version="1.0.0" />
<PackageReference Include="Flurl.Http" Version="3.2.4" /> <PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Flurl.Serialization.TextJson" Version="3.1.0" /> <PackageReference Include="Flurl.Serialization.TextJson" Version="3.1.0" />