【学习】摘要认证从零开始
本文最后更新于 137 天前,其中的信息可能已经有所发展或是发生改变。

故事是这样的:

前些日子接了一个对接上传接口的活,里面的接口有个叫”注册接口“、”保活接口“……,里面并没有说明密码如何传输。于是我截图问对方,如何写?对方直接甩了一句”百度下摘要认证,国际通用的认证方式“。于是我查资料,恶补。

相关资料:
HTTP认证之摘要认证——Digest(一) – xiaoxiaotank – 博客园 (cnblogs.com)
HTTP的几种认证方式之DIGEST 认证(摘要认证) – wenbin_ouyang – 博客园 (cnblogs.com)

总结就是:

《注册》:
第一步:向指定接口发送一条空请求信息,然后获取返回的401信息中header中的内容。
【客户端】-请求->【服务端】
【客户端】<-返回401、header值-【服务端】
第二步:将header中获取到的内容和密码进行MD5加密组合,再次发送一条带加密header的请求,然后得到返回结果。
【客户端】-加密header值+密码,传入header请求->【服务端】
【客户端】<-成功信息-【服务端】
《传输》:
第三步:以返回成功的header中的几个值为准,对后续传输时用这几个值进行加密。

返回的header中几个值的意义:

  • WWW-Authentication:用来定义使用何种方式(Basic、Digest、Bearer等)去进行认证以获取受保护的资源
  • realm:表示Web服务器中受保护文档的安全域(比如公司财务信息域和公司员工信息域),用来指示需要哪个域的用户名和密码
  • qop:保护质量,包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略,(可以为空,但是)不推荐为空值
  • nonce:服务端向客户端发送质询时附带的一个随机数,这个数会经常发生变化。客户端计算密码摘要时将其附加上去,使得多次生成同一用户的密码摘要各不相同,用来防止重放攻击
  • nc:nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量。例如,在响应的第一个请求中,客户端将发送“nc=00000001”。这个指示值的目的是让服务器保持这个计数器的一个副本,以便检测重复的请求
  • cnonce:客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
  • response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令
  • Authorization-Info:用于返回一些与授权会话相关的附加信息
  • nextnonce:下一个服务端随机数,使客户端可以预先发送正确的摘要
  • rspauth:响应摘要,用于客户端对服务端进行认证
  • stale:当密码摘要使用的随机数过期时,服务器可以返回一个附带有新随机数的401响应,并指定stale=true,表示服务器在告知客户端用新的随机数来重试,而不再要求用户重新输入用户名和密码了

上代码(C#为例):

首先创建一个用于解析和创建header的类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Security.Principal;

namespace InformationUpload.Digest
{
    internal class DigestUtils
    {
        public static string GenerateToken()
        {
            string s = $"{DateTime.Now.Ticks}{new Random().Next()}";

            using (MD5 md5 = MD5.Create())
            {
                byte[] inputBytes = Encoding.ASCII.GetBytes(s);
                byte[] hashBytes = md5.ComputeHash(inputBytes);
                return Convert.ToBase64String(hashBytes);
            }
        }

        public static string CalculateMD5(string input)
        {
            using (MD5 md5 = MD5.Create())
            {
                byte[] inputBytes = Encoding.ASCII.GetBytes(input);
                byte[] hashBytes = md5.ComputeHash(inputBytes);

                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < hashBytes.Length; i++)
                {
                    sb.Append(hashBytes[i].ToString("x2"));
                }

                return sb.ToString();
            }
        }

        public static DigestAuthInfo GetAuthInfoObject(string authStr)
        {
            if (string.IsNullOrEmpty(authStr) || authStr.Length <= 7)
                return null;

            if (authStr.ToLower().IndexOf("digest") >= 0)
            {
                // 截掉前缀 Digest
                authStr = authStr.Substring(6);
            }

            // 将双引号去掉
            authStr = authStr.Replace("\"", "");

            DigestAuthInfo digestAuthObject = new DigestAuthInfo();
            string[] authArray = authStr.Split(',');
            foreach (string auth in authArray)
            {
                string[] parts = auth.Trim().Split('=');
                if (parts.Length == 2)
                {
                    string key = parts[0].Trim();
                    string value = parts[1].Trim();
                    switch (key)
                    {
                        case "username":
                            digestAuthObject.Username = value;
                            break;
                        case "realm":
                            digestAuthObject.Realm = value;
                            break;
                        case "nonce":
                            digestAuthObject.Nonce = value;
                            break;
                        case "uri":
                            digestAuthObject.Uri = value;
                            break;
                        case "response":
                            digestAuthObject.Response = value;
                            break;
                        case "qop":
                            digestAuthObject.Qop = value;
                            break;
                        case "nc":
                            digestAuthObject.Nc = value;
                            break;
                        case "cnonce":
                            digestAuthObject.Cnonce = value;
                            break;
                        case "opaque":
                            digestAuthObject.Opaque = value;
                            break;
                        case "User-Identify":
                            digestAuthObject.UI = value;
                            break;
                    }
                }
            }
            return digestAuthObject;
        }
    }
    public class DigestAuthInfo
    {
        public string Username { get; set; }
        public string Realm { get; set; }
        public string Nonce { get; set; }
        public string Uri { get; set; }
        public string Response { get; set; }
        public string Qop { get; set; }
        public string Nc { get; set; }
        public string Cnonce { get; set; }
        public string Opaque { get; set; }
        public string UI { get; set; }

        public string authorizationString()
        {
            return $"Digest username=\"{Username}\", realm=\"{Realm}\", nonce=\"{Nonce}\", uri=\"{Uri}\", response=\"{Response}\", opaque=\"{Opaque}\", qop=\"{Qop}\", nc=\"{Nc}\", cnonce=\"{Cnonce}\"";

        }
    }
}

