php getdigest,http digest

2023-05-16

HTTP digest

摘要访问认证是一种协议规定的Web服务器用来同网页浏览器进行认证信息协商的方法。它在密码发出前,先对其应用哈希函数,这相对于HTTP基本认证发送明文而言,更安全。

从技术上讲,摘要认证是使用随机数来阻止进行密码分析的MD5加密哈希函数应用。它使用HTTP协议。

18fb07f2f65e

图片来自 unsplash.com

HTTP认证方式

Basic authentication

Digest authentication

WSSE(WS-Security) HTTP authentication

token Authentication

OAuth1.0 Authentication

OAuth2.0 Authentication

Kerberos

NTLM

Hawk Authentication

AWS Signature

https

今天主要科普的是digest认证,其他的认证我也不太熟悉.

http基本认证和digest认证

基本流程都是如下:

a.客户端发起GET请求

b.服务器响应401 Unauthorized,WWW-Authenticate指定认证算法,realm指定安全域

c.客户端重新发起请求,Authorization指定用户名和密码信息

d.服务器认证成功,响应200,可选Authentication-Info

basic和digest的区别如下:

basic

将“用户名:密码”打包并采用Base-64编码。(提示:base64是可以直接解码的)

缺点:密码很容易被窥探,可以挟持编码后的用户名、密码信息,然后发给服务器进行认证;可以与SSL配合,隐藏用户名密码。

digest

不以明文发送密码,在上述第2步时服务器响应返回随机字符串nonce,而客户端发送响应摘要 =MD5(HA1:nonce:HA2),其中HA1=MD5(username:realm:password),HA2=MD5(method:digestURI)

在HTTP 摘要认证中使用 MD5 加密是为了达成"不可逆的",也就是说,当输出已知的时候,确定原始的输入应该是相当困难的。

如果密码本身太过简单,也许可以通过尝试所有可能的输入来找到对应的输出(穷举攻击),甚至可以通过字典或者适当的查找表加快查找速度。

digest中一些参数介绍

username: 用户名(网站定义)

password: 密码

realm: 服务器返回的realm,一般是域名

method: 请求的方法

nonce: 服务器发给客户端的随机的字符串

nc(nonceCount):请求的次数,用于标记,计数,防止重放攻击

cnonce(clinetNonce): 客户端发送给服务器的随机字符串

qop: 保护质量参数,一般是auth,或auth-int,这会影响摘要的算法

uri: 请求的uri(只是path)

response: 客户端根据算法算出的摘要值

digest的算法:

A1 = username:realm:password

A2 = mthod:uri

HA1 = MD5(A1)

如果 qop 值为“auth”或未指定,那么 HA2 为

HA2 = MD5(A2)=MD5(method:uri)

如果 qop 值为“auth-int”,那么 HA2 为

HA2 = MD5(A2)=MD5(method:uri:MD5(entityBody))

如果 qop 值为“auth”或“auth-int”,那么如下计算 response:

response = MD5(HA1:nonce:nc:cnonce:qop:HA2)

如果 qop 未指定,那么如下计算 response:

response = MD5(HA1:nonce:HA2)

上面的算法,是不是把你绕晕了,下面,用实例介绍一下,便于你的理解

客户端请求 (无认证)

Host: localhost```

**服务器响应**

HTTP/1.0 401 Unauthorized

Server: HTTPd/0.9

Date: Sun, 10 Apr 2005 20:26:47 GMT

WWW-Authenticate: Digest realm="testrealm@host.com",

qop="auth,auth-int",

nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",

opaque="5ccc069c403ebaf9f0171e9517f40e41"

Content-Type: text/html

Content-Length: 311

/p>

"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">

Error

401 Unauthorized.

```

客户端请求 (用户名 "Mufasa", 密码 "Circle Of Life")

GET /dir/index.html HTTP/1.0

Host: localhost

Authorization: Digest username="Mufasa",

realm="testrealm@host.com",

nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",

uri="/dir/index.html",

qop=auth,

nc=00000001,

cnonce="0a4f113b",

response="6629fae49393a05397450978507c4ef1",

opaque="5ccc069c403ebaf9f0171e9517f40e41"

服务器响应

HTTP/1.0 200 OK

Server: HTTPd/0.9

Date: Sun, 10 Apr 2005 20:27:03 GMT

