废话

这个项目最初是由于一篇三千字的检讨引发的,那是一个悠闲的午后在公园里吃着凉米线: )一个电话让我陷入了沉思,我记得上次写这么多字的时候还是在上一次;

思路

先查阅互联网易校园相关的打卡项目,发现了一个简单的打卡方案,通过抓包调试来获取手机打卡时提交的表单信息,此方法的步骤核心就是uutoken提交表单的唯一凭据,作用是让打卡后台识别你的身份以完成信息提交;那么这个uutoken一定是在用户登录自己账户时获取的,并且具有一定的时效性,在失效后可通过用户登录后获取的token重新申请;显而易见这种方法只能用于接下来半个月至一个月的打卡,过期后得在重新抓包一次,过程很繁琐我不能忍受。

加密分析

我的想法就是要实现通过登录获取到用户的token这样上述的两个痛点都可以解决,通过对登录时的抓包分析,发现登录时发送的密码是256位加密字符串,首先想到是常用的RSA非对称加密,发送的字符长度符合这一特性 ,在登录之前手机都会通过GetPublicKey接口向服务器申请公钥,那么加密方式基本就可以确定了;接下来就是将抓包后的表单copy到接口调试工具中,找一个RSA加密的工具,将获取到的RSA公钥和明文密码一起填入,后将生成加密的字符串填入表单中并发送请求至Login接口,然而返回结果提示密码错误。

加密延申

经过我的再三确认,密码明文没有问题,感觉可能是还混合了其他的加密方法;一般在做客户端数据通信的时候都会针对通信的数据进行加固,数据传输过程中的加密主要是为防止中间人攻击,显然我现在做为软件的使用者不用去考虑这个逆向过程,剩下的就是针对用户者本身的安全措施我首先想到salt加盐,这个就非常棘手,继续深入分析如果使用这种加密手段客户端和服务端都会约定这个salt,抓取了整个客户端运行时发送接收的所有请求,先过滤掉大部分无用的请求,逐个进行分析(这个过程很漫长就不一一展开),结果也没有发现可疑的字段。现在有两个假设摆在面前,第一这个加盐使用一些约定俗成的办法比如md5, sha1, base64这些不需要传递seed的手段?继续实验,将密码通过以上的加密方式进行了混淆后在使用RSA公钥进行加密(这是我离成功最近的一次),结果也不对,我整个人emo了;第二就是这个加密的seed被直接写死在app的配置文件中,我综合以上方法,也只能继续做下去!

APP破解

破解app这个比较麻烦,在此之前我试着从浏览器的登录方式进行求证,抓包分析了网页登录时的加密方法,结果js加密脚本被加密了what fuck?手上还没有成熟的解密脚本的方案,暂时搁置。先回到APk安装包上,之前搞过逆向安装包,还算是有些经验,翻出了很久没用的破解工具套装对着这个APK就是一顿反向工程,解密后得到以下文件👇

apk-filemenu

进入编辑器找到了这个非常重要的目录,整个Android的手机端设计架构使用了H5作为前端,可以覆盖大部分平台用一套源码就可以实现且具有非常强的灵活性和适配性,缺点也显而易见了。

apk-sourcemenu

登录算法也在其中,这下终于是一锅端了,最终的加密算法是hex + md5混淆然后再RSA加密,之前的分析还是挺准的,上面的操作差一点就给我猜对了,还是差一点点。接下来就是代码迁移,Here we go!
apk-login-encryption

算法验证

经过以上折腾,将加密方法移植到.NET上后测试,那个Response终于如期的返回了登录成功!,加密的类已经在下方,该c#加密类是从网路上找的,恰好能用上。

登录与校验

登录是可以登录了,但是!设备只能有一个在线,也就是说自动打卡端登录后手机端就会自动下线,这很烦哦!那有没有办法可以不影响手机端又可以自动打开同时进行?有!依据我的经验,将clientid 和 oaid留空即可,这样服务端无法校验手机端是否在新设备登录,就不会贸然的发送给手机下线的指令,在实际测试中也成功了。

