当我们下发DESCRIBE方法获取sdp数据的时候,神奇的一幕出现了。
DESCRIBE rtsp://192.168.1.65:554/cam/realmonitor?channel=1&subtype=0 RTSP/1.0
CSeq: 3
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
Accept: application/sdp
RTSP/1.0 401 Unauthorized
CSeq: 3
WWW-Authenticate: Digest realm="IP Camera(G8735)", nonce="251223aed58c9ed59d99e14dc6215ae1", stale="FALSE"
Date: Fri, Jun 24 2022 14:42:01 GMT
401 未认证,既然server大佬说了要认证,那作为client的小弟,就按部就班的进行认证,可以看出server大佬告诉了小弟,认证方法和一些参数,其中认证方式采用摘要认证(Digest),realm为“IP Camera(G8735)”,nonce为251223aed58c9ed59d99e14dc6215ae1。
在进行认证之前先了解一下摘要认证。如下图所示,当发送POST请求的时候,服务器返回401 Unauthorized,并且返回信息头中WWW-Authenticate:中包含了认证方式以及认证信息,其中:
realm:领域
nonce:这是由服务器规定的数据字符串,每次请求的时候这个参数都不一样,服务器根据自己的规则生成的。
qop:一般可以为“auth” “auth-int” 或者这个字段可以缺少,也就是服务器可以不返回。
algorithm:计算算法,如:MD5
算法 |
A1 |
MD5 |
A1 =MD5(<user>:<realm>:<password> |
MD5-sess |
A1 = MD5(<usr>:<realm>:<password>):<nonce>:<cnonce> |
qop |
A2 |
未定义 |
MD5(<request-method>:<uri-directive-value>) |
auth |
MD5(<request-method>:<uri-directive-value>) |
auth-int |
MD5(<request-method>:<uri-directive-value>:H(<reuest-entity-body>)) |
qop |
摘要计算 |
未定义 |
MD5(<A1>:<nonce>:<A2>) |
auth |
MD5(<A1>:<nonce>:<nc>:<cnonce>:<qop>:<A2>) |
根据上述计算摘要步骤,我们一步一步进行计算,
首先根据Algorithm=“MD5”,如果服务器返回的该字段缺少,则依旧采用MD5,此时我们计算A1,A1=MD5(<user>:<realm>:<password>)
user:admin realm:example.com password:admin123456
A1=MD5(admin:example.com:admin123456)
A1=01584afdc23b82c514d4a6368c3b9530
由于server返回qop="auth",因此这里A2的计算方式根据上面公式可以得到
A2=MD5(<request-method>:<uri-directive-value>)
其中request-method表示请求的方法,这里请求的方法为POST
uri-directive-value表示请求uri跳转值,这里为/test_api/user/login
A2=MD5(POST:/test_api/user/login)
A2=0168a093f66f599ef4930de5b537a377
好了到此我们需要计算出摘要,也就是请求中的response带的参数
摘要计算是根据qop的值而不同,当qop为定义的时候,也就是server返回401的时候没有带qop参数,此时采用MD5(<A1>:<nonce>:<A2>) 而qop带参数的时候如"auth"或者"auth-int"的时候摘要计算方式为MD5(<A1>:<nonce>:<nc>:<cnonce>:<qop>:<A2>)
这里nc是一个计数器,累加,用一个十六进制8位的字符串表示,如demo中的00000001,cnonce是客户端生成的一个随机字符。
response=MD5(<A1>:<nonce>:<nc>:<cnonce>:<qop>:<A2>)
response=MD5(01584afdc23b82c514d4a6368c3b9530:ZXhhbXBsZS5jb206NTBmYTYyMzU6NA==:00000001:5cqIUuPK:auth:0168a093f66f599ef4930de5b537a377)
response=e9b7e9780568c90c6011e1980013542f
至此我们的摘要计算完成。
代码也是通过一步一步实现摘要计算,通过先计算A1,A2,最后再计算摘要
A1的计算方式:
static void md5_A1(char A1[33],const char *algorithm,const char *usr,const char *pwd,const char *realm,const char *nonce,const char *cnonce)
{
//A1 = <user>:<realm>:<password>
//md5(A1)
MD5_CTX ctx;
unsigned char md5[16];
MD5_Init(&ctx);
MD5_Update(&ctx,(unsigned char*)usr,(unsigned int)strlen(usr));
MD5_Update(&ctx,(unsigned char*)":",1);
MD5_Update(&ctx,(unsigned char*)realm,(unsigned int)strlen(realm));
MD5_Update(&ctx,(unsigned char*)":",1);
MD5_Update(&ctx,(unsigned char*)pwd,(unsigned int)strlen(pwd));
MD5_Final(md5,&ctx);
base16(A1,md5,16);
}
A2的计算方式:
static void md5_A2(char A2[33],const char *method,const char *uri,const char *qop)
{
//A2=<request-method>:<url-directive-value>
//md5(A2)
MD5_CTX ctx;
unsigned char md5[16];
MD5_Init(&ctx);
MD5_Update(&ctx,(unsigned char*)method,(unsigned int)strlen(method));
MD5_Update(&ctx,(unsigned char*)":",1);
MD5_Update(&ctx,(unsigned char*)uri,(unsigned int)strlen(uri));
if(0 == strcmp(qop,"auth-int")){
//TODO
}
MD5_Final(md5,&ctx);
base16(A2,md5,16);
}
response计算(摘要):
static void md5_response(char *response,const char *A1,const char *A2,const char *nonce,int nc,const char *cnonce,const char *qop)
{
//MD5(MD5(A1):<nonce>:<nc>:<conce>:<qop>:MD5(A2))
MD5_CTX ctx;
unsigned char md5[16];
char hex[32];
memset(hex,0x00,sizeof(hex));
MD5_Init(&ctx);
MD5_Update(&ctx,(unsigned char*)A1,32);
MD5_Update(&ctx,(unsigned char*)":",1);
MD5_Update(&ctx,(unsigned char*)nonce,(unsigned int)strlen(nonce));
MD5_Update(&ctx,(unsigned char*)":",1);
if(*qop){
snprintf(hex,sizeof(hex),"%08x",nc);
MD5_Update(&ctx,(unsigned char*)hex,(unsigned int)strlen(hex));
MD5_Update(&ctx,(unsigned char*)":",1);
MD5_Update(&ctx,(unsigned char*)cnonce,(unsigned int)strlen(cnonce));
MD5_Update(&ctx,(unsigned char*)":",1);
MD5_Update(&ctx,(unsigned char*)qop,(unsigned int)strlen(qop));
MD5_Update(&ctx,(unsigned char*)":",1);
}
MD5_Update(&ctx,(unsigned char*)A2,32);
MD5_Final(md5,&ctx);
base16(response,md5,16);
}
static void base16(char* str, const uint8_t* data, int bytes)
{
int i;
const char hex[] = "0123456789abcdef";
for (i = 0; i < bytes; i++)
{
str[i * 2] = hex[data[i] >> 4];
str[i * 2 + 1] = hex[data[i] & 0xF];
}
str[bytes * 2] = 0;
}
然后在封装RTSP协议中的认证字段,也就是Authorization响应头。
int http_header_auth(struct http_www_authenticate_t *auth,char *pwd,char *method)
{
char A1[33];
char A2[33];
memset(A1,0x00,sizeof(A1));
memset(A2,0x00,sizeof(A2));
//根据信息计算出respone
if(NULL == auth){
return -1;
}
printf("auth->cnonce is %s\n",auth->cnonce);
//nc+1
auth->nc += 1;
if(auth->scheme == HTTP_AUTH_DIGEST){
md5_A1(A1,auth->algorithm,auth->username,pwd,auth->realm,auth->nonce,auth->cnonce);
md5_A2(A2,method,auth->uri,auth->qop);
md5_response(auth->response,A1,A2,auth->nonce,auth->nc,auth->cnonce,auth->qop);
return 0;
}
return -1;
}
int digest_auth_authorization(struct http_www_authenticate_t *auth,char *pwd,char *method,char *auth_buf,int auth_buf_size)
{
if(NULL == auth || NULL == pwd || NULL == method || NULL == auth_buf)
{
return -1;
}
if(http_header_auth(auth,pwd,method) < 0){
return -1;
}
snprintf(auth_buf,auth_buf_size,"Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
auth->username,auth->realm,auth->nonce,auth->uri,auth->response);
return 0;
}