主要加密方式:MD5(MD5(<username>:<realm>:<password>):<nonce>:<nc>:<cnonce>:<qop>:MD5(<request-method>:<uri>))

然后对http请求:

using InformationUpload.Digest;
using InformationUpload.JsonModel;
using InformationUpload.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mime;
using System.Security.Policy;
using System.Text;
using System.Windows.Forms.VisualStyles;
using System.Windows.Forms;

namespace InformationUpload.Controller
{
    internal class DigestController
    {
        static string iniPath = Application.StartupPath.ToString() + @"\Config.ini";
        static IniFiles ini = new IniFiles(iniPath);
        // 接口http
        private static string HttpUrl = ini.IniReadValue("合作商配置", "Url");
        // User-Identify 域和鉴权信息
        private static string UI = ini.IniReadValue("合作商配置", "UI"); 
        private static string Username = ini.IniReadValue("合作商配置", "Username"); 
        private static string Password = ini.IniReadValue("合作商配置", "Password"); 
        private static string OtherRealm = "acsncgVIIDExt"; 
        // 定义要发送的JSON数据
        private static string GetRegisterObject()
        {
            RegisterRequestData registerRequestData = new RegisterRequestData
            {
                RegisterObject = new RegisterObject
                {
                    DeviceID = UI
                }
            };
            return JsonConvert.SerializeObject(registerRequestData);
        }
        private static string GetKeepaliveObject()
        {
            KeepaliveRequestData keepaliveRequestData = new KeepaliveRequestData
            {
                KeepaliveObject = new KeepaliveObject
                {
                    DeviceID = UI
                }
            };
            return JsonConvert.SerializeObject(keepaliveRequestData);
        }
        // nc初始值
        private static int nc = 0;

        /// <summary>
        /// 注册消息
        /// </summary>
        /// <returns></returns>
        public static string[] SystemRegister()
        {
            string[] returnArray = new string[3];
            Form1.form.outPut("=第一次请求:");
            string uri = "/VIID/System/Register";
            try
            {
                string[] firstRequest = SendRequest($"{HttpUrl}{uri}", GetRegisterObject());
                if (firstRequest[0] == "Unauthorized")
                {
                    Form1.form.outPut("=第一次请求返回的 WWW-Authenticate: " + firstRequest[1]);
                    nc = 0;
                    string authorization = PostAuthorization("SystemRegister", uri, firstRequest[1]);
                    Form1.form.outPut("=第二次请求:");
                    string[] secondRequest = SendRequest($"{HttpUrl}{uri}", GetRegisterObject(), authorization);
                    if (secondRequest[0] == "OK")
                    {
                        Form1.form.outPut("=第二次请求返回成功");
                        RegisterResponseData registerResponseData = JsonConvert.DeserializeObject<RegisterResponseData>(secondRequest[2]);
                        if (registerResponseData.ResponseStatusObject.StatusString == "success")
                        {
                            returnArray[0] = "success";
                            returnArray[1] = firstRequest[1];
                            returnArray[2] = nc.ToString();
                        } else
                        {
                            returnArray[0] = "error";
                            returnArray[1] = firstRequest[2];
                        }
                    } else
                    {
                        returnArray[0] = "error";
                        returnArray[1] = "对方服务器未返回结果2:" + secondRequest[0]; ;
                    }
                } else
                {
                    returnArray[0] = "error";
                    returnArray[1] = "对方服务器未返回结果1:" + firstRequest[0];
                }
            }
            catch (Exception ex)
            {
                Form1.form.outPut("=第一次请求失败:" + ex.Message);
                returnArray[0] = "error";
                returnArray[1] = ex.Message;
            }
            return returnArray;
        }

