HTTP Digest Auth

相比于 Basic,Digest 不需要在网络上传输用户的密码。

但是它依然不是一个身份验证的完美方案,它没有提供认证消息内容加密的机制。

这个协议的目的是创建一个简单的认证方法,避免 Basic 的严重缺陷。


基本原理


和 Basic 一样,基于 challenge-response 模型。

使用一个 nonce 作为 challenge,一个合法的 response 应该包含一个 checksum.

这个 checksum 是由 username, password, given nonce value, HTTP method, and Request URI.(Checksum 算法默认是 MD5).

注意,这里密码是不需要在网络上传输的,最后传输的部分仅仅是一个于密码相关的哈希值。


HTTP 头


和 Basic 类似。 使用 WWW-AuthenticateAuthorization, 和一个额外的头 Authentication-Info.


1. WWW-Authentication


当服务器收到一个请求访问受保护的对象的请求,但是该请求没有携带 Authorization 头, 服务器便会回复一个 401 Unauthorized 响应给客户端,并且附带 WWW-Authentication 头。 这个头的定义如下:

challenge        = "Digest" digest-challenge
digest-challenge = 1#( realm | [ domain ] | nonce |
                   [ opaque ] |[ stale ] | [ algorithm ] |
                   [ qop-options ] | [auth-param] )
domain           = "domain" "=" <"> URI ( 1*SP URI ) <">
URI              = absoluteURI | abs_path
nonce            = "nonce" "=" nonce-value
nonce-value      = quoted-string
opaque           = "opaque" "=" quoted-string
stale            = "stale" "=" ( "true" | "false" )
algorithm        = "algorithm" "=" ( "MD5" | "MD5-sess" | token )
qop-options      = "qop" "=" <"> 1#qop-value <">
qop-value        = "auth" | "auth-int" | token

各个字段的含义如下:

  • realm: Required

    一个展示给用户的字符串,通过这个字符串提醒用户应该用什么用户名和密码。

    这个字段串中至少应该包含要求授权的主机信息,和可能具有访问权限的用户集合信息。

    例如:registered_users@gotham.news.com

  • domain: Optional

    一个用双引号括起来,用逗号作为分隔符的 URI 列表。

    If a URI is an abs_path, it is relative to the canonical root URL (see section 1.2 above) of the server being accessed. An absoluteURI in this list may refer to a different server than the one being accessed. The client can use this list to determine the set of URIs for which the same authentication information may be sent: any URI that has a URI in this list as a prefix (after both have been made absolute) may be assumed to be in the same protection space. If this directive is omitted or its value is empty, the client should assume that the protection space consists of all URIs on the responding server.

  • nonce: Required

    一个服务器端指定的字符串, 对于每个 401 响应,这个字符串都应该是唯一的。

    这个字符串中实际存储的是什么内容,客户端不需要关心,这个数据对客户端时透明的。

  • opaque: Optional

    一个服务器端指定的字符串。 客户端需要将这个字段的内容附带在接下来的 Authorization header 中。 不能改变。

  • stale: Optional

    // Ignore

  • algorithm: Optional

    指定用来计算 checksum 的算法名称。 如果这个字段没有出现,默认是 “MD5”。

  • qop-options: Optional

    “auth”, “auth-init”, “auth,auth-int”.

  • auth-param:

    用于将来对协议进行扩充时使用。


2. Authorization


客户端收到服务器端发回来的 401 响应,解析 WWW-Authentication, 发现其中是一个 Digest 的 challenge,此时客户端需要计算 response,并发送 Authorization 头。

格式如下:

credentials     = "Digest" digest-response
digest-response = 1#( username | realm | nonce | digest-uri
                  | response | [ algorithm ] | [cnonce] |
                  [opaque] | [message-qop] |
                  [nonce-count] | [auth-param] )
username         = "username" "=" username-value
username-value   = quoted-string
digest-uri       = "uri" "=" digest-uri-value
digest-uri-value = request-uri ; As specified by HTTP/1.1
message-qop      = "qop" "=" qop-value
cnonce           = "cnonce" "=" cnonce-value
cnonce-value     = nonce-value
nonce-count      = "nc" "=" nc-value
nc-value         = 8LHEX
response         = "response" "=" request-digest
request-digest   = <"> 32LHEX <">
LHEX             = "0" | "1" | "2" | "3" |
                   "4" | "5" | "6" | "7" |
                   "8" | "9" | "a" | "b" |
                   "c" | "d" | "e" | "f"

