Sig 是 App 用户在加入房间时采用的一种安全的鉴权方式,目的是为了阻止恶意攻击者盗用您的云服务使用权。本文展示如何使用 BRTC 提供的代码在您的服务端生成相应的 Sig 和在百家云后台生成临时 Sig。
要使用这些服务,您都需要在相应 SDK 的初始化或登录函数中提供 AppID , UserID , RoomID 和 Sig 四个关键参数:
Sig 的计算原理如下图所示,其本质就是对 AppID、UserID、RoomID、ExpireTime 等关键信息进行了一次哈希加密:
//Sig 计算公式,其中 key 为计算 Sig 用的应用密钥 sig = hmacsha256(key, (userid + roomid + appid + currtime + expire + base64(userid + roomid + appid + currtime + expire)))
登录百家云后台,进入 BRTC 频道,点击对应的 App 相关的配置信息,及可以获取对应 App 的应用密钥(key)和 AppID。
采用服务端计算 Sig 的方案,可以最大限度地保障计算 Sig 用的密钥不被泄露。具体的做法如下:
BRTC 提供了不同语言生成 Sig 的示例代码供你参考:
[# PHP #]
<?php class TLSSigAPI { private $sdkappid; private $key; public function __construct($app_id , $key) { $this->sdkappid = $app_id; $this->key = $key; } /** * @param string $string 需要编码的字符串数据 * @return string 编码后的base64串 * @throws \Exception */ private function base64_url_encode($string) { static $replace = Array('+' => '*', '/' => '-', '=' => '_'); $base64 = base64_encode($string); if ($base64 === false) { throw new \Exception('base64_encode error'); } return str_replace(array_keys($replace), array_values($replace), $base64); } /** * *【功能说明】使用 hmac sha256 生成 sig 字段内容,经过 base64 编码 * *【参数说明】 * userId - 用户id,限制为 int(32) 类型 * roomId - 房间id,限制长度为64字节,支持大小写英文字母(a-zA-Z)、数字(0-9)、"+"、"-"、"_"、"."、"/" * expire - Sig 票据的过期时长,单位是秒,比如 86400 代表生成的 Sig 票据在一天后就无法再使用了 * currTime - 当前时间 **/ private function hmacsha256($user_id, $room_id , $curr_time, $expire) { $content_to_be_signed = "TLS.identifier:" . $user_id . "\n" . "TLS.room:" . $room_id. "\n" . "TLS.sdkappid:" . $this->sdkappid . "\n" . "TLS.time:" . $curr_time . "\n" . "TLS.expire:" . $expire . "\n"; return base64_encode(hash_hmac( 'sha256', $content_to_be_signed, $this->key, true)); } /** * 【功能说明】用于签发 BRTC 服务中必须要使用的 Sig 鉴权票据 */ public function genSig($user_id, $room_id , $expire=86400*1) { $curr_time = time(); $sig_array = Array( 'TLS.ver' => '2.0', 'TLS.identifier' => strval($user_id), 'TLS.room' => strval($room_id), 'TLS.sdkappid' => strval($this->sdkappid), 'TLS.expire' => intval($expire), 'TLS.time' => intval($curr_time), ); $sig_array['TLS.sig'] = $this->hmacsha256( $user_id, $room_id, $curr_time, $expire ); if ($sig_array['TLS.sig'] === false) { throw new \Exception('base64_encode error'); } $json_str_sig = json_encode($sig_array); if ($json_str_sig === false) { throw new \Exception('json_encode error'); } $compressed = gzcompress($json_str_sig); if ($compressed === false) { throw new \Exception('gzcompress error'); } return $this->base64_url_encode($compressed); } }
[# Java #]
import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Base64; import java.util.Base64.Encoder; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.zip.Deflater; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.lang3.StringEscapeUtils; import com.alibaba.fastjson.JSONObject; @SuppressWarnings("deprecation") public class CalSig { private static String secret; private static String appid; final static Encoder base64_Encoder = Base64.getEncoder(); public CalSig(String secret, String appid) { super(); this.secret = secret; this.appid = appid; } /** * @param str需要编码的字符串数据 * @return string 编码后的base64串 * @throws Exception * @throws \Exception */ public static String base64_url_encode(String str) throws Exception { Map<String,String> characterMap=new HashMap<String,String>(); characterMap.put("\\+", "*"); characterMap.put("\\/", "-"); characterMap.put("\\=", "_"); //将字符串中的+,/,=替换成*,-,_;可利用正则表达式 for(Map.Entry<String,String> entry : characterMap.entrySet()) { str=str.replaceAll(entry.getKey(),entry.getValue()); } return str; } /** * 使用 hmac sha256 生成 sig 字段内容,经过 base64 编码 * @param string identifier 用户名,utf-8 编码 * @param int curr_time 当前生成 sig 的 unix 时间戳 * @param int expire 有效期,单位秒 * @return string base64 后的 sig * @throws NoSuchAlgorithmException * @throws UnsupportedEncodingException * @throws InvalidKeyException */ public static String hmacsha256(String identifier,String room,long curr_time,int expire) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException { String content_to_be_signed="TLS.identifier:"+identifier+"\n" +"TLS.room:"+room+"\n" +"TLS.sdkappid:"+appid+"\n" +"TLS.time:"+curr_time+"\n" +"TLS.expire:"+expire+"\n"; Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"); sha256_HMAC.init(secret_key); byte[] array=sha256_HMAC.doFinal(content_to_be_signed.getBytes("UTF-8")); return new String(base64_Encoder.encode(array)); } /** * @param identifier 用户身份标识 * @param expire 过期时间,单位秒,默认 1天 * @return string 签名字符串 * @throws Exception * @throws \Exception */ public String genSign(String identifier,String room,int expire) throws Exception { long curr_time=System.currentTimeMillis(); String hashhmacStr=hmacsha256(identifier,room,curr_time,expire); //为了使输出键的顺序和输入的顺序保持一致,使用LinkedHashMap LinkedHashMap<String,Object> signMap=new LinkedHashMap<String,Object>(); signMap.put("TLS.ver", "2.0"); signMap.put("TLS.identifier", identifier); signMap.put("TLS.room", room); signMap.put("TLS.sdkappid", appid); signMap.put("TLS.expire", expire); signMap.put("TLS.time", curr_time); signMap.put("TLS.sig", hashhmacStr.replaceAll("/", "\\\\/")); byte[] _compressed = compress((StringEscapeUtils.unescapeJava(JSONObject.toJSONString(signMap))).getBytes()); byte[] _encodeCompress = Base64.getEncoder().encode(_compressed); String str=new String(_encodeCompress); return base64_url_encode(str); } public static byte[] compress(byte[] bytes){ byte[] output = new byte[1024]; Deflater compresser = new Deflater(); compresser.setInput(bytes); compresser.finish(); int compressedDataLength = compresser.deflate(output); return Arrays.copyOf(output,compressedDataLength); } }
[# Python #]
#! /usr/bin/python # coding:utf-8 import hmac import hashlib import base64 import zlib import json import time def base64_encode_url(data): """ base url encode 实现""" base64_data = base64.b64encode(data) base64_data_str = bytes.decode(base64_data) base64_data_str = base64_data_str.replace('+', '*') base64_data_str = base64_data_str.replace('/', '-') base64_data_str = base64_data_str.replace('=', '_') return base64_data_str class TLSSigAPI: __sdkappid = "" # Sig 计算版本号,默认为 2.0 __version = '2.0' __key = "" #【参数说明】 # sdkappid - 应用id,控制台可获取 # key - 计算 usersig 用的加密密钥,控制台可获取 def __init__(self, sdkappid, key): self.__sdkappid = sdkappid self.__key = key ## # 使用 hmac sha256 生成 sig 字段内容,经过 base64 编码 ## #【参数说明】 # userid - 用户id,限制为 int(32) 类型 # roomid - 房间id,限制长度为64字节,支持大小写英文字母(a-zA-Z)、数字(0-9)、"+"、"-"、"_"、"."、"/" # expire - UserSig 票据的过期时长,单位是秒,比如 86400 代表生成的 Sig 票据在一天后就无法再使用了 # curr_time - 当前时间 def __hmacsha256(self, user_id, room_id, curr_time, expire): """ 通过固定串进行 hmac 然后 base64 得的 sig 字段的值""" raw_content_to_be_signed = "TLS.identifier:" + str(user_id) + "\n"\ + "TLS.room:" + str(room_id) + "\n"\ + "TLS.sdkappid:" + str(self.__sdkappid) + "\n"\ + "TLS.time:" + str(curr_time) + "\n"\ + "TLS.expire:" + str(expire) + "\n" return base64.b64encode(hmac.new(self.__key.encode('utf-8'), raw_content_to_be_signed.encode('utf-8'), hashlib.sha256).digest()) def __gen_sig(self, user_id, room_id, expire=86400): """ 用户可以采用默认的有效期生成 sig """ curr_time = int(time.time()) # curr_time = 1605331559 m = dict() m["TLS.ver"] = self.__version m["TLS.identifier"] = str(user_id) m["TLS.room"] = str(room_id) m["TLS.sdkappid"] = str(self.__sdkappid) m["TLS.expire"] = int(expire) m["TLS.time"] = int(curr_time) m["TLS.sig"] = bytes.decode(self.__hmacsha256( user_id, room_id ,curr_time, expire)) raw_sig = json.dumps(m) sig_cmpressed = zlib.compress(raw_sig.encode('utf-8')) base64_sig = base64_encode_url(sig_cmpressed) return base64_sig def genUserSig(self, user_id, room_id, expire=180*86400): """ 用户可以采用默认的有效期生成 sig """ return self.__gen_sig(user_id, room_id, expire)
[# Node.js #]
var crypto = require('crypto'); var zlib = require('zlib'); var base64url = {}; var newBuffer = function (fill, encoding) { return Buffer.from ? Buffer.from(fill, encoding) : new Buffer(fill, encoding) }; base64url.escape = function escape(str) { return str.replace(/\+/g, '*') .replace(/\//g, '-') .replace(/=/g, '_'); }; base64url.encode = function encode(str) { return this.escape(newBuffer(str).toString('base64')); }; function base64encode(str) { return newBuffer(str).toString('base64') } var TLSSigAPI = function (sdkappid, key) { this.sdkappid = sdkappid; this.key = key; }; /** * *【功能说明】使用 hmac sha256 生成 sig 字段内容,经过 base64 编码 * *【参数说明】 * userId - 用户id,限制为 int(32) 类型 * roomId - 房间id,限制长度为64字节,支持大小写英文字母(a-zA-Z)、数字(0-9)、"+"、"-"、"_"、"."、"/" * expire - Sig 票据的过期时长,单位是秒,比如 86400 代表生成的 Sig 票据在一天后就无法再使用了 * currTime - 当前时间 **/ TLSSigAPI.prototype._hmacsha256 = function (userId, roomId, currTime, expire) { var contentToBeSigned = "TLS.identifier:" + userId + "\n"; contentToBeSigned += "TLS.room:" + roomId + "\n"; contentToBeSigned += "TLS.sdkappid:" + this.sdkappid + "\n"; contentToBeSigned += "TLS.time:" + currTime + "\n"; contentToBeSigned += "TLS.expire:" + expire + "\n"; const hmac = crypto.createHmac("sha256", this.key); return hmac.update(contentToBeSigned).digest('base64'); }; TLSSigAPI.prototype.genSig = function (userId, roomId, expire) { var currTime = Math.floor(Date.now() / 1000); var sigDoc = { 'TLS.ver': "2.0", 'TLS.identifier': String(userId), 'TLS.room': String(roomId), 'TLS.sdkappid': String(this.sdkappid), 'TLS.expire': Number(expire), 'TLS.time': Number(currTime) }; var sig = this._hmacsha256(userId, roomId, currTime, expire); sigDoc['TLS.sig'] = sig; var compressed = zlib.deflateSync(newBuffer(JSON.stringify(sigDoc))).toString('base64'); return base64url.escape(compressed); } /** *【功能说明】用于签发 BRTC 服务中必须要使用的 Sig 鉴权票据 * *【参数说明】 * @param userid - 用户id,为 int(32) 类型。 * @param expire - Sig 票据的过期时长,单位是秒,比如 86400 代表生成的 UserSig 票据在一天后就无法再使用了。 */ TLSSigAPI.prototype.genUserSig = function (userId, roomId, expire) { return this.genSig(userId, roomId, expire); }; exports.TLSSigAPI = TLSSigAPI;
[# Go #]
package main import ( "bytes" "compress/zlib" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "strconv" "time" ) /** * *【功能说明】用于签发 BRTC 服务中必须要使用的 Sig 鉴权票据 * *【参数说明】 * userId - 用户id,限制为 int(32) 类型 * roomId - 房间id,限制长度为64字节,支持大小写英文字母(a-zA-Z)、数字(0-9)、"+"、"-"、"_"、"."、"/" * expire - Sig 票据的过期时长,单位是秒,比如 86400 代表生成的 Sig 票据在一天后就无法再使用了 * currTime - 当前时间 **/ func TLSSigAPI(sdkappid string, key string, userId string, roomId string, expire int) (string, error) { return genSig(sdkappid, key, userId, roomId, expire) } /** * *【功能说明】使用 hmac sha256 生成 sig 字段内容,经过 base64 编码 * **/ func hmacsha256(sdkappid string, key string, userId int, roomId string, currTime int64, expire int) string { var contentToBeSigned string contentToBeSigned = "TLS.identifier:" + strconv.Itoa(userId) + "\n" contentToBeSigned += "TLS.room:" + roomId + "\n" contentToBeSigned += "TLS.sdkappid:" + sdkappid + "\n" contentToBeSigned += "TLS.time:" + strconv.FormatInt(currTime, 10) + "\n" contentToBeSigned += "TLS.expire:" + strconv.Itoa(expire) + "\n" h := hmac.New(sha256.New, []byte(key)) h.Write([]byte(contentToBeSigned)) return base64.StdEncoding.EncodeToString(h.Sum(nil)) } func genSig(sdkappid string, key string, userId string, roomId string, expire int) (string, error) { currTime := time.Now().Unix() var sigDoc map[string]interface{} sigDoc = make(map[string]interface{}) sigDoc["TLS.ver"] = "2.0" sigDoc["TLS.identifier"] = userId sigDoc["TLS.room"] = roomId sigDoc["TLS.sdkappid"] = sdkappid sigDoc["TLS.expire"] = expire sigDoc["TLS.time"] = currTime sigDoc["TLS.sig"] = hmacsha256(sdkappid, key, userId, roomId, currTime, expire) data, err := json.Marshal(sigDoc) if err != nil { return "", err } var b bytes.Buffer w := zlib.NewWriter(&b) w.Write(data) w.Close() return base64.StdEncoding.EncodeToString(b.Bytes()), nil }
SDK 动态授权
Sig 是 App 用户在加入房间时采用的一种安全的鉴权方式,目的是为了阻止恶意攻击者盗用您的云服务使用权。
本文展示如何使用 BRTC 提供的代码在您的服务端生成相应的 Sig 和在百家云后台生成临时 Sig。
前提条件
要使用这些服务,您都需要在相应 SDK 的初始化或登录函数中提供 AppID , UserID , RoomID 和 Sig 四个关键参数:
Sig 的计算原理如下图所示,其本质就是对 AppID、UserID、RoomID、ExpireTime 等关键信息进行了一次哈希加密:
获取应用密钥
登录百家云后台,进入 BRTC 频道,点击对应的 App 相关的配置信息,及可以获取对应 App 的应用密钥(key)和 AppID。
服务端计算 Sig
采用服务端计算 Sig 的方案,可以最大限度地保障计算 Sig 用的密钥不被泄露。具体的做法如下:
BRTC 提供了不同语言生成 Sig 的示例代码供你参考:
[# PHP #]
[# Java #]
[# Python #]
[# Node.js #]
[# Go #]
获取临时 Sig