HTTP的認證方式之DIGEST
- 1.是什么
- 2.認值流程
- 2.1 客戶端發送請求
- 2.2 服務器返回質詢信息
- 2.2.1 質詢參數
- 2.2.2 質詢舉例
- 2.3 客戶端生成響應
- 2.4 服務器驗證響應
- 2.5 服務器返回響應
- 3.算法
- 3.1 SHA-256
- 3.1.1 Response
- 3.1.2 A1
- 3.1.3 A2
- 3.2 MD5
- 3.2.1 Request-Digest
- 3.2.2 A1
- 3.2.3 A2
- 4.舉例
詳細的說明文檔:WWW-Authenticate - HTTP | MDN (mozilla.org)
1.是什么
摘要認證(Digest Authentication)是一種用于在網絡通信中驗證用戶身份的認證方法。它主要應用于HTTP和其他應用層協議中。
Digest認證相對于基本認證更加安全,因為它不直接傳輸明文密碼。但它也不是完全的安全解決方案,因為在中間人攻擊等情況下,仍然可能受到攻擊。在現代網絡中,更安全的認證方法通常是基于令牌(Token)的認證機制。
2.認值流程
類似與Basic認證流程:
Digest認證的整體流程如下:
-
客戶端發送請求: 客戶端向服務器發送請求,請求中包含需要訪問的資源路徑。
-
服務器返回挑戰信息: 服務器接收到客戶端請求后,返回一個“質詢”信息(Challenge)給客戶端。這個挑戰信息是一個包含隨機數、領域名(realm)以及其他一些參數的字符串。
-
客戶端生成響應: 客戶端使用用戶名、密碼和挑戰信息來生成一個響應字符串。這個響應字符串的生成過程包括以下幾個步驟:
- 拼接:將用戶名、領域名和密碼用冒號分隔,并將它們拼接成一個字符串。
- 對字符串進行哈希:對上述拼接后的字符串進行哈希運算,通常使用MD5或SHA-1等哈希算法。
-
客戶端發送響應: 客戶端將生成的響應字符串發送給服務器,放在請求的"Authorization"頭部中。
-
服務器驗證響應: 服務器收到客戶端的響應后,使用相同的方式在服務器端重現生成響應字符串。然后將客戶端發送的響應字符串和服務器端生成的響應字符串進行比較。如果兩者相等,說明客戶端擁有正確的用戶名和密碼。
-
服務器返回響應: 如果服務器驗證成功,它會返回請求的資源內容給客戶端,同時在響應的頭部中包含認證成功的標識。
2.1 客戶端發送請求
客戶端的第一次請求。
2.2 服務器返回質詢信息
2.2.1 質詢參數
-
qop:帶引號的字符串,表示服務器支持的保護程度。這必須提供,并且必須忽略無法識別的選項。
-
"auth"
:身份驗證"auth-int"
:有完整保護的身份驗證
-
nonce:一個服務器指定的帶引號的字符串,在每次的 401 響應期間,服務器可以使用它去驗證指定的憑據。這必須是在每次 401 響應時唯一的生成,并且可以更頻繁地重新生成(例如,允許一個摘要僅使用一次)。該規范包含有關生成此值算法的建議。nonce 值對客戶端是不透明的。
-
opaque:一個服務器指定的帶引號的字符串,應在
Authorization
中原封不動的返回。這對客戶端是不透明的。建議服務器包含 Base64 或十六進制數據。 -
<realm>
(可選)一個指示要使用的用戶名/密碼的字符串。至少應該包括主機名,但是可能指示具有訪問權限的用戶或組。 -
domain
(可選)一個帶引號,以空格分隔的 URI 前綴列表,定義了可以使用身份驗證信息的所有位置。如果未指定此關鍵字,則可以在 web 根目錄的任意位置使用身份驗證信息。 -
stale
(可選)一個不區分大小寫的標志,指示客戶端之前的請求因nonce
太舊了(過期)而被拒絕。如果為true
,則可以使用新的nonce
加密相同用戶名/密碼重試請求。如果它是任意其他的值,那么用戶名/密碼無效,并且必須向用戶重新請求。 -
algorithm
(可選)algorithm 被用于產生一個摘要。有效的非會話值是:"MD5"
(如果未指定,則是默認)、"SHA-256"
、"SHA-512"
。有效的會話值是:"MD5-sess"
、"SHA-256-sess"
、"SHA-512-sess"
。 -
charset="UTF-8"
(可選)當提交用戶名和密碼時,告訴客戶端服務器的首選編碼方案。僅允許的值是不區分大小寫的“UTF-8”字符串。 -
userhash
(可選)服務器可能指定為"true"
,以指示它支持用戶名哈希(默認是"false"
)。
2.2.2 質詢舉例
客戶端試圖訪問http://www.example.org/dir/index.html
處的文檔,該文檔受到 digest 身份驗證的保護。這個文檔的用戶名是“Mufsas”,并且它的密碼是“Circle of Life”。客戶端第一次請求該文檔時,不會發送 Authorization
標頭字段。在這里,服務器使用 HTTP 401 消息響應,其中包括對它支持的每個摘要算法的質詢,按照其優先順序(SHA256
,然后是 MD5
)。
服務器將質詢信息放在WWW-Authenticate響應頭發送給客戶端,如下例子:
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Digestrealm="http-auth@example.org",qop="auth, auth-int",algorithm=SHA-256,nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"WWW-Authenticate: Digestrealm="http-auth@example.org",qop="auth, auth-int",algorithm=MD5,nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
2.3 客戶端生成響應
客戶端接收到401響應,表示需要進行認證,客戶端提示用戶輸入他們的用戶名和密碼,然后響應一個新的請求,該請求在 Authorization
標頭字段中對憑據進行加密。如果客戶端選擇 MD5 摘要,則 Authorization
標頭字段看起來可能像如下這樣:
Authorization: Digest username="Mufasa",realm="http-auth@example.org",uri="/dir/index.html",algorithm=MD5,nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",nc=00000001,cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ",qop=auth,response="8ca523f5e9506fed4657c9700eebdbec",opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
如果客戶端選擇 SHA-256 摘要,則 Authorization
標頭看起來可能像以下這樣:
Authorization: Digest username="Mufasa",realm="http-auth@example.org",uri="/dir/index.html",algorithm=SHA-256,nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",nc=00000001,cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ",qop=auth,response="753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1",opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
2.4 服務器驗證響應
服務器收到客戶端的響應后,使用相同的方式在服務器端重現生成響應字符串。然后將客戶端發送的響應字符串和服務器端生成的響應字符串進行比較。如果兩者相等,說明客戶端擁有正確的用戶名和密碼。
2.5 服務器返回響應
如果服務器驗證成功,它會返回請求的資源內容給客戶端,同時在響應的頭部中包含認證成功的標識。
3.算法
根據請求體里的algorithm
的值:
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Digestrealm="http-auth@example.org",qop="auth, auth-int",algorithm=SHA-256,nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
一下是標準文檔里的說明,來自RFC 7616: HTTP Digest Access Authentication (rfc-editor.org):
A string indicating an algorithm used to produce the digest and an unkeyed digest. If this is not present, it is assumed to be “MD5”. If the algorithm is not understood, the challenge SHOULD be ignored (and a different one used, if there is more than one). When used with the Digest mechanism, each one of the algorithms has two variants: Session variant and non-Session variant. The non-Session variant is denoted by “”, e.g., “SHA-256”, and the Session variant is denoted by “-sess”, e.g., “SHA-256-sess”.
In this document, the string obtained by applying the digest algorithm to the data “data” with secret “secret” will be denoted by KD(secret, data), and the string obtained by applying theunkeyed digest algorithm to the data “data” will be denoted H(data). KD stands for Keyed Digest, and the notation unq(X) means the value of the quoted-string X without the surrounding quotes and with quoting slashes removed.
For "<algorithm>" and "<algorithm>-sess"H(data) = <algorithm>(data)
andKD(secret, data) = H(concat(secret, ":", data))For example:For the "SHA-256" and "SHA-256-sess" algorithmsH(data) = SHA-256(data)For the "MD5" and "MD5-sess" algorithms H(data) = MD5(data)
i.e., the digest is the “” of the secret concatenated with a colon concatenated with the data. The “-sess” is intended to allow efficient third-party authentication servers; for the difference in usage, see the description in Section 3.4.2.
簡單進行一下解釋:
- KD(secret,data)是將
secret
和data
用冒號:
拼接之后secret:data
進行算法加密 - H(data)是直接對數據進行算法加密
- unq(username)是不帶引號的字符串
3.1 SHA-256
我們可以參考RFC 7616: HTTP Digest Access Authentication (rfc-editor.org)
3.1.1 Response
If the qop value is “auth” or “auth-int”:
response = KD(H(A1),unq(nonce):nc:unq(cnonce):unq(qop):H(A2))
See below for the definitions for A1 and A2.
3.1.2 A1
If the algorithm parameter’s value is “”, e.g., “SHA-256”,then A1 is:
A1 = unq(username):unq(realm):passwd
where passwd = < user's password >
If the algorithm parameter’s value is “-sess”, e.g., “SHA-256-sess”, then A1 is calculated using the nonce value provided in the challenge from the server, and cnonce value from the request by the client following receipt of a WWW-Authenticate challenge from the server. It uses the server nonce from that challenge, herein called nonce-prime, and the client nonce value from the response, herein called cnonce-prime, to construct A1 as follows:
A1 = H(unq(username):unq(realm):passwd):unq(nonce-prime):unq(cnonce-prime)
This creates a “session key” for the authentication of subsequent requests and responses that is different for each “authentication session”, thus limiting the amount of material hashed with any one key. (Note: see further discussion of the authentication session in Section 3.6.) Because the server needs only use the hash of the user credentials in order to create the A1 value, this construction could be used in conjunction with a third-party authentication service so that the web server would not need the actual password value. The specification of such a protocol is beyond the scope of this specification.
3.1.3 A2
If the qop parameter’s value is “auth” or is unspecified, then A2 is:
A2 = Method:request-uri
If the qop value is “auth-int”, then A2 is:
A2 = Method:request-uri:H(entity-body)
3.2 MD5
我們可以參考RFC 2617 - HTTP Authentication: Basic and Digest Access Authentication (ietf.org)
3.2.1 Request-Digest
If the “qop” value is “auth” or “auth-int”:
request-digest = KD(H(A1), unq(nonce-value):nc-value:unq(cnonce-value):unq(qop-value):H(A2))
If the “qop” directive is not present (this construction is for compatibility with RFC 2069):
request-digest = KD(H(A1), unq(nonce-value):H(A2))
See below for the definitions for A1 and A2.
3.2.2 A1
If the “algorithm” directive’s value is “MD5” or is unspecified, then A1 is:
A1 = unq(username-value):unq(realm-value):passwd
where passwd = < user's password >
If the “algorithm” directive’s value is “MD5-sess”, then A1 is calculated only once - on the first request by the client following receipt of a WWW-Authenticate challenge from the server. It uses the server nonce from that challenge, and the first client nonce value to construct A1 as follows:
A1 = H(unq(username-value):unq(realm-value):passwd):unq(nonce-value):unq(cnonce-value)
This creates a ‘session key’ for the authentication of subsequent requests and responses which is different for each “authentication session”, thus limiting the amount of material hashed with any one key. (Note: see further discussion of the authentication session in section 3.3.) Because the server need only use the hash of the user credentials in order to create the A1 value, this construction could be used in conjunction with a third party authentication service so that the web server would not need the actual password value. The specification of such a protocol is beyond the scope of this specification.
3.2.3 A2
If the “qop” directive’s value is “auth” or is unspecified, then A2 is:
A2 = Method:digest-uri-value
If the “qop” value is “auth-int”, then A2 is:
A2 = Method:digest-uri-value:H(entity-body)
4.舉例
我們還拿上邊的例子進行一下算法處理,algorithm
沒有賦值就是默認MD5
,用戶名還是使用Mufasa
,密碼使用123456
:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digestrealm="http-auth@example.org",qop="auth",nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
響應:
Authorization: Digest username="Mufasa", realm="http-auth@example.org", uri="/dir/index.html", algorithm=MD5, nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", nc=00000001, cnonce="nvlfh1ra", qop=auth, response="7bddc3c7fceb317dc002c524187fa170", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
完整算法,這里我們要非常注意帶不帶雙引號:
// A1 = unq(username-value):unq(realm-value):passwd
String A1 = StrUtil.format("{}:{}:\"{}\"", "Mufasa", "http-auth@example.org", "123456");
// A2 = Method:digest-uri-value
String A2 = StrUtil.format("\"{}\":\"{}\"", "POST", "/dir/index.html");
// request-digest = KD(H(A1), unq(nonce-value):nc-value:unq(cnonce-value):unq(qop-value):H(A2))
String HA1 = SecureUtil.md5(A1);
String HA2 = SecureUtil.md5(A2);
String responseStr = StrUtil.format("\"{}\":{}:\"{}\":{}:{}:\"{}\"", HA1, "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v","00000001", "nvlfh1ra", "auth", HA2);
String response = SecureUtil.md5(responseStr);