        /// <summary>
        /// 保活接口
        /// </summary>
        /// <param name="firstResponseWwwAuthenticate">注册时第一次返回的 WWW-Authenticate</param>
        /// <param name="nowNc">现在的 Nc 值</param>
        /// <returns></returns>
        public static string[] SystemKeepalive(string firstResponseWwwAuthenticate)
        {
            Form1.form.outPut("=SystemKeepalive:");
            string[] returnArray = new string[3];
            string uri = "/VIID/System/Keepalive";
            string authorization = PostAuthorization("SystemKeepalive", uri, firstResponseWwwAuthenticate, OtherRealm);
            string[] keepaliveRequest = SendRequest($"{HttpUrl}{uri}", GetKeepaliveObject(), authorization);
            if (keepaliveRequest[0] == "OK")
            {
                KeepaliveResponseData keepaliveResponseData = JsonConvert.DeserializeObject<KeepaliveResponseData>(keepaliveRequest[2]);
                if (keepaliveResponseData.ResponseStatusObject.StatusString == "success")
                {
                    Form1.form.outPut("=活动中");
                    returnArray[0] = "success";
                    returnArray[1] = keepaliveRequest[1];
                    returnArray[2] = nc.ToString();
                }
            }
            else
            {
                returnArray[0] = "error";
                returnArray[1] = "对方服务器未返回结果:" + keepaliveRequest[0];
            }
            return returnArray;
        }

        /// <summary>
        /// 数据传输接口
        /// </summary>
        /// <param name="firstResponseWwwAuthenticate">第一次传送的WwwAuthenticate</param>
        /// <param name="transferData">发送的 json 数据</param>
        /// <param name="nowNc">现在的 nc</param>
        /// <returns>[0]:返回的状态码;[1]:当前的WwwAuthenticate;[2]:当前的nc;[3]:正常返回的结果</returns>
        public static DataTransferResultObject DataTransfer(string firstResponseWwwAuthenticate, string transferData, string nowNc)
        {
            DataTransferResultObject resultObject = new DataTransferResultObject();
            string uri = "/VIID/DataTransfer";
            nc = Convert.ToInt32(nowNc);
            string[] keepaliveMessage = DigestController.SystemKeepalive(firstResponseWwwAuthenticate);
            if (keepaliveMessage[0] != "success")
            {
                Form1.form.outPut("保活不成功");
                string[] registerMessage = DigestController.SystemRegister();
                if (registerMessage[0] == "success")
                {
                    Form1.form.outPut("重新注册成功");
                    firstResponseWwwAuthenticate = registerMessage[1];
                    nowNc = registerMessage[2];
                    nc = Convert.ToInt32(nowNc);
                }
                else {
                    resultObject.Status = "error";
                    resultObject.FRWA = firstResponseWwwAuthenticate;
                    resultObject.NowNC = nc.ToString();
                    return resultObject;
                }
            } else Form1.form.outPut("保活成功");
            string dataTransferAuthorization = PostAuthorization("DataTransfer", uri, firstResponseWwwAuthenticate, OtherRealm);

            Form1.form.outPut("发送数据中...");
            string[] dataTransferResult = SendRequest($"{HttpUrl}{uri}", transferData, dataTransferAuthorization, true);
            if (dataTransferResult[0] == "OK")
            {
                Form1.form.outPut("发送成功", 0);
                resultObject.Data = dataTransferResult[2];
            }
            Form1.form.outPut("发送结束");
            resultObject.Status = dataTransferResult[0];
            resultObject.FRWA = firstResponseWwwAuthenticate;
            resultObject.NowNC = nc.ToString();
            return resultObject;
        }

