# Key防盗链
为保障直播资源不被非法盗用，您可以使用直播的Key防盗链功能，在原始推流或播放地址末尾加上鉴权信息。在主播请求直播推流或观众请求播放时，CDN会对其URL带的加密信息进行合法性判断，仅校验通过的请求会予以响应，其它非法的访问将予以拒绝。
若您有其它自定义防盗链规则的需求，请您[提交工单](https://console.huaweicloud.com/ticket/#/ticketindex/createIndex)与华为云技术客服联系。
#### 工作原理
图1Key防盗链工作原理   
![](https://support.huaweicloud.com/iLive-live/zh-cn_image_0253712971.png "点击放大")
流程说明如下所示：
1. 租户在直播控制台开启Key防盗链功能，并配置鉴权方式、Key值和时长。
2. 直播服务将租户配置的鉴权方式、Key值和时长下发到CDN节点中。
3. 主播/观众通过租户提供的鉴权推流/播放URL向CDN请求推流或播放。
4. CDN根据推流或播放URL中携带的鉴权信息校验请求的合法性，仅校验通过的请求会被允许。
 
#### 注意事项
- 该功能为可选项，默认不启用。启用该功能后，原始直播加速URL将无法使用，需要按规则生成合法的防盗链URL。
- 建议推流与播放鉴权使用不同的Key值，以增强安全性。若防盗链URL过期，或者签名不能通过，直播流将播放失败，并返回"403 Forbidden"信息。
- 针对RTMP、FLV这类长连接业务，只有服务端收到用户请求时，才进行防盗链参数校验，校验通过后可以持续播放。
- 针对HLS这类业务，用户播放后会携带相同的防盗链参数，持续发起请求。一旦防盗链参数过期，服务端便会因校验不通过，而拒绝访问，导致播放中断。 所以建议这类业务，适当调整鉴权过期时间，避免因时间过短，而中途就播放失败。示例：如果预估HLS播放时长每次都在1小时以内，可设置过期时间为3600秒。
  
 
#### 前提条件
- 已[添加推流域名和播放域名](https://support.huaweicloud.com/iLive-live/live010002.html)，且已完成[域名关联](https://support.huaweicloud.com/iLive-live/live_01_0026.html)。
- 已在域名DNS服务商处完成[CNAME解析配置](https://support.huaweicloud.com/iLive-live/live010010.html)。
 
 #### 开启Key防盗链
1. 登录[视频直播控制台](https://console.huaweicloud.com/live2/#/live/home)。
2. 在左侧导航栏中，选择"域名管理"，进入域名管理页面。
3. 在需要配置鉴权信息的域名行，单击"管理"。
4. 选择"鉴权配置"页签。
5. 单击"Key防盗链"右侧的编辑图标，界面右侧弹出"Key防盗链"对话框。
6. 单击"开关"，配置Key防盗链参数，如[图2]所示。
   
   图2配置Key防盗链   
   ![](https://support.huaweicloud.com/iLive-live/zh-cn_image_0000001752023069.png "点击放大")
   表1Key防盗链参数说明 
   | 参数名 | 描述                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
   |:---|:---|
   | 类型   | 计算鉴权串的方式，可选为：方式A、方式B、方式C或方式D。 AB鉴权方式：采用MD5信息摘要算法，具体实现方法请参见[鉴权方式A]和[鉴权方式B]。 C鉴权方式：采用对称加密算法，具体实现方法请参见[鉴权方式C]。 D鉴权方式：采用HMAC-SHA256算法，具体实现方法请参见[鉴权方式D]。 说明： 鉴权方式ABC存在安全风险，鉴权方式D拥有更高的安全性，建议您优先使用鉴权方式D。                                                                  |
   | Key | 鉴权key值。 - 支持自定义设置，由32位的字母和数字组成。  - 支持自动生成。                                                                                                                                                                                                                                                                                                                                              |
   | 时长 | URL鉴权信息的超时时长，指的是鉴权信息中携带的请求时间与直播服务收到请求时的时间的最大差值，用于检查直播推流URL或者直播播放URL是否已过期，单位：秒，范围限制：1分钟-30天。 说明： - 针对RTMP、FLV这类长连接业务，只有服务端收到用户请求时，才进行防盗链参数校验，校验通过后可以持续播放。  - 针对HLS这类业务，用户播放后会携带相同的防盗链参数，持续发起请求。一旦防盗链参数过期，服务端便会因校验不通过，而拒绝访问，导致播放中断。 所以建议这类业务，适当调整鉴权过期时间，避免因时间过短，而中途就播放失败。示例：如果预估HLS播放时长每次都在1小时以内，可设置过期时间为3600秒。    |
      
   
   
7. 配置完成后，单击"确定"。
8. 通过以下方式获取鉴权地址。 
   ![](https://support.huaweicloud.com/iLive-live/public_sys-resources/caution_3.0-zh-cn.png)
   如果播放域名的协议选择"FLV+RTMP+RTC"协议，则不支持HLS协议内容播放；如果播放域名的协议选择HLS协议，则不支持"FLV+RTMP+RTC"协议内容播放。
   - 手动拼接：根据配置的鉴权**类型** 拼接对应的鉴权地址，各鉴权类型对应的鉴权地址拼接方法请分别参见[鉴权方式A]、[鉴权方式B]、[鉴权方式C]和[鉴权方式D]。
   
   - 自动生成：使用防盗链地址生成工具快速生成鉴权地址，具体生成方法请参见[防盗地址生成](https://support.huaweicloud.com/iLive-live/live_01_0031.html)。
   
   
   
   
9. 验证防盗链功能。 
   使用第三方直播推拉流工具，通过鉴权推流地址和播放地址进行验证，若原始推流地址和播放地址无法成功推流和播放，使用鉴权推流地址和播放地址能成功推流和播放，则表示Key防盗链生效。
   
   
 
 #### 鉴权方式A
鉴权方式A主要通过Key、timestamp、rand（随机数）、uid（设置为0）和URL计算鉴权串。
**鉴权URL格式**
```
原始URL?auth_key={timestamp}-{rand}-{uid}-{md5hash}
```
**md5hash** 的计算公式：
```
sstring = "{URI}-{Timestamp}-{rand}-{uid}-{Key}"
HashValue = md5sum(sstring)
```
表2鉴权字段描述 
| 字段      | 描述                                                                                                                                                                                                                                                                                                              |
|:---|:---|
| timestamp | 用户定义的有效访问时间起始点，值为1970年1月1日以来的当前时间秒数 。十进制或者十六进制整数。 示例：1592639100（即2020-06-20 15:45）                                                                                                                                                                                                |
| 时长      | 鉴权URL有效的时间长度。 若设置的有效时间为1800s，则用户可在从timestamp开始的1800s内允许访问直播地址。超出该区间，鉴权失败。 示例：若设置的访问时间为2020-6-30 00：00：00，则链接真正失效时间为2020-6-30 00：30：00。                                                                                                                |
| rand    | 随机数，建议使用UUID，不能包含中划线"-"。 示例：477b3bbc253f467b8def6711128c7bec                                                                                                                                                                                                                        |
| uid      | userID。暂未使用，直接设置成0即可。                                                                                                                                                                                                                                                                                             |
| md5hash | 通过md5算法计算出来的验证串，数字0-9和小写英文字母a-z混合组成，固定长度32。 ``` sstring = "{URI}-{Timestamp}-{rand}-{uid}-{Key}" HashValue = md5sum(sstring) ```                                                                                                                                                                                 |
| URI    | 指原始URL中从域名后开始到最后的路径。 - **标准直播场景** 示例：/livetest/huawei1.flv     - **低时延直播场景** 示例：/livetest/huawei1.sdp    |
| Key     | 在控制台设置的防盗链Key值，具体请参见[开启Key防盗链](https://support.huaweicloud.com/iLive-live/live_01_0049.html#live_01_0049__section176718241440)。                                                                                                                                                                                  |
   
**鉴权URL示例**
- **标准直播场景**
  以生成播放鉴权地址为例，推流鉴权地址的生成与播放鉴权地址的生成方法相同。
  ```
  原始URL：http://test-play.example.com/livetest/huawei1.flv
  timestamp：1592639100
  时长：1800s
  Key：GCTbw44s6MPLh4GqgDpnfuFHgy25Enly
  rand：477b3bbc253f467b8def6711128c7bec
  uid：0
  URI：/livetest/huawei1.flv
  ```
  根据计算公式，得到**md5hash** 。
  ```
  HashValue = md5sum("/livetest/huawei1.flv-1592639100-477b3bbc253f467b8def6711128c7bec-0-GCTbw44s6MPLh4GqgDpnfuFHgy25Enly") = dd1b5ffa00cf26acec0c169ae1cfabea
  ```
  则鉴权播放地址为：
  ```
  http://test-play.example.com/livetest/huawei1.flv?auth_key=1592639100-477b3bbc253f467b8def6711128c7bec-0-dd1b5ffa00cf26acec0c169ae1cfabea
  ```
  
 
- **低时延直播场景**
  以生成播放鉴权地址为例，推流鉴权地址的生成与播放鉴权地址的生成方法相同。
  ```
  原始URL：webrtc//test-play.example.com/livetest/huawei1
  timestamp：1592639100
  时长：1800s
  Key：GCTbw44s6MPLh4GqgDpnfuFHgy25Enly
  rand：477b3bbc253f467b8def6711128c7bec
  uid：0
  URI：/livetest/huawei1.sdp
  ```
  根据计算公式，得到**md5hash** 。
  ```
  HashValue = md5sum("/livetest/huawei1.sdp-1592639100-477b3bbc253f467b8def6711128c7bec-0-GCTbw44s6MPLh4GqgDpnfuFHgy25Enly") = 4116c2c7939307e86c6654178addc987
  ```
  则鉴权播放地址为：
  ```
  webrtc://test-play.example.com/livetest/huawei1?auth_key=1592639100-477b3bbc253f467b8def6711128c7bec-0-4116c2c7939307e86c6654178addc987
  ```
  
 #### 鉴权方式B
鉴权方式B主要通过Key、timestamp和StreamName计算鉴权串。
**鉴权URL格式**
```
原始URL?txSecret=md5(Key + StreamName + txTime)&txTime=hex(timestamp)
```
表3鉴权字段描述 
| 字段         | 描述                                                                                                                                      |
|:---|:---|
| txTime   | **txTime**设置为当前时间，为Unix时间戳的十六进制结果。 示例：5eed5888，即"2020.06.20 08:30:00"。                               |
| Key        | 在控制台设置的防盗链Key值，具体请参见[开启Key防盗链](https://support.huaweicloud.com/iLive-live/live_01_0049.html#live_01_0049__section176718241440)。        |
| txSecret | URL中的加密参数。 通过将key，StreamName，txTime依次拼接的字符串进行MD5加密算法得出。 ``` txSecret = md5（Key + StreamName + txTime） ``` |
| 时长        | 鉴权URL的有效时间。 若**txTime**设置为当前时间，有效时间设置为1249s，则播放URL过期时间为：当前时间+1249s。                                    |
   
**鉴权URL示例**
- **标准直播场景**
  以生成播放鉴权地址为例，推流鉴权地址的生成与播放鉴权地址的生成同理。
  ```
  原始URL：http://test-play.example.com/livetest/huawei1.flv
  Key：GCTbw44s6MPLh4GqgDpnfuFHgy25Enly
  StreamName：huawei1
  txTime：5eed5888
  时长：1249s
  ```
  根据计算公式，得到**txSecret** 。
  ```
  txSecret = md5(GCTbw44s6MPLh4GqgDpnfuFHgy25Enlyhuawei15eed5888) = 5cdc845362c332a4ec3e09ac5d5571d6
  ```
  则鉴权播放地址为：
  ```
  http://test-play.example.com/livetest/huawei1.flv?txSecret=5cdc845362c332a4ec3e09ac5d5571d6&txTime=5eed5888
  ```
  
 
- **低时延直播场景**
  以生成播放鉴权地址为例，推流鉴权地址的生成与播放鉴权地址的生成同理。
  ```
  原始URL：webrtc://test-play.example.com/livetest/huawei1
  Key：GCTbw44s6MPLh4GqgDpnfuFHgy25Enly
  StreamName：huawei1
  txTime：5eed5888
  时长：1249s
  ```
  根据计算公式，得到**txSecret** 。
  ```
  txSecret = md5(GCTbw44s6MPLh4GqgDpnfuFHgy25Enlyhuawei15eed5888) = 5cdc845362c332a4ec3e09ac5d5571d6
  ```
  则鉴权播放地址为：
  ```
  webrtc://test-play.example.com/livetest/huawei1?txSecret=5cdc845362c332a4ec3e09ac5d5571d6&txTime=5eed5888
  ```
  
 #### 鉴权方式C
鉴权方式C主要通过Key、Timestamp、AppName、StreamName和CheckLevel计算鉴权串。
**鉴权URL格式**
```
原始URL?auth_info=加密串.EncodedIV
```
鉴权字段的生成算法如下所示，具体代码示例请参考[代码示例]。
- **LiveID**= \<AppName\>+"/"+\<StreamName\>
- **加密串** = UrlEncode(Base64(AES128(\<Key\>,"$"+\<Timestamp\>+"$"+\<LiveID\>+"$"+\<CheckLevel\>)))
- **EncodedIV**= Hex(加密使用的IV)
 
算法中各加密参数说明如[表4]所示。
 表4加密参数说明 
| 字段           | 描述                                                                                                                                                                                                                                                                      |
|:---|:---|
| AppName    | 应用名称，与推流或播放地址中的AppName一致。                                                                                                                                                                                                                                               |
| StreamName   | 流名称，与推流或播放地址中的StreamName一致。                                                                                                                                                                                                                                               |
| Key        | 在控制台设置的防盗链Key值，具体请参见[开启Key防盗链](https://support.huaweicloud.com/iLive-live/live_01_0049.html#live_01_0049__section176718241440)。                                                                                                                                           |
| LiveID       | 直播流ID，用于标识唯一的直播流，由AppName和StreamName组成。 ``` LiveID = <AppName>+"/"+<StreamName> ```                                                                                                                                                                                     |
| Timestamp  | 鉴权参数生成的UTC时间，格式为"yyyyMMddHHmmss"，用于检查鉴权参数是否已过期，即Timestamp和当前时间差值的绝对值是否大于配置的超时时长。                                                                                                                                                                                       |
| CheckLevel | 检查级别。取值为3或者5。 - CheckLevel＝3，只检查LiveID是否匹配，不检查鉴权URL是否过期。  - CheckLevel＝5，检查LiveID是否匹配，Timestamp是否超时。   |
| IV           | CBC对称加密算法依赖IV向量，随机生成的16位数字和字母组合，IV值长度为128位；CBC模式，PKCS7填充。                                                                                                                                                                                                               |
   
**鉴权URL示例**
- **标准直播场景**
  以生成播放鉴权地址为例，推流鉴权地址的生成与播放鉴权地址的生成同理。
  ```
  原始URL：http://test-play.example.com/livetest/huawei1.flv
  AppName：livetest
  StreamName：huawei1
  Key：GCTbw44s6MPLh4GqgDpnfuFHgy25Enly
  LiveID：livetest/huawei1
  Timestamp：20190428110000
  CheckLevel：3
  IV：yCmE666N3YAq30SN
  ```
  根据计算公式，得到"加密串"和"EncodedIV"
  ```
  加密串 = I90KW7GhxOMwoy5yaeKMStZsOC%2B6WIyqU2kLBYAvcso%3D
  EncodIV = 79436d453636364e335941713330534e
  ```
  则鉴权播放地址为：
  ```
  http://test-play.example.com/livetest/huawei1.flv?auth_info=I90KW7GhxOMwoy5yaeKMStZsOC%2B6WIyqU2kLBYAvcso%3D.79436d453636364e335941713330534e
  ```
  

- **低时延直播场景**
  以生成播放鉴权地址为例，推流鉴权地址的生成与播放鉴权地址的生成同理。
  ```
  原始URL：webrtc://test-play.example.com/livetest/huawei1
  AppName：livetest
  StreamName：huawei1
  Key：GCTbw44s6MPLh4GqgDpnfuFHgy25Enly
  LiveID：livetest/huawei1
  Timestamp：20190428110000
  CheckLevel：3
  IV：yCmE666N3YAq30SN
  ```
  根据计算公式，得到"加密串"和"EncodedIV"
  ```
  加密串 = I90KW7GhxOMwoy5yaeKMStZsOC%2B6WIyqU2kLBYAvcso%3D
  EncodIV = 79436d453636364e335941713330534e
  ```
  则鉴权播放地址为：
  ```
  webrtc://test-play.example.com/livetest/huawei1?auth_info=I90KW7GhxOMwoy5yaeKMStZsOC%2B6WIyqU2kLBYAvcso%3D.79436d453636364e335941713330534e
  ```
  
 #### 鉴权方式D
鉴权方式D主要通过Key、timestamp和StreamName计算鉴权串。
**鉴权URL格式**
```
原始URL?hwSecret=hmac_sha256(Key, StreamName + hwTime)&hwTime=hex(timestamp)
```
表5鉴权字段描述 
| 字段        | 描述                                                                                                                                                                        |
|:---|:---|
| hwTime      | 播放URL的有效时间，为Unix时间戳的十六进制结果。 如果当前hwTime+时长的值大于当前请求的时间则可以正常播放，否则播放会被后台拒绝。 示例：5eed5888（即2020.06.20 08:30:00） |
| Key        | 在控制台设置的防盗链Key值，具体请参见[开启Key防盗链](https://support.huaweicloud.com/iLive-live/live_01_0049.html#live_01_0049__section176718241440)。                                          |
| hwSecret | URL中的加密参数。 以Key和StreamName+hwTime为参数进行HMAC-SHA256加密算法得出。 ``` hwSecret = hmac_sha256（Key, StreamName + hwTime） ```                          |
| 时长         | 鉴权URL的有效时间。 若**hwTime**设置为当前时间，有效时间设置为1249s，则播放URL过期时间为当前时间+ 1249s。                                                                        |
   
**鉴权URL示例**
- **标准直播场景**
  以生成播放鉴权地址为例，推流鉴权地址的生成与播放鉴权地址的生成同理。
  ```
  原始URL：http://test-play.example.com/livetest/huawei1.flv
  Key：GCTbw44s6MPLh4GqgDpnfuFHgy25Enly
  StreamName：huawei1
  hwTime：5eed5888
  时长：1249s
  ```
  根据计算公式，得到**hwSecret** 。
  ```
  hwSecret = hmac_sha256(GCTbw44s6MPLh4GqgDpnfuFHgy25Enly, huawei15eed5888) = ce201856a0957413319e883c8ccae13602f01d3d91e21daf5161964cf708a6a8
  ```
  则鉴权播放地址为：
  ```
  http://test-play.example.com/livetest/huawei1.flv?hwSecret=ce201856a0957413319e883c8ccae13602f01d3d91e21daf5161964cf708a6a8&hwTime=5eed5888
  ```
  
 
- **低时延直播场景**
  以生成播放鉴权地址为例，推流鉴权地址的生成与播放鉴权地址的生成同理。
  ```
  原始URL：webrtc://test-play.example.com/livetest/huawei1
  Key：GCTbw44s6MPLh4GqgDpnfuFHgy25Enly
  StreamName：huawei1
  hwTime：5eed5888
  时长：1249s
  ```
  根据计算公式，得到**hwSecret** 。
  ```
  hwSecret = hmac_sha256(GCTbw44s6MPLh4GqgDpnfuFHgy25Enly, huawei15eed5888) = ce201856a0957413319e883c8ccae13602f01d3d91e21daf5161964cf708a6a8
  ```
  则鉴权播放地址为：
  ```
  webrtc://test-play.example.com/livetest/huawei1?hwSecret=ce201856a0957413319e883c8ccae13602f01d3d91e21daf5161964cf708a6a8&hwTime=5eed5888
  ```
  
#### 鉴权方式A-代码示例
以下为鉴权方式A的鉴权串生成代码示例。
```
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.UUID;
public class Main {
    public static void main(String[] args) {
        // 获取当前时间戳；示例值为：1592639100
        long timestampSeconds = Instant.now().getEpochSecond();
        // UID 默认值0
        int uid = 0;
        // 生成UUID随机数 示例值为：477b3bbc253f467b8def6711128c7bec
        String rand = UUID.randomUUID().toString().replace("-", "");
        // 这里使用示例播放URI，使用时请写入实际URI
        String uri = "/livetest/huawei1.flv";
        // 这里使用示例key，实际使用请替换在控制台设置的防盗链Key值
        String key = "GCTbw44s6MPLh4GqgDpnfuFHgy25Enly";
        String sstring = uri + '-' + timestampSeconds + '-' + rand + '-' + uid + '-' + key;
        String md5hash = md5(sstring);
        String authKey = timestampSeconds + "-" + rand + "-" + uid + "-" + md5hash;
        System.out.println("auth_key: " + authKey);
    }
    /**
     * 对字符串进行 MD5 加密
     * @param input 需要加密的字符串
     * @return 加密后的十六进制字符串（32位小写）
     */
    public static String md5(String input) {
        try {
            // 获取 MD5 算法的 MessageDigest 实例
            MessageDigest digest = MessageDigest.getInstance("MD5");
            // 将字符串转换为字节数组（使用平台默认编码）
            byte[] inputBytes = input.getBytes();
            // 计算哈希值
            byte[] hashBytes = digest.digest(inputBytes);
            // 将字节数组转换为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                // 使用 & 0xff 确保无符号处理，String.format("%02x") 保证两位十六进制
                hexString.append(String.format("%02x", b & 0xff));
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }
}
```
#### 鉴权方式B-代码示例
以下为鉴权方式B的鉴权串生成代码示例。
```
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
// 鉴权B类
public class Main {
    public static void main(String[] args) {
        // 获取当前时间戳；示例值为：1592613000
        long timestampSeconds = Instant.now().getEpochSecond();
        String txTime = hexTimestamp(timestampSeconds);
        // 这里使用示例流名，实际使用请替换流名
        String streamName  = "huawei1";
        // 这里使用示例key，实际使用请替换在控制台设置的防盗链Key值
        String key = "GCTbw44s6MPLh4GqgDpnfuFHgy25Enly";
        String txSecret = md5(key + streamName + txTime);
        System.out.println("txSecret: " + txSecret);
    }
    private static String hexTimestamp(long timestampSeconds) {
        // 转换为十六进制字符串（小写）
        String hexSeconds = Long.toHexString(timestampSeconds);
        // 输出结果
        System.out.println("当前时间戳（秒）: " + timestampSeconds);
        System.out.println("十六进制（小写）: " + hexSeconds);
        return hexSeconds;
    }
    /**
     * 对字符串进行 MD5 加密
     *
     * @param input 需要加密的字符串
     * @return 加密后的十六进制字符串（32位小写）
     */
    public static String md5(String input) {
        try {
            // 获取 MD5 算法的 MessageDigest 实例
            MessageDigest digest = MessageDigest.getInstance("MD5");
            // 将字符串转换为字节数组（使用平台默认编码）
            byte[] inputBytes = input.getBytes();
            // 计算哈希值
            byte[] hashBytes = digest.digest(inputBytes);
            // 将字节数组转换为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                // 使用 & 0xff 确保无符号处理，String.format("%02x") 保证两位十六进制
                hexString.append(String.format("%02x", b & 0xff));
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }
}
```
 #### 鉴权方式C-代码示例
以下为鉴权方式C的鉴权串生成代码示例。
```
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class Main {
        public static void main(String[] args) {
// data="$"+<Timestamp>+"$"+<LiveID>+"$"+<CheckLevel>，具体请参见“鉴权方式C”
                String data = "$20190428110000$live/stream01$3";
                // 随机生成的16位数字和字母组合
byte[] ivBytes = "yCmE666N3YAq30SN".getBytes();
                //在直播控制台配置的Key值
byte[] key = "GCTbw44s6MPLh4GqgDpnfuFHgy25Enly".getBytes();
                String msg = aesCbcEncrypt(data, ivBytes, key);
try {
System.out.println(URLEncoder.encode(msg, "UTF-8") + "." + bytesToHexString(ivBytes));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
        private static String aesCbcEncrypt(String data, byte[] ivBytes, byte[] key) {
try {
SecretKeySpec sk = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                        if (ivBytes != null) {
cipher.init(Cipher.ENCRYPT_MODE, sk, new IvParameterSpec(ivBytes));
} else {
cipher.init(Cipher.ENCRYPT_MODE, sk);
}
                        return Base64.encode(cipher.doFinal(data.getBytes("UTF-8")));
} catch (Exception e) {
return null;
}
}
        public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if ((src == null) || (src.length <= 0)) {
return null;
}
                for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
}
```
以下是Base64类，用于将加密串进行编码。
```
public class Base64
{
    /** Base64编码表。*/
    private static char base64Code[] =
    {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
        'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
        'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1',
        '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',};
    /**
     * 构造方法私有化，防止实例化。
     */
    private Base64()
    {
        super();
    }
    /**
     * Base64编码。将字节数组中字节3个一组编码成4个可见字符。
     * @param bytes 需要被编码的字节数据。
     * @return 编码后的Base64字符串。
     */
    public static String encode(byte[] bytes)
    {
        int a = 0;
        // 按实际编码后长度开辟内存，加快速度
        StringBuffer buffer = new StringBuffer(((bytes.length - 1) / 3) << 2 + 4);
        // 进行编码
        for (int i = 0; i < bytes.length; i++)
        {
            a |= (bytes[i] << (16 - i % 3 * 8)) & (0xff << (16 - i % 3 * 8));
            if (i % 3 == 2 || i == bytes.length - 1)
            {
                buffer.append(Base64.base64Code[(a & 0xfc0000) >>> 18]);
                buffer.append(Base64.base64Code[(a & 0x3f000) >>> 12]);
                buffer.append(Base64.base64Code[(a & 0xfc0) >>> 6]);
                buffer.append(Base64.base64Code[a & 0x3f]);
                a = 0;
            }
        }
        // 对于长度非3的整数倍的字节数组，编码前先补0，编码后结尾处编码用=代替，
        // =的个数和短缺的长度一致，以此来标识出数据实际长度
        if (bytes.length % 3 > 0)
        {
            buffer.setCharAt(buffer.length() - 1, '=');
        }
        if (bytes.length % 3 == 1)
        {
            buffer.setCharAt(buffer.length() - 2, '=');
        }
        return buffer.toString();
    }
}
```
#### 鉴权方式D-代码示例
以下为鉴权方式D的鉴权串生成代码示例。
```
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
// 鉴权D类
public class Main {
    public static void main(String[] args) {
        // 获取当前时间戳，示例值为1592613000（即2020.06.20 08:30:00）
        long timestampSeconds = Instant.now().getEpochSecond();
        String hwTime = hexTimestamp(timestampSeconds);
        // 这里使用示例key，实际使用请替换在控制台设置的防盗链Key值
        String key = "GCTbw44s6MPLh4GqgDpnfuFHgy25Enly";
        // 这里使用示例流名，实际使用请替换流名
        String streamName  = "huawei1";
        String hwSecret = hmacSha256(key, streamName + hwTime);
        System.out.println("hwSecret: " + hwSecret);
    }
    private static String hexTimestamp(long timestampSeconds) {
        // 转换为十六进制字符串（小写）
        String hexSeconds = Long.toHexString(timestampSeconds);
        // 输出结果
        System.out.println("当前时间戳（秒）: " + timestampSeconds);
        System.out.println("十六进制（小写）: " + hexSeconds);
        return hexSeconds;
    }
    private static String hmacSha256(String secretKey, String data) {
        System.out.println("StreamName + hwTime :" + data);
        try {
            // 获取 HMAC-SHA256 的 Mac 实例
            Mac mac = Mac.getInstance("HmacSHA256");
            // 将密钥转换为 SecretKeySpec 对象
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            // 初始化 Mac 对象
            mac.init(secretKeySpec);
            // 计算 HMAC
            byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(hmacBytes);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return null;
        }
    }
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder(2 * bytes.length);
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            hexString.append(hex.length() == 1 ? "0" + hex : hex); // 补零
        }
        return hexString.toString();
    }
}
```