接口分析

以下是用到的几个接口,其中使用getDetail可以获取用户上一次提交的信息(温度、时间戳、uutokendeviceid、现居地址),利用这个接口可以灵活的更新打卡的信息,假设有同学出校几天,那么当到达目的地后手动在手机端手动提交一次,然后自动打卡端就可以抓取上一次提交的信息,可以说很方便了。

# 获取公钥
https://compus.xiaofubao.com/login/getPublicKey

# 登录接口
https://compus.xiaofubao.com/login/doLoginByPwd

# 获取打卡信息
https://h5.xiaofubao.com/marketing/health/getDetail

# 打卡
https://h5.xiaofubao.com/marketing/health/doDetail

更新

# 2022-04

官方更新了打卡提交的接口,此次更新针对网上的纯uutoken提交的方式,经过我的测试发现该次更新修改了提交打卡信息的验证方式,在提交的UserAgent中加入了deviceid进行校验设备。

代码片段

我已经写好了大部分代码,可以直接拿去用(如果你会.NET的话),并且为了方便使用我已经写好了钉钉机器人通知的类,其余根据需求封装使用即可。其它平台语言需自行翻译Goodjob!

注意事项

以上仅仅只是处于对学术的研究与热爱,从自身出发去发现探索,并无其它目的。若采用的本项目代码则视为已同意所有后果自负原则,请勿用于违法违规行为,重申 后果自负!

打卡类

public class DoCard
{
    DingMessage msg = new DingMessage();
    public void DoCards(SpareTimer timer, long count)
    {
        var db = Db.GetRepository<CardUsers>();
        var users = db.DetachedEntities;
        int allpeo = users.Count();

        if (db.Any())
        {
            var api = new CardsApi();
            int fail = 0;
            foreach (var user in users) {
                try {
                    var pk = api.GetPublicKey();
                    var en = new JsEncryptHelper("", pk);
                    var passwrd = en.Encrypt(user.Passwrd);

                    var response = api.Login(user.Account, passwrd);
                    if (!response.success.Equals("true"))
                    {
                        fail += 1;
                        msg.SendFailMsg(user, response.message);
                        continue;
                    }
                    await Task.Delay(RandomDelay());
                    UserDetail usr = api.GetDetail(response.data.id);
                    if (!usr.success.Equals("true"))
                    {
                        fail += 1;
                        msg.SendFailMsg(user, usr.message);
                        continue;
                    }
                    await Task.Delay(RandomDelay());
                    UserDetails u = usr.data;
                    u.address = user.Address;
                    //u.token = response.data.token;
                    //需重新提交的数据
                    u.uuToken = response.data.uuToken;
                    u.loginUserId = u.userId;
                    u.loginUserName = u.name;
                    u.loginSchoolCode = u.schoolCode;
                    u.loginSchoolName = u.schoolName;
                    u.deviceId = response.data.deviceId;
                    u.updateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                    u.platform = "YUNMA_APP";
                    u.temperature = GetRandomNumber(35.5, 37.0, 1).ToString();
                    u.id = "2203413" + GetTimeStamp().ToString();

                    //提交打卡
                    var info = api.DoDetail(u);
                    if (string.IsNullOrEmpty(info.success) || !info.success.Equals("true"))
                    {
                        fail += 1;
                        msg.SendFailMsg(user, info.message);
                        continue;
                    }
                    await Task.Delay(RandomDelay());

                }
                catch (Exception ex)
                {
                    fail++;
                    msg.SendFailMsg(user,"出现异常终止!"+ex.Message);
                }

            }

            string str = "### **易校园打卡汇总通知** \n" +
                "#### **总人数 :** " + allpeo + "人 \n" +
                "#### **成  功  :** " + (allpeo - fail) + " 人 \n" +
                "#### **失  败  :** " + fail + " 人 \n" +
                "#### **日  志  :** 暂无日志!\n" +
                "#### **状  态  :** " + (fail == 0 ? "<font color=\"#00FF00\">打卡成功!</font>" : "<font color=\"#FFA500\">打卡异常!</font>") + " \n" +
                "#### **时  间  :** " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " \n";
            msg.SendBaseMarkdown("" + (fail == 0 ? "打卡成功(All)!" : "打卡异常!") + "", str, true, PushType.YXY);

        }
        else {
            msg.SendFailMsg(new CardUsers() {Name="系统" },"用户列表为空!警告!!!");
        }
    }

