一、背景
rtsp鉴权使用的是base64的明文传输,但是在考虑网络安全时,不可使用明文传输,所以在rtsp上增加digest的加密方式。
二、RTSP 服务器中 Digest 认证的完整实现流程
三、代码实现
c
1. Nonce 生成
int generate_simple_nonce(rtsp_msg_nonce_s *tn)
{
uint8_t random_bytes[16];
const char hex_chars[] = "0123456789abcdef";
for (size_t i = 0; i < 16; i++) {
random_bytes[i] = rand() % 256;
}
for (int i = 0; i < 16; i++) {
tn->nonce[i * 2] = hex_chars[(random_bytes[i] >> 4) & 0x0F];
tn->nonce[i * 2 + 1] = hex_chars[random_bytes[i] & 0x0F];
}
tn->nonce[32] = '\0';
tn->expiration = time(NULL) + 60;
return 0;
}
2. Digest 认证头解析
// 解析Authorization头部的函数
int rtsp_msg_parse_digest_header(const char *header, digestParam_s *params)
{
if (!header || !params) {
return -1;
}
memset(params, 0, sizeof(digestParam_s));
const char *ptr = header;
if (strncmp(ptr, "Digest ", 7) == 0) {
ptr += 7;
}
while (*ptr != '\0') {
while (isspace(*ptr)) ptr++;
if (*ptr == '\0') break;
char key[32] = { 0 };
int key_len = 0;
while (*ptr != '=' && *ptr != '\0' && !isspace(*ptr) && key_len < sizeof(key) - 1) {
key[key_len++] = *ptr++;
}
key[key_len] = '\0';
if (*ptr == '=') ptr++;
while (isspace(*ptr)) ptr++;
int quoted = 0;
if (*ptr == '"') {
quoted = 1;
ptr++;
}
char value[512] = { 0 };
int value_len = 0;
while (*ptr != '\0' && (!quoted || *ptr != '"') &&
(quoted || (*ptr != ',' && !isspace(*ptr))) &&
value_len < sizeof(value) - 1) {
value[value_len++] = *ptr++;
}
value[value_len] = '\0';
if (quoted && *ptr == '"') ptr++;
if (*ptr == ',') ptr++;
while (isspace(*ptr)) ptr++;
if (strcmp(key, "username") == 0) {
strncpy(params->username, value, sizeof(params->username) - 1);
}
else if (strcmp(key, "realm") == 0) {
strncpy(params->realm, value, sizeof(params->realm) - 1);
}
else if (strcmp(key, "nonce") == 0) {
strncpy(params->nonce, value, sizeof(params->nonce) - 1);
}
else if (strcmp(key, "uri") == 0) {
strncpy(params->uri, value, sizeof(params->uri) - 1);
}
else if (strcmp(key, "response") == 0) {
strncpy(params->response, value, sizeof(params->response) - 1);
}
}
return 0;
}
3. 认证验证核心逻辑
int rtsp_msg_verify_response(const unsigned char *ha1_digest, const rtsp_msg_nonce_s *nonce, const char *method, const char *uri, const char *client_response)
{
if(nonce->expiration < time(NULL)) {
return -1;
}
char ha2_data[256];
sprintf(ha2_data, "%s:%s", method, uri);
unsigned char ha2_digest[16];
MD5((unsigned char *)ha2_data, strlen(ha2_data), ha2_digest);
char res_plain[512];
char ha1_hex[33], ha2_hex[33];
for (int i = 0; i < 16; i++) sprintf(ha1_hex + i * 2, "%02x", ha1_digest[i]);
for (int i = 0; i < 16; i++) sprintf(ha2_hex + i * 2, "%02x", ha2_digest[i]);
sprintf(res_plain, "%s:%s:%s", ha1_hex, nonce->nonce, ha2_hex);
unsigned char server_res[16];
MD5((unsigned char *)res_plain, strlen(res_plain), server_res);
char server_res_hex[33];
for (int i = 0; i < 16; i++) sprintf(server_res_hex + i * 2, "%02x", server_res[i]);
return (strcmp(server_res_hex, client_response));
}
4. 在 RTSP 服务器中集成
static int rtsp_authorization_request (struct rtsp_client_connection *cc, const rtsp_msg_s *reqmsg, rtsp_msg_s *resmsg)
{
// struct rtsp_demo *d = cc->demo;
struct rtsp_session *s = cc->session;
switch (s->auth_type) {
case RTSP_AUTH_TYPE_NONE:
return 0;
case RTSP_AUTH_TYPE_BASIC: {
char basic_up[32] = "";
char basic_b64a[64] = "";
char basic_b64b[64] = "";
if (rtsp_msg_get_auth_basic(reqmsg, basic_b64a, sizeof(basic_b64a))) {
dbg("authorization get fail. [peer %s:%u]\n", inet_ntoa(cc->peer_addr), cc->peer_port);
rtsp_msg_set_www_auth_basic(resmsg, "rtspserver");
rtsp_msg_set_response(resmsg, 401);//Unauthorized
return 1;
}
sprintf(basic_up, "%s:%s", s->auth_user, s->auth_passwd);
base64_encode(basic_b64b, sizeof(basic_b64b), (uint8_t*)basic_up, strlen(basic_up));
if (strcmp(basic_b64a, basic_b64b)) {
warn("authorization basic fail, my %s his %s. [peer %s:%u]\n",
basic_b64b, basic_b64a, inet_ntoa(cc->peer_addr), cc->peer_port);
rtsp_msg_set_www_auth_basic(resmsg, "rtspserver");
rtsp_msg_set_response(resmsg, 401);//Unauthorized
return 1;
}
break;
}
case RTSP_AUTH_TYPE_DIGEST:
//TODO
if(reqmsg->hdrs.auth == NULL) {
dbg("authorization get fail. [peer %s:%u]\n", inet_ntoa(cc->peer_addr), cc->peer_port);
generate_simple_nonce(&s->nonce);
rtsp_msg_set_www_auth_digest(resmsg, "rtspserver", s->nonce.nonce);
rtsp_msg_set_response(resmsg, 401);//Unauthorized
return 1;
}
digestParam_s params = {0};
rtsp_msg_parse_digest_header(reqmsg->hdrs.auth->digest, ¶ms);
char method[32];
rtsp_msg_get_method(reqmsg, method, sizeof(method));
if(rtsp_msg_verify_response(s->auth_hash, &s->nonce, method, params.uri, params.response) != 0)
{
generate_simple_nonce(&s->nonce);
rtsp_msg_set_www_auth_digest(resmsg, "rtspserver", s->nonce.nonce);
rtsp_msg_set_response(resmsg, 401);
warn("client not support authorization\n");
return 1;
}
default:
break;
}
return 0;
}
四、认证流程
- 客户端首次请求(无认证)
c
DESCRIBE rtsp://192.168.1.108:554/av_stream/ch0 RTSP/1.0
CSeq: 1
User-Agent: Lavf58.29.100
- 服务器返回 401 Unauthorized
c
RTSP/1.0 401 Unauthorized
CSeq: 1
WWW-Authenticate: Digest realm="rtspserver", nonce="9eaecd32fdcbec323660", algorithm=MD5
- 客户端使用认证重新请求
c
DESCRIBE rtsp://192.168.1.108:554/av_stream/ch0 RTSP/1.0
CSeq: 2
User-Agent: Lavf58.29.100
Authorization: Digest username="admin", realm="rtspserver",
nonce="9eaecd32fdcbec323660",
uri="rtsp://192.168.1.108:554/av_stream/ch0",
response="9f3abf8648429eaecd32fdcbec323660",
algorithm="MD5"
- 服务器验证过程
a. 解析认证头
b. 检查 nonce 是否有效(未过期、匹配客户端 IP)
c. 获取用户密码
d. 计算期望的响应值:
■ HA1 = MD5(username:realm:password)
■ HA2 = MD5(method:uri)
■ 期望响应 = MD5(HA1:nonce:HA2)
e. 比较客户端提供的响应值
f. 如果匹配,处理请求;否则返回 401 - Nonce 过期场景
c
RTSP/1.0 401 Unauthorized
CSeq: 2
WWW-Authenticate: Digest realm="rtspserver",
nonce="新nonce值",
stale=true,
algorithm=MD5
X-Auth-Error: nonce expired