C# 生成 Steam 令牌验证码

其实已经有比较全面的 nuget 包了 https://github.com/geel9/SteamAuth (ArchiSteamFarm 内部也实现了验证码生成,但是 ASF 太庞大了,代码一时看不过来,SteamAuth 代码比较简单),由于我只需要生成验证码,使用整个库太重,而且还需要添加 Newtonsoft.Json 这个额外包,所以只把生成验证码的逻辑整理出来

没有仔细了解过 TOTP,也许跟 TOTP 的生成方法一样,这里直接贴代码

using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Text;

namespace SteamTest {
    internal class Program {
        private static byte[] steamGuardCodeTranslations = new byte[] { 50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89 };
        private static string SharedSecret = ""; // shared_secret from Steam OAuth manifest file (.maFile)
        public static string TWO_FACTOR_TIME_QUERY = "https://api.steampowered.com/ITwoFactorService/QueryTime/v0001";

        static async Task Main() {
            Console.WriteLine(GenerateSteamGuardCodeForTime(GetSystemUnixTime() + await GetTimeGapAsync()));
        }

        // From https://github.com/geel9/SteamAuth/blob/e9c224f53d4e656cc93adcbcf10cd91d08910202/SteamAuth/SteamGuardAccount.cs#L85
        public static string GenerateSteamGuardCodeForTime(long time) {
            if (SharedSecret == null || SharedSecret.Length == 0) {
                return "";
            }

            string sharedSecretUnescaped = Regex.Unescape(SharedSecret);
            byte[] sharedSecretArray = Convert.FromBase64String(sharedSecretUnescaped);
            byte[] timeArray = new byte[8];

            time /= 30L;

            for (int i = 8; i > 0; i--) {
                timeArray[i - 1] = (byte)time;
                time >>= 8;
            }

            HMACSHA1 hmacGenerator = new HMACSHA1();
            hmacGenerator.Key = sharedSecretArray;
            byte[] hashedData = hmacGenerator.ComputeHash(timeArray);
            byte[] codeArray = new byte[5];
            try {
                byte b = (byte)(hashedData[19] & 0xF);
                int codePoint = (hashedData[b] & 0x7F) << 24 | (hashedData[b + 1] & 0xFF) << 16 | (hashedData[b + 2] & 0xFF) << 8 | (hashedData[b + 3] & 0xFF);

                for (int i = 0; i < 5; ++i) {
                    codeArray[i] = steamGuardCodeTranslations[codePoint % steamGuardCodeTranslations.Length];
                    codePoint /= steamGuardCodeTranslations.Length;
                }
            } catch (Exception) {
                return null; //Change later, catch-alls are bad!
            }
            return Encoding.UTF8.GetString(codeArray);
        }

        public static long GetSystemUnixTime() {
            return (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
        }

        public static async Task<long> GetTimeGapAsync() {
            long currentTime = GetSystemUnixTime();
            HttpClient client = new HttpClient();
            var response = await client.PostAsync(TWO_FACTOR_TIME_QUERY, null);
            string content = await response.Content.ReadAsStringAsync();
            long serverTime = Convert.ToInt64(content.Split("server_time\":\"")[1].Split('"')[0]);
            return serverTime - currentTime;
        }
    }
}

将 SteamAuth 中使用的 WebClient 替换成了 HttpClient

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据