前提概要:

Digest认证是为了修复基本认证协议的严重缺陷而设计的,秉承“毫不经过明文在网络发送密码”的原则,经过“密码摘要”进行认证,大大提升了安全性

  • 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,表示服务器在告知客户端用新的随机数来重试,而再也不要求用户从新输入用户名和密码了
MD5(MD5(A1):<nonce>:<nc>:<cnonce>:<qop>:MD5(A2))

算法 A1

MD5(默认)	<username>:<realm>:<password>
MD5-sess	MD5(<username>:<realm>:<password>):<nonce>:<cnonce>

算法 A2

auth(默认)	<request-method>:<uri>
auth-int	<request-method>:<uri>:MD5(<request-entity-body>)
# -*- coding: utf-8 -*-
import json
from contextlib import closing
import requests
import hashlib

uri = 'http://192.168.10.108/cgi-bin/snapshot.cgi?channel=1'
payload = {}
resp = requests.get(uri, data=payload)
print(resp.status_code)
response = resp.headers
print(response)
response = json.loads(json.dumps(dict(response)))
nonce = eval((((response['WWW-Authenticate'].split(','))[2]).split('='))[1])
opaque = eval((((response['WWW-Authenticate'].split(','))[3]).split('='))[1])
realm = eval((((response['WWW-Authenticate'].split(','))[0]).split('='))[1])
print(f'nonce:{nonce}')
print(f'opaque:{opaque}')
print(f'realm:{realm}')


def MD5_demo(str):
    md = hashlib.md5()  # 创建md5对象
    md.update(str.encode(encoding='utf-8'))
    return md.hexdigest()  # 小写


def getResponse(admin, password, uri, nonce):
    a1 = MD5_demo(f"{admin}:{realm}:{password}")
    a2 = MD5_demo(f"GET:{uri}")
    nc = "00000001"  # 
    cnonce = "0a4f113b"  # 
    qop = 'auth'
    response = MD5_demo(f"{a1}:{nonce}:{nc}:{cnonce}:{qop}:{a2}")
    print(f'response:{response}')
    return response


if __name__ == '__main__':
    response = getResponse("xxxxx", "xxxxx", "/cgi-bin/snapshot.cgi?channel=1", nonce)
    Authorization = f"""Digest username="admin", realm="Login to 3c31ed46887da25fbd83f9be93f99263", nonce={nonce}, nc=00000001, cnonce="0a4f113b", qop="auth", uri="/cgi-bin/snapshot.cgi?channel=1", response={response}  opaque="e9f7b2ca236c7b4c603cbfa07905fbbdcd427ff0" """
    print(Authorization)

    headers = dict()
    headers['Authorization'] = Authorization
    print(headers, type(headers))
    with closing(requests.get(uri, headers=headers, data=payload, stream=True)) as response:
        # 这里打开一个空的png文件,相当于创建一个空的txt文件,wb表示写文件
        with open('car.png', 'wb') as file:
            # 每128个流遍历一次
            for data in response.iter_content(128):
                # 把流写入到文件,这个文件最后写入完成就是,car.png
                file.write(data)