Content-Type: text/html

Content-Length: 7984

如下所述,response 值由三步计算而成。当多个数值合并的时候,使用冒号作为分割符。

对用户名、认证域(realm)以及密码的合并值计算 MD5 哈希值,结果称为 HA1。

对HTTP方法以及URI的摘要的合并值计算 MD5 哈希值,例如,"GET" 和 "/dir/index.html",结果称为 HA2。

对 HA1、服务器密码随机数(nonce)、请求计数(nc)、客户端密码随机数(cnonce)、保护质量(qop)以及 HA2 的合并值计算 MD5 哈希值。结果即为客户端提供的 response 值。

因为服务器拥有与客户端同样的信息,因此服务器可以进行同样的计算,以验证客户端提交的 response 值的正确性。在上面给出的例子中,结果是如下计算的。 (MD5()表示用于计算 MD5 哈希值的函数;“\”表示接下一行;引号并不参与计算)

根据上面的算法所给出的示例,将在每步得出如下结果。

HA1 = MD5( "Mufasa:testrealm@host.com:Circle Of Life" )

= 939e7578ed9e3c518a452acee763bce9

HA2 = MD5( "GET:/dir/index.html" )

= 39aff3a2bab6126f332b942af96d3366

Response = MD5( "939e7578ed9e3c518a452acee763bce9:\

dcd98b7102dd2f0e8b11d0f600bfb0c093:\

00000001:0a4f113b:auth:\

39aff3a2bab6126f332b942af96d3366" )

= 6629fae49393a05397450978507c4ef1

此时客户端可以提交一个新的请求,重复使用服务器密码随机数(nonce)(服务器仅在每次“401”响应后发行新的nonce),但是提供新的客户端密码随机数(cnonce)。在后续的请求中,十六进制请求计数器(nc)必须比前一次使用的时候要大,否则攻击者可以简单的使用同样的认证信息重放老的请求。由服务器来确保在每个发出的密码随机数nonce时,计数器是在增加的,并拒绝掉任何错误的请求。显然,改变HTTP方法和/或计数器数值都会导致不同的 response 值。

服务器应当记住最近所生成的服务器密码随机数nonce的值。也可以在发行每一个密码随机数nonce后,记住过一段时间让它们过期。如果客户端使用了一个过期的值,服务器应该响应“401”状态号,并且在认证头中添加stale=TRUE,表明客户端应当使用新提供的服务器密码随机数nonce重发请求,而不必提示用户其它用户名和口令。

服务器不需要保存任何过期的密码随机数,它可以简单的认为所有不认识的数值都是过期的。服务器也可以只允许每一个服务器密码随机数nonce使用一次,当然,这样就会迫使客户端在发送每个请求的时候重复认证过程。需要注意的是,在生成后立刻过期服务器密码随机数nonce是不行的,因为客户端将没有任何机会来使用这个nonce。

______________________________________

眼睛休息一下

18fb07f2f65e

放松一下

php客户端的实现

下面是用php实现生成digest认证的头部信息的函数

* @param $params 传递的参数是一个数组,包含key:url,username,password,realm,method,cnonce,nc

* @return array返回的数组包含Authorization: Digest所需的所有数据

*/

protected function _gen_digest_auth_header_param($params) {

$url = $params['url'];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);

curl_setopt($ch, CURLOPT_HEADER, TRUE);

curl_setopt($ch, CURLOPT_NOBODY, FALSE);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);

curl_setopt($ch, CURLOPT_AUTOREFERER, TRUE);

curl_setopt($ch, CURLOPT_TIMEOUT, 120);

$result = curl_exec($ch);

if(curl_getinfo($ch, CURLINFO_HTTP_CODE) == '401') {

$raw = explode(PHP_EOL,$result);

foreach ($raw as $value) {

if(explode(': ',$value)[0] == 'WWW-Authenticate') {

$all = explode(': ',$value)[1];

$all = str_replace('"','',$all);

$all = trim($all);

$all = explode('Digest ',$all)[1];

$auth = str_replace(', ','&',$all);

parse_str($auth,$digest);

break;

}

}

} else {

return array();

}

$c = explode('/',$url,4)[3];

$d = '/'.$c;

$digest['uri'] = $d;

$data = array_merge($digest,$params);

$HA1 = md5($data['username'].':'.$data['realm'].':'.$data['password']);

