using System; using System.Security.Cryptography; using System.Text; public enum ControllerAuthenticationState { NotStarted, AwaitingControllerVersion, ReadyToStartHandshake, AwaitingChallenge, AwaitingControllerProof, AwaitingReady, Ready, Failed } public sealed class ControllerAuthenticator { private string _hostChallenge; public ControllerAuthenticationState State { get; private set; } public void BeginControllerReset() { _hostChallenge = null; State = ControllerAuthenticationState.AwaitingControllerVersion; } public bool AcceptControllerVersion(string line) { if (State != ControllerAuthenticationState.AwaitingControllerVersion || !BoardAuthentication.IsControllerVersionLine(line)) return false; State = ControllerAuthenticationState.ReadyToStartHandshake; return true; } public string StartHandshake() { if (State != ControllerAuthenticationState.ReadyToStartHandshake) return null; _hostChallenge = BoardAuthentication.CreateChallengeHex(); State = ControllerAuthenticationState.AwaitingChallenge; return "07" + _hostChallenge; } public string Handle(string line) { if (State == ControllerAuthenticationState.AwaitingChallenge && BoardAuthentication.TryExtractHexPayload(line, "CHAL", out string controllerChallenge)) { State = ControllerAuthenticationState.AwaitingControllerProof; return "08" + BoardAuthentication.EncryptHexBlock(controllerChallenge); } if (State == ControllerAuthenticationState.AwaitingControllerProof && BoardAuthentication.TryExtractHexPayload(line, "RAES", out string encryptedResponse)) { if (!BoardAuthentication.ValidateEncryptedResponse(_hostChallenge, encryptedResponse)) { Fail(); return null; } State = ControllerAuthenticationState.AwaitingReady; return null; } if (State == ControllerAuthenticationState.AwaitingReady && string.Equals(line, "READY!", StringComparison.Ordinal)) { State = ControllerAuthenticationState.Ready; } return null; } public void Reset() { _hostChallenge = null; State = ControllerAuthenticationState.NotStarted; } public void Fail() { _hostChallenge = null; State = ControllerAuthenticationState.Failed; } } public static class BoardAuthentication { private const int _blockSizeBytes = 16; private const int _hexBlockLength = _blockSizeBytes * 2; private const byte _keyObfuscationMask = 0x37; private static readonly byte[] _aesObfuscatedKey = { 0x69, 0x40, 0xC7, 0x0E, 0xF2, 0xC7, 0x45, 0x70, 0x72, 0xCD, 0x28, 0x58, 0x99, 0x8F, 0x73, 0xA8, 0x5F, 0x5F, 0xED, 0xB4, 0xC1, 0x62, 0xB4, 0x24, 0xAC, 0xD7, 0xA3, 0xD6, 0xD8, 0x0B, 0x50, 0x37 }; public static string CreateChallengeHex() { byte[] challenge = new byte[_blockSizeBytes]; using RandomNumberGenerator generator = RandomNumberGenerator.Create(); generator.GetBytes(challenge); return ToHex(challenge); } public static bool IsControllerVersionLine(string line) => !string.IsNullOrEmpty(line) && line.IndexOf("ver_", StringComparison.OrdinalIgnoreCase) >= 0; public static string EncryptHexBlock(string plainHex) { byte[] plain = ParseHexBlock(plainHex); using Aes aes = Aes.Create(); aes.KeySize = 256; aes.BlockSize = 128; aes.Mode = CipherMode.ECB; aes.Padding = PaddingMode.None; aes.Key = GetAesKey(); using ICryptoTransform encryptor = aes.CreateEncryptor(); byte[] encrypted = encryptor.TransformFinalBlock(plain, 0, plain.Length); return ToHex(encrypted); } public static bool TryExtractHexPayload(string line, string prefix, out string payload) { payload = null; if (string.IsNullOrEmpty(line) || string.IsNullOrEmpty(prefix) || line.Length != prefix.Length + _hexBlockLength || !line.StartsWith(prefix, StringComparison.Ordinal)) return false; string candidate = line.Substring(prefix.Length); for (int i = 0; i < candidate.Length; i++) { if (!IsHexCharacter(candidate[i])) return false; } payload = candidate.ToUpperInvariant(); return true; } public static bool ValidateEncryptedResponse(string challengeHex, string responseHex) { if (!TryNormalizeHexBlock(responseHex, out string normalizedResponse)) return false; byte[] expectedResponse = ParseHexBlock(EncryptHexBlock(challengeHex)); byte[] receivedResponse = ParseHexBlock(normalizedResponse); return CryptographicOperations.FixedTimeEquals(expectedResponse, receivedResponse); } private static byte[] ParseHexBlock(string value) { if (!TryNormalizeHexBlock(value, out string normalized)) throw new ArgumentException($"Ожидался HEX-блок длиной {_hexBlockLength} символа.", nameof(value)); byte[] bytes = new byte[_blockSizeBytes]; for (int i = 0; i < bytes.Length; i++) bytes[i] = Convert.ToByte(normalized.Substring(i * 2, 2), 16); return bytes; } private static bool TryNormalizeHexBlock(string value, out string normalized) { normalized = null; if (string.IsNullOrEmpty(value) || value.Length != _hexBlockLength) return false; for (int i = 0; i < value.Length; i++) { if (!IsHexCharacter(value[i])) return false; } normalized = value.ToUpperInvariant(); return true; } private static bool IsHexCharacter(char value) => value >= '0' && value <= '9' || value >= 'A' && value <= 'F' || value >= 'a' && value <= 'f'; private static byte[] GetAesKey() { byte[] key = new byte[_aesObfuscatedKey.Length]; for (int i = 0; i < key.Length; i++) key[i] = (byte)(_aesObfuscatedKey[i] ^ _keyObfuscationMask); return key; } private static string ToHex(byte[] bytes) { StringBuilder builder = new(bytes.Length * 2); for (int i = 0; i < bytes.Length; i++) builder.Append(bytes[i].ToString("X2")); return builder.ToString(); } }