http digest

HTTP digest

摘要訪問認(rèn)證是一種協(xié)議規(guī)定的Web服務(wù)器用來同網(wǎng)頁瀏覽器進(jìn)行認(rèn)證信息協(xié)商的方法。它在密碼發(fā)出前,先對其應(yīng)用哈希函數(shù),這相對于HTTP基本認(rèn)證發(fā)送明文而言,更安全。
從技術(shù)上講,摘要認(rèn)證是使用隨機(jī)數(shù)來阻止進(jìn)行密碼分析的MD5加密哈希函數(shù)應(yīng)用。它使用HTTP協(xié)議。

圖片來自 unsplash.com

HTTP認(rèn)證方式

  • 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認(rèn)證,其他的認(rèn)證我也不太熟悉.

http基本認(rèn)證和digest認(rèn)證

基本流程都是如下:

a.客戶端發(fā)起GET請求
b.服務(wù)器響應(yīng)401 Unauthorized,WWW-Authenticate指定認(rèn)證算法,realm指定安全域
c.客戶端重新發(fā)起請求,Authorization指定用戶名和密碼信息
d.服務(wù)器認(rèn)證成功,響應(yīng)200,可選Authentication-Info

basic和digest的區(qū)別如下:
  1. basic

將“用戶名:密碼”打包并采用Base-64編碼。(提示:base64是可以直接解碼的)
缺點(diǎn):密碼很容易被窺探,可以挾持編碼后的用戶名、密碼信息,然后發(fā)給服務(wù)器進(jìn)行認(rèn)證;可以與SSL配合,隱藏用戶名密碼。

  1. digest

不以明文發(fā)送密碼,在上述第2步時(shí)服務(wù)器響應(yīng)返回隨機(jī)字符串nonce,而客戶端發(fā)送響應(yīng)摘要 =MD5(HA1:nonce:HA2),其中HA1=MD5(username:realm:password),HA2=MD5(method:digestURI)
在HTTP 摘要認(rèn)證中使用 MD5 加密是為了達(dá)成"不可逆的",也就是說,當(dāng)輸出已知的時(shí)候,確定原始的輸入應(yīng)該是相當(dāng)困難的。
如果密碼本身太過簡單,也許可以通過嘗試所有可能的輸入來找到對應(yīng)的輸出(窮舉攻擊),甚至可以通過字典或者適當(dāng)?shù)牟檎冶砑涌觳檎宜俣取?/p>

digest中一些參數(shù)介紹

username: 用戶名(網(wǎng)站定義)
password: 密碼
realm: 服務(wù)器返回的realm,一般是域名
method: 請求的方法
nonce: 服務(wù)器發(fā)給客戶端的隨機(jī)的字符串
nc(nonceCount):請求的次數(shù),用于標(biāo)記,計(jì)數(shù),防止重放攻擊
cnonce(clinetNonce): 客戶端發(fā)送給服務(wù)器的隨機(jī)字符串
qop: 保護(hù)質(zhì)量參數(shù),一般是auth,或auth-int,這會影響摘要的算法
uri: 請求的uri(只是path)
response: 客戶端根據(jù)算法算出的摘要值

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”,那么如下計(jì)算 response:
response = MD5(HA1:nonce:nc:cnonce:qop:HA2)

如果 qop 未指定,那么如下計(jì)算 response:
response = MD5(HA1:nonce:HA2)
上面的算法,是不是把你繞暈了,下面,用實(shí)例介紹一下,便于你的理解

客戶端請求 (無認(rèn)證)

Host: localhost```

**服務(wù)器響應(yīng)**

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

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
<HTML>
<HEAD>
<TITLE>Error</TITLE>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1">
</HEAD>
<BODY><H1>401 Unauthorized.</H1></BODY>
</HTML>```

