diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Login.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Login.cs index 0a8b16735a..9cdb0b0469 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Login.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Login.cs @@ -1,3 +1,5 @@ +using BizHawk.Common; + namespace BizHawk.Client.EmuHawk { public partial class RCheevos @@ -57,7 +59,7 @@ namespace BizHawk.Client.EmuHawk { var config = _getConfig(); Username = config.RAUsername; - ApiToken = config.RAToken; + ApiToken = SensitiveStrings.DecryptString(config.RAToken); if (LoggedIn) { @@ -65,7 +67,7 @@ namespace BizHawk.Client.EmuHawk if (DoLogin(Username, apiToken: ApiToken)) { config.RAUsername = Username; - config.RAToken = ApiToken; + config.RAToken = SensitiveStrings.EncryptString(ApiToken); PlaySound(_loginSound); return; } @@ -75,7 +77,7 @@ namespace BizHawk.Client.EmuHawk loginForm.ShowDialog(); config.RAUsername = Username; - config.RAToken = ApiToken; + config.RAToken = SensitiveStrings.EncryptString(ApiToken); if (LoggedIn) { diff --git a/src/BizHawk.Common/SensitiveStrings.cs b/src/BizHawk.Common/SensitiveStrings.cs new file mode 100644 index 0000000000..29df4acfc4 --- /dev/null +++ b/src/BizHawk.Common/SensitiveStrings.cs @@ -0,0 +1,68 @@ +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace BizHawk.Common +{ + /// + /// Goals + /// 1. Allows for encrypting/decrypting strings, suitable for any sensitive info (e.g. api tokens) to be placed in a config file + /// 2. Encryption is unique to a particular machine, so another person with the string can't decrypt it + /// 3. ASSUMES ANY ATTACK HAS NO KNOWLEDGE OF THE MACHINE NAME + /// 4. As such, DOES NOT protect against attacks which have access to said machine or within the same LAN + /// (This is mainly to mitigate sensitive info leaks in the case the user posted the config publicly, unredacted) + /// (For anyone who's generally security minded, don't trust this to keep your tokens safe) + /// + public static class SensitiveStrings + { + private static readonly Aes _aes; + + static SensitiveStrings() + { + _aes = Aes.Create(); + // lame, but this seems to be the best method to do this... + _aes.Key = SHA256Checksum.Compute(Encoding.UTF8.GetBytes(Environment.MachineName)); + _aes.IV = new byte[_aes.BlockSize / 8]; + _aes.Mode = CipherMode.CBC; + _aes.Padding = PaddingMode.PKCS7; + } + + public static string EncryptString(string sensitiveString) + { + var bytes = Encoding.UTF8.GetBytes(sensitiveString); + using var ms = new MemoryStream(); + using var encryptor = _aes.CreateEncryptor(); + using (var cryptoStream = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(bytes, 0, bytes.Length); + } + + return Convert.ToBase64String(ms.ToArray()); + } + + public static string DecryptString(string encryptedString) + { + try + { + if (string.IsNullOrEmpty(encryptedString)) + { + return string.Empty; + } + + var bytes = Convert.FromBase64String(encryptedString); + using var ms = new MemoryStream(); + using var decryptor = _aes.CreateDecryptor(); + using (var cryptoStream = new CryptoStream(ms, decryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(bytes, 0, bytes.Length); + } + + return Encoding.UTF8.GetString(ms.ToArray()); + } + catch + { + return string.Empty; + } + } + } +}