    public static double GetRandomNumber(double minimum, double maximum, int Len)
    {
        Random random = new Random();
        return Math.Round(random.NextDouble() * (maximum - minimum) + minimum, Len);
    }

    public static int RandomDelay() {
        Random rd = new Random();
        int i = rd.Next(1, 15);
        return i * 1000;
    }

    public static string GetTimeStamp()
    {
        TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
        return Convert.ToInt64(ts.TotalSeconds).ToString();
    }
}

接口类

public class CardsApi
{
    public string GetPublicKey()
    {
        string url = "https://compus.xiaofubao.com/login/getPublicKey";
        try
        {
            var client = new RestClient(url);
            var request = new RestRequest(Method.POST);

            request.Timeout = 10000;
            request.AddHeader("Cache-Accept", "application/json");
            request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
            request.AddHeader("Host", "compus.xiaofubao.com");
            request.AddHeader("Connection", "Keep-Alive");
            request.AddHeader("Accept-Encoding", "gzip");

            //参数
            request.AddParameter("schoolCode", "10681");
            request.AddParameter("deviceId", "");
            request.AddParameter("testAccount", "1");
            request.AddParameter("appVersion", "170");
            request.AddParameter("platform", "YUNMA_APP");

            IRestResponse response = client.Execute(request);
            dynamic descJsonStu = JsonConvert.DeserializeObject<dynamic>(response.Content);
            return descJsonStu["data"]["publicKey"];
        }
        catch (Exception ex)
        {
            return "连接服务器出错:\r\n" + ex.Message;
        }

    }


    public LoginResp Login(string Account, string Passwd)
    {
        string url = "https://compus.xiaofubao.com/login/doLoginByPwd";
        try
        {
            var client = new RestClient(url);
            var request = new RestRequest(Method.POST);
            request.Timeout = 10000;
            request.AddHeader("Accept", "application/json");
            request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
            request.AddHeader("Host", "compus.xiaofubao.com");
            request.AddHeader("Connection", "Keep-Alive");
            request.AddHeader("Accept-Encoding", "gzip");
            //参数
            request.AddParameter("mobilePhone", Account);
            request.AddParameter("password", Passwd);
            request.AddParameter("clientId", "");
            request.AddParameter("osType", "iOS");
            request.AddParameter("osVersion", "15.4.1");
            request.AddParameter("mobileType", "iPhone13");
            //手机标识 为空则跳过短信验证
            //机制:当手机标识为空时登录账号则服务器认为该设备在原手机登录不会触发新设备登录的验证流程
            request.AddParameter("oaid", "");
            request.AddParameter("appAllVersion", "2.5.7");
            request.AddParameter("appWgtVersion", "2.5.7");
            request.AddParameter("schoolCode", "10681");
            //与上述oaid作用一致留空即可
            request.AddParameter("deviceId", "");
            request.AddParameter("testAccount", "1");
            request.AddParameter("appVersion", "170");
            request.AddParameter("platform", "YUNMA_APP");
            IRestResponse response = client.Execute(request);

            var dt = JsonConvert.DeserializeObject<LoginResp>(response.Content.ToString());

            return dt;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return null;
        }
    }

    //从接口请求用户复用数据
    //作用:从服务端获取上一次设备提交的打开信息进行复用(UserDetail中的字段)特殊字段如温度需随机生成,可在必要提交新的信息时在手机端手动提交 通过此接口来获取已更新的信息进行提交。
    public UserDetail GetDetail(string userId)
    {
        string url = "https://h5.xiaofubao.com/marketing/health/getDetail?userId=" + userId;
        try
        {
            var client = new RestClient(url);
            var request = new RestRequest(Method.GET);
            request.Timeout = 10000;

            IRestResponse response = client.Execute(request);

            var dt = JsonConvert.DeserializeObject<UserDetail>(response.Content.ToString());

            return dt;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return null;
        }
    }