客戶端請求 (用戶名 "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"

服務(wù)器響應(yīng)

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 值由三步計(jì)算而成。當(dāng)多個(gè)數(shù)值合并的時(shí)候,使用冒號作為分割符。

  1. 對用戶名、認(rèn)證域(realm)以及密碼的合并值計(jì)算 MD5 哈希值,結(jié)果稱為 HA1。
  2. 對HTTP方法以及URI的摘要的合并值計(jì)算 MD5 哈希值,例如,"GET" 和 "/dir/index.html",結(jié)果稱為 HA2。
  3. 對 HA1、服務(wù)器密碼隨機(jī)數(shù)(nonce)、請求計(jì)數(shù)(nc)、客戶端密碼隨機(jī)數(shù)(cnonce)、保護(hù)質(zhì)量(qop)以及 HA2 的合并值計(jì)算 MD5 哈希值。結(jié)果即為客戶端提供的 response 值。

因?yàn)榉?wù)器擁有與客戶端同樣的信息,因此服務(wù)器可以進(jìn)行同樣的計(jì)算,以驗(yàn)證客戶端提交的 response 值的正確性。在上面給出的例子中,結(jié)果是如下計(jì)算的。 (MD5()表示用于計(jì)算 MD5 哈希值的函數(shù);“\”表示接下一行;引號并不參與計(jì)算)
根據(jù)上面的算法所給出的示例,將在每步得出如下結(jié)果。

    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

此時(shí)客戶端可以提交一個(gè)新的請求,重復(fù)使用服務(wù)器密碼隨機(jī)數(shù)(nonce)(服務(wù)器僅在每次“401”響應(yīng)后發(fā)行新的nonce),但是提供新的客戶端密碼隨機(jī)數(shù)(cnonce)。在后續(xù)的請求中,十六進(jìn)制請求計(jì)數(shù)器(nc)必須比前一次使用的時(shí)候要大,否則攻擊者可以簡單的使用同樣的認(rèn)證信息重放老的請求。由服務(wù)器來確保在每個(gè)發(fā)出的密碼隨機(jī)數(shù)nonce時(shí),計(jì)數(shù)器是在增加的,并拒絕掉任何錯(cuò)誤的請求。顯然,改變HTTP方法和/或計(jì)數(shù)器數(shù)值都會導(dǎo)致不同的 response 值。
服務(wù)器應(yīng)當(dāng)記住最近所生成的服務(wù)器密碼隨機(jī)數(shù)nonce的值。也可以在發(fā)行每一個(gè)密碼隨機(jī)數(shù)nonce后,記住過一段時(shí)間讓它們過期。如果客戶端使用了一個(gè)過期的值,服務(wù)器應(yīng)該響應(yīng)“401”狀態(tài)號,并且在認(rèn)證頭中添加stale=TRUE,表明客戶端應(yīng)當(dāng)使用新提供的服務(wù)器密碼隨機(jī)數(shù)nonce重發(fā)請求,而不必提示用戶其它用戶名和口令。
服務(wù)器不需要保存任何過期的密碼隨機(jī)數(shù),它可以簡單的認(rèn)為所有不認(rèn)識的數(shù)值都是過期的。服務(wù)器也可以只允許每一個(gè)服務(wù)器密碼隨機(jī)數(shù)nonce使用一次,當(dāng)然,這樣就會迫使客戶端在發(fā)送每個(gè)請求的時(shí)候重復(fù)認(rèn)證過程。需要注意的是,在生成后立刻過期服務(wù)器密碼隨機(jī)數(shù)nonce是不行的,因?yàn)榭蛻舳藢]有任何機(jī)會來使用這個(gè)nonce。

______________________________________

眼睛休息一下

放松一下

php客戶端的實(shí)現(xiàn)

下面是用php實(shí)現(xiàn)生成digest認(rèn)證的頭部信息的函數(shù)

     * @param $params 傳遞的參數(shù)是一個(gè)數(shù)組,包含key:url,username,password,realm,method,cnonce,nc
     * @return array返回的數(shù)組包含Authorization: Digest所需的所有數(shù)據(jù)
     */
    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;
    }
 // 傳給函數(shù)的參數(shù)格式如下:
         $params = array(
            'username' => $username,
            'password' => $password,
            'nc' => '00000015',
            'method' => 'GET',
            'cnonce' => 'noasgnsijhretrkksanmlghnebitb',
            'opaque' => '',
            'url'   => $url
        );

php的服務(wù)器實(shí)現(xiàn)方式

