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
相关推荐
大袁同学20 小时前
【进程间通信】:洞穿边界修管道,映射内存渡进程
linux·c++·管道·进程间通信·ipc
ShineWinsu2 天前
对于Linux:进程间通信IPC(匿名管道)的解析
linux·c++·面试·进程·通信·管道·ipc
ShineWinsu2 天前
对于Linux:进程间通信IPC(共享内存)的解析
linux·服务器·面试·笔试·进程·共享内存·ipc
mounter6258 天前
【内核前沿】Linux IPC 迎来大变局?POSIX 消息队列增强、io_uring IPC 与 Bus1 十年回归
linux·运维·服务器·kernel·ipc·io_uring
ShineWinsu9 天前
对于Linux:进程间通信IPC(命名管道)的解析
linux·c++·面试·笔试·进程·ipc·命名管道
拾光Ծ9 天前
【Linux系统编程】深入理解命名管道(Named Pipe):从原理到实战的完整指南
linux·c语言·linux系统编程·进程间通信·ipc·命名管道
v1326656236814 天前
博通集成:BK7259 wifi6音视频芯片 200w视频流IPC 超低功耗
物联网·音视频·低功耗·ipc
v1326656236815 天前
博通集成:BK7259 支持200w视频流IPC 带ISP 硬件H264编解码 本地算力0.1T
物联网·音视频·ipc·ai边缘
键盘会跳舞15 天前
[开源上新] 基于Share Memory的IPC : https://github.com/missionlove/SMIPC
ipc·进程间通讯·share memory·smipc
♛识尔如昼♛2 个月前
操作系统(4)第二章- 进程通信
操作系统·进程·ipc