各个字段含义如下:

  • username: Required

    指定域中的用户名。

  • realm: Required

    同 WWW-Authenticate 中所带 realm 值

  • nonce:Required

    同 WWW-Authenticate 中所带 nonce 值

  • digest-uri: Required

    同当前 HTTP 请求中 Request-Line 中 的 Request-URI.

  • response: Required

    一个由32个hex字符组成的字符串,用来证明当前用户知道密码。后面详解。

  • algorithm: Optional

    当前 response 使用哪种算法计算。

  • cnonce: Optional

    如果服务器没有发送 qop,那么客户端不要用 cnonce。

    如果客户端在 Authorization中带 qop,那么cnonce 也必须被带上。

    它的值由客户端提供,同时被客户端和服务器端使用来避免选择明文攻击(chosen plaintext attacks),提供相互认证(mutual authentication), 和提供一定程度的消息完整性保护(message integrity protection)。

  • opaque: Optional

    同 WWW-Authenticate 中所带 opaque 值

  • qop:Optional

    qop = Quality of protection.

    如果出现,那它的值必须是 WWW-Authenticate 中所带 qop 的值之一。

    这个字段被设计为 Optional 是为了保持兼容性。如果服务器在 WWW-Authenticate 中带了 qop 字段,那么 Authorization中也应该带上 qop。

  • nonce-count: Optional

    如果服务器没有发送 qop,那么客户端不要用 nonce-count。

    如果客户端在 Authorization中带 qop,那么nonce-count也必须被带上。

    nc 是一个数字,代表当前客户端使用当前请求中的 nonce 已经发送了多少次请求了(包含当前请求)。

    例如: 对于第一个请求,“nc=00000001”.

    这个字段的目的是服务器用来检测请求重播攻击(request replay). 服务器端可以自己维护一个 nc value,当检测到有两次请求的 nc value 相同时,便可以说明发生了重播攻击。

  • auth-param: Optional

    用于将来对协议进行扩充时使用。


3. Authentication-Info


被服务器端用户通知客户端关于认证成功的一些信息。

AuthenticationInfo = "Authentication-Info" ":" auth-info
auth-info          = 1#(nextnonce | [ message-qop ]
                     | [ response-auth ] | [ cnonce ]
                     | [nonce-count] )
nextnonce          = "nextnonce" "=" nonce-value
response-auth      = "rspauth" "=" response-digest
response-digest    = <"> *LHEX <">


如何计算 Digest


// qop == "auth" or qop == "auth-int"
request-digest = <"> < KD ( H(A1), 		unq(nonce-value)
									":" nc-value
									":" unq(cnonce-value)
									":" unq(qop-value)
									":" H(A2)
							) <">
							
// qop is not present
request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) ><">
// Algorithm=MD5
A1 = unq(username-value) ":" unq(realm-value) ":" passwd
passwd = < user’s password >

// if Algorithm=MD5-sess
A1 = H( unq(username-value) ":" unq(realm-value)
		":" passwd )
		":" unq(nonce-value) ":" unq(cnonce-value)
// if qop == "auth"
A2 = Method ":" digest-uri-value
// if qop == "auth-int"
A2 = Method ":" digest-uri-value ":" H(entity-body)

官方示例


HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest
					realm="testrealm@host.com",
					qop="auth,auth-int",
					nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
					opaque="5ccc069c403ebaf9f0171e9517f40e41"

Authorization: Digest username="Mufasa",
				realm="testrealm@host.com",
				nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
				uri="/dir/index.html",
				qop=auth,
				nc=00000001,
				cnonce="0a4f113b",
				response="6629fae49393a05397450978507c4ef1",
				opaque="5ccc069c403ebaf9f0171e9517f40e41"

官方示例代码


// File "digcalc.h"

#define HASHLEN 16
typedef char HASH[HASHLEN];
#define HASHHEXLEN 32
typedef char HASHHEX[HASHHEXLEN+1];
#define IN
#define OUT

/* calculate H(A1) as per HTTP Digest spec */
void DigestCalcHA1(
	IN char * pszAlg,
	IN char * pszUserName,
	IN char * pszRealm,
	IN char * pszPassword,
	IN char * pszNonce,
	IN char * pszCNonce,
	OUT HASHHEX SessionKey
);

/* calculate request-digest/response-digest as per HTTP Digest spec */
void DigestCalcResponse(
	IN HASHHEX HA1, /* H(A1) */
	IN char * pszNonce, /* nonce from server */
	IN char * pszNonceCount, /* 8 hex digits */
	IN char * pszCNonce, /* client nonce */
	IN char * pszQop, /* qop-value: "", "auth", "auth-int" */
	IN char * pszMethod, /* method from the request */
	IN char * pszDigestUri, /* requested URL */
	IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */
	OUT HASHHEX Response /* request-digest or response-digest */
);
// File "digcalc.c"

#include <global.h>
#include <md5.h>
#include <string.h>
#include "digcalc.h"