$HA2 = md5($data['method'].':'.$data['uri']);

$response = md5($HA1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$HA2);

$data['response'] = $response;

return $data;

}

// 传给函数的参数格式如下:

$params = array(

'username' => $username,

'password' => $password,

'nc' => '00000015',

'method' => 'GET',

'cnonce' => 'noasgnsijhretrkksanmlghnebitb',

'opaque' => '',

'url' => $url

);

php的服务器实现方式

$realm = 'Restricted area';

//user => password

$users = array('admin' => 'mypass', 'guest' => 'guest');

if (empty($_SERVER['PHP_AUTH_DIGEST'])) {

header('HTTP/1.1 401 Unauthorized');

header('WWW-Authenticate: Digest realm="'.$realm.

'" qop="auth" nonce="'.uniqid().'" opaque="'.md5($realm).'"');

die('Text to send if user hits Cancel button');

}

// analyze the PHP_AUTH_DIGEST variable

if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||

!isset($users[$data['username']]))

die('Wrong Credentials!');

// generate the valid response

$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);

$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);

$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);

if ($data['response'] != $valid_response)

die('Wrong Credentials!');

// ok, valid username & password

echo 'Your are logged in as: ' . $data['username'];

// function to parse the http auth header

function http_digest_parse($txt)

{

// protect against missing data

$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);

$data = array();

preg_match_all('@(\w+)=([\'"]?)([a-zA-Z0-9=./\_-]+)\2@', $txt, $matches, PREG_SET_ORDER);

foreach ($matches as $m) {

$data[$m[1]] = $m[3];

unset($needed_parts[$m[1]]);

}

return $needed_parts ? false : $data;

}

?>

______________________________________

python实现的方法:

from requests.auth import HTTPDigestAuth

url = 'http://httpbin.org/digest-auth/auth/user/pass'

response = requests.get(url, auth=HTTPDigestAuth('user', 'pass'))```

上面获取请求的内容,可以使用response.content,response.text等等

response的方法有

['attrs', 'bool', 'class', 'delattr', 'dict', 'doc', 'format', 'getattribute', 'getstate', 'hash', 'init',

'iter', 'module', 'new', 'nonzero', 'reduce', 'reduce_ex', 'repr', 'setattr', 'setstate', 'sizeof', 'str',

'subclasshook', 'weakref', '_content', '_content_consumed', 'apparent_encoding', 'close', 'connection', 'content', 'cookies', 'elapsed', 'encod

ing', 'headers', 'history', 'is_permanent_redirect', 'is_redirect', 'iter_content', 'iter_lines', 'json', 'links', 'ok', 'raise_for_status', 'raw', 're

ason', 'request', 'status_code', 'text', 'url']

下面,我带你们阅读一下源码:

```language

class HTTPDigestAuth(AuthBase):

"""Attaches HTTP Digest Authentication to the given Request object."""

def __init__(self, username, password): # 初始化,参数用户名和密码

self.username = username

self.password = password

self.last_nonce = ''

self.nonce_count = 0 # digest中的nc

self.chal = {}

self.pos = None

# 构建认证的http请求头

def build_digest_header(self, method, url):

realm = self.chal['realm'] # 服务器返回的realm,一般是域名的格式

nonce = self.chal['nonce'] # 随机数

qop = self.chal.get('qop') # 保护质量参数,是auth或auth-int

algorithm = self.chal.get('algorithm') # 摘要算法

opaque = self.chal.get(

'opaque') # opaque是个字符串,它只是透传而已,即客户端还会原样返回过来。实际上,上面的那些域,客户端都还是会原样返回的,但返回时除了以上的那些域之外,还会增加新的内容进来。

# 下面开始区分不同算法

if algorithm is None:

_algorithm = 'MD5'

else:

_algorithm = algorithm.upper()

# lambdas assume digest modules are imported at the top level

if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':

def md5_utf8(x):

if isinstance(x, str):

x = x.encode('utf-8')

return hashlib.md5(x).hexdigest() # 算出摘要值并返回

hash_utf8 = md5_utf8

elif _algorithm == 'SHA':

def sha_utf8(x):

if isinstance(x, str):

x = x.encode('utf-8')

return hashlib.sha1(x).hexdigest()

hash_utf8 = sha_utf8