    //提交打卡
    public UserDetail DoDetail(UserDetails userd)
    {
        string url = "https://h5.xiaofubao.com/marketing/health/doDetail";
        try
        {
            var client = new RestClient(url);
            var request = new RestRequest(Method.POST);
            //UserAgent校验2022年四月份加入 
            client.UserAgent= "Mozilla / 5.0(iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit / 605.1.15(KHTML, like Gecko) Mobile / 15E148 Html5Plus / 1.0(Immersed / 44)(Immersed / 44) WKWebview ZJYXYwebviewbroswer ZJYXYIphone tourCustomer / yunmaapp.NET / 2.5.7 / "+ userd.deviceId;     
            request.Timeout = 10000;

            var user = JsonConvert.SerializeObject(userd);

            request.AddJsonBody(user);

            IRestResponse response = client.Execute(request);

            return JsonConvert.DeserializeObject<UserDetail>(response.Content.ToString());
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return null;
        }
    }
}

Entity

namespace xxx.Entity
{
    [Table("card_user")]
    [Comment("打卡用户")]
    public class CardUsers : DEntityBase
    {
        [Comment("姓名")]
        [Required, MaxLength(20)]
        public string Name { get; set; }

        [Comment("账号")]
        [Required,MaxLength(25)]
        public string Account { get; set; }

        [Comment("密码")]
        [Required, MaxLength(30)]
        public string Passwrd { get; set; }

        [Comment("现居地址")]
        [Required, MaxLength(100)]
        public string Address { get; set; }

        [Comment("邮箱")]
        [Required, MaxLength(100)]
        public string Email { get; set; }
    }
  