void CvtHex(
		IN HASH Bin,
		OUT HASHHEX Hex
){
	unsigned short i;
	unsigned char j;
	for (i = 0; i < HASHLEN; i++) {
		j = (Bin[i] >> 4) & 0xf;
		if (j <= 9)
			Hex[i*2] = (j + ’0’);
		else
			Hex[i*2] = (j + ’a’ - 10);
		j = Bin[i] & 0xf;
		if (j <= 9)
			Hex[i*2+1] = (j + ’0’);
		else
			Hex[i*2+1] = (j + ’a’ - 10);
	};
	Hex[HASHHEXLEN] = ’\0’;
};

/* calculate H(A1) as per spec */
void DigestCalcHA1(
	IN char * pszAlg,
	IN char * pszUserName,
	IN char * pszRealm,
	IN char * pszPassword,
	IN char * pszNonce,
	IN char * pszCNonce,
	OUT HASHHEX SessionKey
) {
	MD5_CTX Md5Ctx;
	HASH HA1;
   
	MD5Init(&Md5Ctx);
	MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName));
	MD5Update(&Md5Ctx, ":", 1);
	MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm));
	MD5Update(&Md5Ctx, ":", 1);
	MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword));
	MD5Final(HA1, &Md5Ctx);
	if (stricmp(pszAlg, "md5-sess") == 0) {
    	MD5Init(&Md5Ctx);
		MD5Update(&Md5Ctx, HA1, HASHLEN);
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
		MD5Final(HA1, &Md5Ctx);
	};
	CvtHex(HA1, SessionKey);
};

/* calculate request-digest/response-digest as per HTTP Digest spec */
void DigestCalcResponse(
	IN HASHHEX HA1, /* H(A1) */
	IN char * pszNonce, /* nonce from server */
	IN char * pszNonceCount, /* 8 hex digits */
	IN char * pszCNonce, /* client nonce */
	IN char * pszQop, /* qop-value: "", "auth", "auth-int" */
	IN char * pszMethod, /* method from the request */
	IN char * pszDigestUri, /* requested URL */
	IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */
	OUT HASHHEX Response /* request-digest or response-digest */
) {
	MD5_CTX Md5Ctx;
	HASH HA2;
	HASH RespHash;
	HASHHEX HA2Hex;
    
	// calculate H(A2)
	MD5Init(&Md5Ctx);
	MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod));
	MD5Update(&Md5Ctx, ":", 1);
	MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri));
	if (stricmp(pszQop, "auth-int") == 0) {
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, HEntity, HASHHEXLEN);
	};
	MD5Final(HA2, &Md5Ctx);
	CvtHex(HA2, HA2Hex);
    
	// calculate response
	MD5Init(&Md5Ctx);
	MD5Update(&Md5Ctx, HA1, HASHHEXLEN);
	MD5Update(&Md5Ctx, ":", 1);
	MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
	MD5Update(&Md5Ctx, ":", 1);
	if (*pszQop) {
    	MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount));
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
		MD5Update(&Md5Ctx, ":", 1);
		MD5Update(&Md5Ctx, pszQop, strlen(pszQop));
		MD5Update(&Md5Ctx, ":", 1);
	};
	MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
	MD5Final(RespHash, &Md5Ctx);
	CvtHex(RespHash, Response);
};
// File "digtest.c"

#include <stdio.h>
#include "digcalc.h"

void main(int argc, char ** argv) {
	char * pszNonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093";
	char * pszCNonce = "0a4f113b";
	char * pszUser = "Mufasa";
	char * pszRealm = "testrealm@host.com";
	char * pszPass = "Circle Of Life";
	char * pszAlg = "md5";
	char szNonceCount[9] = "00000001";
	char * pszMethod = "GET";
	char * pszQop = "auth";
	char * pszURI = "/dir/index.html";
	HASHHEX HA1;
	HASHHEX HA2 = "";
	HASHHEX Response;
    	
	DigestCalcHA1(pszAlg, pszUser, pszRealm, pszPass, pszNonce, pszCNonce, HA1);
	DigestCalcResponse(HA1, pszNonce, szNonceCount, pszCNonce, pszQop, pszMethod, pszURI, HA2, Response);
	printf("Response = %s\n", Response);
};

其他


编码 Digest 值


这里只讨论使用 MD5 的情况.

MD5 digest of 128 bits ===> 32 ASCII printable characters.
The bits in the 128 bit digest are converted from most significant to least significant bit, four bits at a time to their ASCII presentation as follows. Each four bits is represented by its familiar hexadecimal notation from the characters 0123456789abcdef. That is, binary 0000 gets represented by the character ’0’, 0001, by ’1’, and so on up to the representation of 1111 as ’f’.  

其实就 hex 编码。

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VFSSoft

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值