        /// <summary>
        /// 发送请求
        /// </summary>
        /// <param name="url">请求地址</param>
        /// <param name="data">请求数据</param>
        /// <param name="authorization">请求头 Authorization,第一次默认为空</param>
        /// <returns>[0]:返回的状态码;[1]:返回的Www-Authenticate;[2]:返回结果</returns>
        private static string[] SendRequest(string url, string data, string authorization = null, bool bUI = false)
        {
            // DataTransferResultObject resultObject = new DataTransferResultObject();
            string[] returnArray = new string[3];
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/json";
            request.Accept = "*/*";
            // 添加头Authorization信息
            if (!string.IsNullOrEmpty(authorization))
            {
                request.Headers.Add("Authorization", authorization);
            }
            if (bUI)
            {
                request.Headers.Add("User-Identify", UI);
            }
            Form1.form.outPut("NC:" + nc);
            Form1.form.outPut("URL:" + url, 0);
            Form1.form.outPut("Data:" + data, 0);
            Form1.form.outPut("Authorization:" + authorization, 0);
            // 添加请求内容
            using (var streamWriter = new StreamWriter(request.GetRequestStream()))
            {
                streamWriter.Write(data);
                streamWriter.Flush();
            }
            try
            {
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
                {
                    returnArray[0] = response.StatusCode.ToString();
                    returnArray[1] = response.Headers["WWW-Authenticate"];
                    returnArray[2] = reader.ReadToEnd();
                    // Form1.form.outPut("请求成功返回的WWW-Authenticate内容: " + returnArray[1]);
                }
                return returnArray;
            }
            catch (WebException ex)
            {
                Form1.form.outPut("请求失败:" + ex.Message);
                if (ex.Response is HttpWebResponse errorResponse)
                {
                    if (errorResponse.StatusCode == HttpStatusCode.Unauthorized)
                    {
                        using (StreamReader errorReader = new StreamReader(errorResponse.GetResponseStream(), Encoding.UTF8))
                        {
                            returnArray[0] = errorResponse.StatusCode.ToString();
                            returnArray[1] = errorResponse.Headers["WWW-Authenticate"];
                            returnArray[2] = errorReader.ReadToEnd();
                        }
                        return returnArray;
                    }
                }
                throw ex;
            }
        }

        /// <summary>
        /// 请求头 Authorization 生成
        /// </summary>
        /// <param name="postType">post 类型 </param>
        /// <param name="uri">uri 地址</param>
        /// <param name="wwwAuthenticate">WWW-Authenticate 内容</param>
        /// <param name="realm">需要组合的 realm 值 第一次默认为空, 即 viid</param>
        /// <returns></returns>
        private static string PostAuthorization(string postType, string uri, string wwwAuthenticate, string realm = null)
        {
            DigestAuthInfo authObject = DigestUtils.GetAuthInfoObject(wwwAuthenticate);
            // nc 递增
            nc++;
            authObject.Username = Username;
            authObject.Nc = nc.ToString("X8"); // 转换为十六进制字符串
            authObject.Cnonce = DigestUtils.GenerateToken(); // 生成随机值
            authObject.Uri = uri;
            authObject.UI = UI;

            if (!string.IsNullOrEmpty(realm))
            {
                authObject.Realm = realm;
            }

            // MD5(MD5(<username>:<realm>:<password>):<nonce>:<nc>:<cnonce>:<qop>:MD5(<request-method>:<uri>))
            string HA1 = DigestUtils.CalculateMD5($"{authObject.Username}:{authObject.Realm}:{Password}");
            string HD = $"{authObject.Nonce}:{authObject.Nc}:{authObject.Cnonce}:{authObject.Qop}";
            string HA2 = DigestUtils.CalculateMD5($"POST:{authObject.Uri}");
            authObject.Response = DigestUtils.CalculateMD5($"{HA1}:{HD}:{HA2}");
            // Form1.form.outPut("计算出的 response: " + authObject.Response);
            return authObject.authorizationString();
        }
    }
}

理解了就挺简单的。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
Source: https://github.com/MengXi2021/Argon-Emoji-DailyNotes
Source: https://github.com/Ghost-chu/argon-huhu-emotions
Source: github.com/zhheo/Sticker-Heo
颜文字
Emoji
小恐龙
花!
每日手帐
呼呼
Heo
上一篇
下一篇