rtsp增加digest鉴权

一、背景

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, &params);
      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;
}

四、认证流程

  1. 客户端首次请求(无认证)
c 复制代码
DESCRIBE rtsp://192.168.1.108:554/av_stream/ch0 RTSP/1.0
CSeq: 1
User-Agent: Lavf58.29.100
  1. 服务器返回 401 Unauthorized
c 复制代码
RTSP/1.0 401 Unauthorized
CSeq: 1
WWW-Authenticate: Digest realm="rtspserver", nonce="9eaecd32fdcbec323660", algorithm=MD5
  1. 客户端使用认证重新请求
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"
  1. 服务器验证过程
    a. 解析认证头
    b. 检查 nonce 是否有效(未过期、匹配客户端 IP)
    c. 获取用户密码
    d. 计算期望的响应值:
    ■ HA1 = MD5(username:realm:password)
    ■ HA2 = MD5(method:uri)
    ■ 期望响应 = MD5(HA1:nonce:HA2)
    e. 比较客户端提供的响应值
    f. 如果匹配,处理请求;否则返回 401
  2. 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
相关推荐
思忖小下8 个月前
深入Android架构(从线程到AIDL)_22 IPC的Proxy-Stub设计模式04
ipc·proxy-stub
思忖小下8 个月前
深入Android架构(从线程到AIDL)_19 IPC的Proxy-Stub设计模式01
ipc·proxy-stub
思忖小下8 个月前
深入Android架构(从线程到AIDL)_20 IPC的Proxy-Stub设计模式02
ipc·proxy-stub
微澜-10 个月前
海康IPC接入TRTC时,从海康中获取的数据显示时色差不正确
音视频·ipc·trtc
小乖兽技术10 个月前
C#与C++交互开发系列(二十):跨进程通信之共享内存(Shared Memory)
c++·c#·交互·ipc
小乖兽技术10 个月前
C#与C++交互开发系列(十九):跨进程通信之套接字(Sockets)
c++·c#·交互·ipc
Hdnw10 个月前
Android IPC机制(一)多进程模式
android·ipc
bbqz0071 年前
逆向WeChat (五)
c++·微信·逆向·mojo·ipc·wechat·mmmojo
林多1 年前
【通信中间件】Fdbus HelloWorld实例
c++·rpc·ipc·例子·fdbus·通信中间件