$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實(shí)現(xiàn)的方法:

    from requests.auth import HTTPDigestAuth
    url = 'http://httpbin.org/digest-auth/auth/user/pass'
    response = requests.get(url, auth=HTTPDigestAuth('user', 'pass'))```


上面獲取請求的內(nèi)容,可以使用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']


下面,我?guī)銈冮喿x一下源碼:
```language
class HTTPDigestAuth(AuthBase):
    """Attaches HTTP Digest Authentication to the given Request object."""

    def __init__(self, username, password):  # 初始化,參數(shù)用戶名和密碼
        self.username = username
        self.password = password
        self.last_nonce = ''
        self.nonce_count = 0  # digest中的nc
        self.chal = {}
        self.pos = None

    # 構(gòu)建認(rèn)證的http請求頭
    def build_digest_header(self, method, url):

        realm = self.chal['realm']  # 服務(wù)器返回的realm,一般是域名的格式
        nonce = self.chal['nonce']  # 隨機(jī)數(shù)
        qop = self.chal.get('qop')  # 保護(hù)質(zhì)量參數(shù),是auth或auth-int
        algorithm = self.chal.get('algorithm')  # 摘要算法
        opaque = self.chal.get(
            'opaque')  # opaque是個(gè)字符串,它只是透傳而已,即客戶端還會原樣返回過來。實(shí)際上,上面的那些域,客戶端都還是會原樣返回的,但返回時(shí)除了以上的那些域之外,還會增加新的內(nèi)容進(jìn)來。

        # 下面開始區(qū)分不同算法
        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,請求頭中參數(shù)url是不包含前面主機(jī)
        path = p_parsed.path
        if p_parsed.query:
            path += '?' + p_parsed.query

        # 構(gòu)造 A1,A2,下面開始按http digest算法計(jì)算出返回值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連在一起再進(jìn)行一次摘要計(jì)算

        # 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')

        # 客戶端的隨機(jī)字符,這里是使用時(shí)間+隨機(jī)值,再hash計(jì)算,取得16位的隨機(jī)字符串
        s += nonce.encode('utf-8')
        s += time.ctime().encode('utf-8')
        s += os.urandom(8)
        cnonce = (hashlib.sha1(s).hexdigest()[:16])

        # 下面開始進(jìn)行最后一次的摘要計(jì)算,有區(qū)分摘要算法
        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就是提交給服務(wù)器的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)

    # 一般沒有加請求頭認(rèn)證信息時(shí),請求服務(wù)器,會返回 401 HTTP Status 401 - Unauthorized,但是頭部會返回服務(wù)器的認(rèn)證信息
    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)

        # 獲取認(rèn)證的頭部信息,格式是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): # 定義一個(gè)魔術(shù)方法,方法用于實(shí)例自身的調(diào)用,r是request方法的返回對象
        # If we have a saved nonce, skip the 401
        if self.last_nonce:  # 判斷是有沒有設(shè)置認(rèn)證頭
            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) # 調(diào)用鉤子函數(shù)
        return r

知道具體的digest加密算法,就很容易了.
實(shí)在抱歉,這么多內(nèi)容辛苦你了?。。?/p>

遠(yuǎn)方
下期再見
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、概念(載錄于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434閱讀 8,738評論 6 152
  • 工作流程 一次HTTP操作稱為一個(gè)事務(wù),其工作過程可分為四步: 1)首先客戶機(jī)與服務(wù)器需要建立連接。只要單擊某個(gè)超...
    保川閱讀 4,724評論 2 14
  • Http協(xié)議詳解 標(biāo)簽(空格分隔): Linux 聲明:本片文章非原創(chuàng),內(nèi)容來源于博客園作者M(jìn)IN飛翔的HTTP協(xié)...
    Sivin閱讀 5,344評論 3 82
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,537評論 19 139
  • 《至死方休》 獨(dú)自在人山人海中 不曾被胸口致命的傷痛擊垮 卻被生活的瑣碎 一刀一刀宰割到面目全非 就這樣的在光陰里...
    晚熟的柿子閱讀 274評論 0 1

友情鏈接更多精彩內(nèi)容