    public class Data
    {
        /// <summary>
        /// 
        /// </summary>
        public string id { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string schoolCode { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string schoolName { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int qrcodePayType { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string account { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string accountEncrypt { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string userName { get; set; }
        /// <summary>
        ///
        /// </summary>
        public string userType { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string mobilePhone { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string jobNo { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string userIdcard { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string identityNo { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int sex { get; set; }
        /// <summary>

        /// </summary>
        public string userClass { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int realNameStatus { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string regiserTime { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string nickName { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int bindCardStatus { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string bindCardTime { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string lastLogin { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string headImg { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string deviceId { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int testAccount { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string token { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int joinNewactivityStatus { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int createStatus { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int eacctStatus { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int schoolClasses { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int schoolNature { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string platform { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string uuToken { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string qrcodePrivateKey { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int bindCardRate { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int points { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int schoolIdentityType { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int alumniFlag { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string extJson { get; set; }
    }

    public class LoginResp
    {
        /// <summary>
        /// 
        /// </summary>
        public int statusCode { get; set; }
        /// <summary>
        /// 操作成功
        /// </summary>
        public string message { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public Data data { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string success { get; set; }
    }
  
    public class UserDetails
    {
        public string id { get; set; }

        public string schoolCode { get; set; }

        public string schoolName { get; set; }

        public int identityType { get; set; }

        public string userId { get; set; }

        public string mobilePhone { get; set; }

        public string name { get; set; }

        public string jobNo { get; set; }

        public string departmentCode { get; set; }

        public string department { get; set; }

        public string specialitiesCode { get; set; }

        public string specialities { get; set; }

        public string classCode { get; set; }

        public string className { get; set; }

        public int provinceCode { get; set; }

        public string province { get; set; }

        public int cityCode { get; set; }

        public string city { get; set; }

        public int inSchool { get; set; }

        public int contactArea { get; set; }

        public int isPatient { get; set; }

        public int contactPatient { get; set; }

        public string linkPhone { get; set; }

        public string parentsPhone { get; set; }

        public string createTime { get; set; }

        public string createDate { get; set; }

        public string updateTime { get; set; }

        public string remark { get; set; }

        public string locationInfo { get; set; }

        public string longitudeAndLatitude { get; set; }

        public int isSuspected { get; set; }

        public string healthStatusNew { get; set; }

        public string identitySecondType { get; set; }

        public int districtCode { get; set; }

        public string district { get; set; }

        public int isFamiliyPatient { get; set; }

        public int isCommunityPatient { get; set; }

        public int isTodayBack { get; set; }
    
        public string patientHospital { get; set; }

        public string isolatedPlace { get; set; }

        public string temperature { get; set; }

        public string temperatureAfter { get; set; }

        public string country { get; set; }

        public string address { get; set; }

        public string token { get; set; }

        public string uuToken { get; set; }

        public string loginUserId { get; set; }

        public string loginUserName { get; set; }

        public string loginSchoolCode { get; set; }

        public string loginSchoolName { get; set; }

        public string platform { get; set; }

        //疫苗接种情况
        public string vaccineStatus { get; set; }

        public string vaccineOneTime { get; set; }

        public string vaccineTwoTime { get; set; }

        public string vaccineThreeTime { get; set; }

        public string deviceId {get; set;}
    }

    public class UserDetail
    {
        public int statusCode { get; set; }

        public string message { get; set; }

        public UserDetails data { get; set; }

        public string success { get; set; }
    }
}

登录加密

public internal class JsEncryptHelper
{
    private readonly RSACryptoServiceProvider _privateKeyRsaProvider;
    private readonly RSACryptoServiceProvider _publicKeyRsaProvider;

    public JsEncryptHelper(string privateKey, string publicKey = null)
    {
        if (!string.IsNullOrEmpty(privateKey))
        {
            _privateKeyRsaProvider = CreateRsaProviderFromPrivateKey(privateKey);
        }

        if (!string.IsNullOrEmpty(publicKey))
        {
            _publicKeyRsaProvider = CreateRsaProviderFromPublicKey(publicKey);
        }
    }

    private static string Md5Hex(string data)
    {

        using (var md5 = MD5.Create())
        {
            byte[] dataHash = md5.ComputeHash(Encoding.UTF8.GetBytes(data));
            StringBuilder sb = new StringBuilder();
            foreach (byte b in dataHash)
            {
                sb.Append(b.ToString("x2").ToLower());
            }
            return sb.ToString();
        }
    }

    public string Decrypt(string cipherText)
    {
        if (_privateKeyRsaProvider == null)
        {
            throw new Exception("_privateKeyRsaProvider is null");
        }

        return Encoding.UTF8.GetString(_privateKeyRsaProvider.Decrypt(Convert.FromBase64String(cipherText), false));
    }

    //加密函数
    public string Encrypt(string text)
    {
        if (_publicKeyRsaProvider == null)
        {
            throw new Exception("_publicKeyRsaProvider is null");
        }
        text = Md5Hex(text);

        return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(Encoding.UTF8.GetBytes(text), false));
    }

    private RSACryptoServiceProvider CreateRsaProviderFromPrivateKey(string privateKey)
    {
        byte[] privateKeyBits = Convert.FromBase64String(privateKey);

        RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
        RSAParameters RSAparams = new RSAParameters();

        using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits)))
        {
            byte bt = 0;
            ushort twobytes = 0;
            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130)
            {
                binr.ReadByte();
            }
            else if (twobytes == 0x8230)
            {
                binr.ReadInt16();
            }
            else
            {
                throw new Exception("Unexpected value read binr.ReadUInt16()");
            }

            twobytes = binr.ReadUInt16();
            if (twobytes != 0x0102)
            {
                throw new Exception("Unexpected version");
            }

            bt = binr.ReadByte();
            if (bt != 0x00)
            {
                throw new Exception("Unexpected value read binr.ReadByte()");
            }

            RSAparams.Modulus = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.Exponent = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.D = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.P = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.Q = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.DP = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.DQ = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.InverseQ = binr.ReadBytes(GetIntegerSize(binr));
        }

        RSA.ImportParameters(RSAparams);
        return RSA;
    }

    private int GetIntegerSize(BinaryReader binr)
    {
        byte bt = 0;
        byte lowbyte = 0x00;
        byte highbyte = 0x00;
        int count = 0;
        bt = binr.ReadByte();
        if (bt != 0x02)
        {
            return 0;
        }

        bt = binr.ReadByte();

        if (bt == 0x81)
        {
            count = binr.ReadByte();
        }
        else if (bt == 0x82)
        {
            highbyte = binr.ReadByte();
            lowbyte = binr.ReadByte();
            byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
            count = BitConverter.ToInt32(modint, 0);
        }
        else
        {
            count = bt;
        }

        while (binr.ReadByte() == 0x00)
        {
            count -= 1;
        }

        binr.BaseStream.Seek(-1, SeekOrigin.Current);
        return count;
    }

    private RSACryptoServiceProvider CreateRsaProviderFromPublicKey(string publicKeyString)
    {
        // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
        byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
        byte[] x509key;
        byte[] seq = new byte[15];
        int x509size;

        x509key = Convert.FromBase64String(publicKeyString);
        x509size = x509key.Length;

        // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
        using (MemoryStream mem = new MemoryStream(x509key))
        {
            using (BinaryReader binr = new BinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading
            {
                byte bt = 0;
                ushort twobytes = 0;

                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                {
                    binr.ReadByte(); //advance 1 byte
                }
                else if (twobytes == 0x8230)
                {
                    binr.ReadInt16(); //advance 2 bytes
                }
                else
                {
                    return null;
                }

                seq = binr.ReadBytes(15); //read the Sequence OID
                if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
                {
                    return null;
                }

                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
                {
                    binr.ReadByte(); //advance 1 byte
                }
                else if (twobytes == 0x8203)
                {
                    binr.ReadInt16(); //advance 2 bytes
                }
                else
                {
                    return null;
                }

                bt = binr.ReadByte();
                if (bt != 0x00) //expect null byte next
                {
                    return null;
                }

                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                {
                    binr.ReadByte(); //advance 1 byte
                }
                else if (twobytes == 0x8230)
                {
                    binr.ReadInt16(); //advance 2 bytes
                }
                else
                {
                    return null;
                }

                twobytes = binr.ReadUInt16();
                byte lowbyte = 0x00;
                byte highbyte = 0x00;

                if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
                {
                    lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
                }
                else if (twobytes == 0x8202)
                {
                    highbyte = binr.ReadByte(); //advance 2 bytes
                    lowbyte = binr.ReadByte();
                }
                else
                {
                    return null;
                }

                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order
                int modsize = BitConverter.ToInt32(modint, 0);

                int firstbyte = binr.PeekChar();
                if (firstbyte == 0x00)
                {
                    //if first byte (highest order) of modulus is zero, don't include it
                    binr.ReadByte(); //skip this null byte
                    modsize -= 1; //reduce modulus buffer size by 1
                }

                byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes

                if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data
                {
                    return null;
                }

                int expbytes = binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)
                byte[] exponent = binr.ReadBytes(expbytes);

                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
                RSAParameters RSAKeyInfo = new RSAParameters();
                RSAKeyInfo.Modulus = modulus;
                RSAKeyInfo.Exponent = exponent;
                RSA.ImportParameters(RSAKeyInfo);

                return RSA;
            }
        }
    }

    private bool CompareBytearrays(byte[] a, byte[] b)
    {
        if (a.Length != b.Length)
        {
            return false;
        }

        int i = 0;
        foreach (byte c in a)
        {
            if (c != b[i])
            {
                return false;
            }

            i++;
        }

        return true;
    }

}

@以上分析解密内容仅供学习交流,不得将上述内容用于商业或者非法用途。

最后修改:2024 年 08 月 06 日
如果觉得我的文章对你有用,请随意赞赏