KD = lambda s, d: hash_utf8("%s:%s" % (s, d))

if hash_utf8 is None:

return None

# XXX not implemented yet

entdig = None

p_parsed = urlparse(url) # 解析url,请求头中参数url是不包含前面主机

path = p_parsed.path

if p_parsed.query:

path += '?' + p_parsed.query

# 构造 A1,A2,下面开始按http digest算法计算出返回值response

A1 = '%s:%s:%s' % (self.username, realm, self.password)

A2 = '%s:%s' % (method, path)

HA1 = hash_utf8(A1)

HA2 = hash_utf8(A2)

# 未完,下面还要将HA1和HA2连在一起再进行一次摘要计算

# nc的值

if nonce == self.last_nonce:

self.nonce_count += 1

else:

self.nonce_count = 1

ncvalue = '%08x' % self.nonce_count

s = str(self.nonce_count).encode('utf-8')

# 客户端的随机字符,这里是使用时间+随机值,再hash计算,取得16位的随机字符串

s += nonce.encode('utf-8')

s += time.ctime().encode('utf-8')

s += os.urandom(8)

cnonce = (hashlib.sha1(s).hexdigest()[:16])

# 下面开始进行最后一次的摘要计算,有区分摘要算法

noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, HA2)

if _algorithm == 'MD5-SESS':

HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))

if qop is None:

respdig = KD(HA1, "%s:%s" % (nonce, HA2))

elif qop == 'auth' or 'auth' in qop.split(','):

respdig = KD(HA1, noncebit)

else:

# XXX handle auth-int.

return None

# 上面的respdig就是提交给服务器的response

self.last_nonce = nonce

# XXX should the partial digests be encoded too?

# 下面是放在http头中的请求信息,格式 "Authorization: Digest base"

base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \

'response="%s"' % (self.username, realm, nonce, path, respdig)

if opaque:

base += ', opaque="%s"' % opaque

if algorithm:

base += ', algorithm="%s"' % algorithm

if entdig:

base += ', digest="%s"' % entdig

if qop:

base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)

return 'Digest %s' % (base)

# 一般没有加请求头认证信息时,请求服务器,会返回 401 HTTP Status 401 - Unauthorized,但是头部会返回服务器的认证信息

def handle_401(self, r, **kwargs):

"""Takes the given response and tries digest-auth, if needed."""

if self.pos is not None:

# Rewind the file position indicator of the body to where

# it was to resend the request.

r.request.body.seek(self.pos)

num_401_calls = getattr(self, 'num_401_calls', 1)

# 获取认证的头部信息,格式是WWW-Authenticate →Digest realm="api.ruoyu.com", qop="auth", nonce="MTQ4MTUzMzcwNjM5ODpjYjQ5N2MwNjYyMmM4Y2JkZDM0NzI0ZDZhY2U2YTk4Yw=="

s_auth = r.headers.get('www-authenticate', '')

if 'digest' in s_auth.lower() and num_401_calls < 2:

setattr(self, 'num_401_calls', num_401_calls + 1)

pat = re.compile(r'digest ', flags=re.IGNORECASE)

self.chal = parse_dict_header(pat.sub('', s_auth, count=1))

# Consume content and release the original connection

# to allow our new request to reuse the same one.

r.content

r.raw.release_conn()

prep = r.request.copy()

extract_cookies_to_jar(prep._cookies, r.request, r.raw)

prep.prepare_cookies(prep._cookies)

prep.headers['Authorization'] = self.build_digest_header(

prep.method, prep.url)

_r = r.connection.send(prep, **kwargs)

_r.history.append(r)

_r.request = prep

return _r

setattr(self, 'num_401_calls', 1)

return r

def __call__(self, r): # 定义一个魔术方法,方法用于实例自身的调用,r是request方法的返回对象

# If we have a saved nonce, skip the 401

if self.last_nonce: # 判断是有没有设置认证头

r.headers['Authorization'] = self.build_digest_header(r.method, r.url)

try:

self.pos = r.body.tell()

except AttributeError:

pass

r.register_hook('response', self.handle_401) # 调用钩子函数

return r

知道具体的digest加密算法,就很容易了.

实在抱歉,这么多内容辛苦你了!!!

18fb07f2f65e

远方

下期再见

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

php getdigest,http digest 的相关文章

随机推荐