import{_ as t,c,a as o,b as s,d as a,e,w as p,r,o as i}from"./app-Dgsdh8A6.js";const D={};function y(d,n){const l=r("RouteLink");return i(),c("div",null,[n[21]||(n[21]=o(`

BiliTicket

简述

bili_ticket 位于请求头 Cookie 中, 非必需, 但存在可降低风控概率

@aynuarance#903 提供的思路,根据时间戳使用 hmac_sha256 算法计算 hexsign

JWT 令牌,有效时长为 259260 秒,即 3 天。 例如 eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDI3NDI3NDYsImlhdCI6MTcwMjQ4MzQ4NiwicGx0IjotMX0.xQgtTAc41NA1gzvd9yKUPgucUy_DKcQj6OG1vj8V7ZA

{
  "alg": "HS256",
  "kid": "s03",
  "typ": "JWT"
}

算法

  1. 获取 UNIX 秒级时间戳存入变量如 timestamp
  2. 计算变量 hexsign 值,使用 hmac_sha256 算法,密钥为 XgwSnGZ1p,消息为字符串 "ts" 与变量 timestamp 值拼接
  3. 构造请求参数,key_idec02hexsign 为变量 hexsign 值,context[ts] 为变量 timestamp 值,csrf 为 cookie 中的 bili_jct 值也可为空
  4. 发送 POST 请求,获取 data 字段中的 ticket 字段的值即为所求

接口

https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket

请求方式: POST

鉴权方式: 请求头 Referer 为空或 .bilibili.com 子域下任意页

URL参数:

参数名类型内容必要性备注
key_idstrec02必要
hexsignstrhmac_sha256 算法计算的 hexsign必要
context[ts]numUNIX 秒级时间戳必要
csrfstrcookie 中的 bili_jct非必要

JSON回复:

根对象:

字段类型内容备注
codenum返回值0: 成功
400: 参数错误
messagestr返回消息OK: 成功
dataobj数据本体
ttlnum1

data 对象:

`,18)),s("table",null,[n[9]||(n[9]=s("thead",null,[s("tr",null,[s("th",null,"字段"),s("th",null,"类型"),s("th",null,"内容"),s("th",null,"备注")])],-1)),s("tbody",null,[n[5]||(n[5]=s("tr",null,[s("td",null,"ticket"),s("td",null,"str"),s("td",null,"bili_ticket"),s("td")],-1)),n[6]||(n[6]=s("tr",null,[s("td",null,"created_at"),s("td",null,"num"),s("td",null,"创建时间"),s("td",null,"UNIX 秒级时间戳")],-1)),n[7]||(n[7]=s("tr",null,[s("td",null,"ttl"),s("td",null,"num"),s("td",null,"有效时长"),s("td",null,"259200 秒 (3 天)")],-1)),n[8]||(n[8]=s("tr",null,[s("td",null,"context"),s("td",null,"obj"),s("td",null,"空"),s("td")],-1)),s("tr",null,[n[2]||(n[2]=s("td",null,"nav",-1)),n[3]||(n[3]=s("td",null,"obj",-1)),n[4]||(n[4]=s("td",null,"wbi_img 相关",-1)),s("td",null,[n[1]||(n[1]=a("参见 ")),e(l,{to:"/docs/misc/sign/wbi.html"},{default:p(()=>n[0]||(n[0]=[a("WBI 签名")])),_:1,__:[0]})])])])]),n[22]||(n[22]=s("p",null,[s("code",null,"nav"),a(" 对象:")],-1)),s("table",null,[n[20]||(n[20]=s("thead",null,[s("tr",null,[s("th",null,"字段"),s("th",null,"类型"),s("th",null,"内容"),s("th",null,"备注")])],-1)),s("tbody",null,[s("tr",null,[n[12]||(n[12]=s("td",null,"img",-1)),n[13]||(n[13]=s("td",null,"str",-1)),n[14]||(n[14]=s("td",null,"img_key 值",-1)),s("td",null,[n[11]||(n[11]=a("参见 ")),e(l,{to:"/docs/misc/sign/wbi.html"},{default:p(()=>n[10]||(n[10]=[a("WBI 签名")])),_:1,__:[10]})])]),s("tr",null,[n[17]||(n[17]=s("td",null,"sub",-1)),n[18]||(n[18]=s("td",null,"str",-1)),n[19]||(n[19]=s("td",null,"sub_key 值",-1)),s("td",null,[n[16]||(n[16]=a("参见 ")),e(l,{to:"/docs/misc/sign/wbi.html"},{default:p(()=>n[15]||(n[15]=[a("WBI 签名")])),_:1,__:[15]})])])])]),n[23]||(n[23]=o(`

示例:

查看响应示例:
{
  "code": 0,
  "message": "OK",
  "data": {
    "ticket": "eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM2OTMwODAsImlhdCI6MTcyMzQzMzgyMCwicGx0IjotMX0.efOwv7i4m0ykABrXEDHGAechU2AByMcP_-3EYpQrNKs",
    "created_at": 1723433820,
    "ttl": 259200,
    "context": {},
    "nav": {
      "img": "https://i0.hdslb.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png",
      "sub": "https://i0.hdslb.com/bfs/wbi/4932caff0ff746eab6f01bf08b70ac45.png"
    }
  },
  "ttl": 1
}

Demo

此处提供 Python, Java, JavaScript (Node.js) 的示例代码

Python

需要 requests 依赖

import hmac
import hashlib
import requests
import time

def hmac_sha256(key, message):
    """
    使用HMAC-SHA256算法对给定的消息进行加密
    :param key: 密钥
    :param message: 要加密的消息
    :return: 加密后的哈希值
    """
    # 将密钥和消息转换为字节串
    key = key.encode('utf-8')
    message = message.encode('utf-8')

    # 创建HMAC对象,使用SHA256哈希算法
    hmac_obj = hmac.new(key, message, hashlib.sha256)

    # 计算哈希值
    hash_value = hmac_obj.digest()

    # 将哈希值转换为十六进制字符串
    hash_hex = hash_value.hex()

    return hash_hex


if __name__ == '__main__':
    o = hmac_sha256("XgwSnGZ1p",f"ts{int(time.time())}")
    url = "https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket"
    params = {
        "key_id":"ec02",
        "hexsign":o,
        "context[ts]":f"{int(time.time())}",
        "csrf": ''
    }

    headers = {
            'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"
        }
    resp = requests.post(url, params=params,headers=headers).json()
    print(resp)

Java

无需第三方依赖

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.StandardCharsets;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class BiliTicketDemo {

    /**
     * Convert a byte array to a hex string.
     * 
     * @param bytes The byte array to convert.
     * @return The hex string representation of the given byte array.
     */
    public static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    /**
     * Generate a HMAC-SHA256 hash of the given message string using the given key
     * string.
     * 
     * @param key     The key string to use for the HMAC-SHA256 hash.
     * @param message The message string to hash.
     * @throws Exception If an error occurs during the HMAC-SHA256 hash generation.
     * @return The HMAC-SHA256 hash of the given message string using the given key
     *         string.
     */
    public static String hmacSha256(String key, String message) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKeySpec);
        byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hash);
    }

    /**
     * Get a Bilibili web ticket for the given CSRF token.
     * 
     * @param csrf The CSRF token to use for the web ticket, can be {@code null} or
     *             empty.
     * @return The Bilibili web ticket raw response for the given CSRF token.
     * @throws Exception If an error occurs during the web ticket generation.
     * @see https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/bili_ticket.md
     */
    public static String getBiliTicket(String csrf) throws Exception {
        // params
        long ts = System.currentTimeMillis() / 1000;
        String hexSign = hmacSha256("XgwSnGZ1p", "ts" + ts);
        StringBuilder url = new StringBuilder(
                "https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket");
        url.append('?');
        url.append("key_id=ec02").append('&');
        url.append("hexsign=").append(hexSign).append('&');
        url.append("context[ts]=").append(ts).append('&');
        url.append("csrf=").append(csrf == null ? "" : csrf);
        // request
        HttpURLConnection conn = (HttpURLConnection) new URI(url.toString()).toURL().openConnection();
        conn.setRequestMethod("POST");
        conn.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0");
        InputStream in = conn.getInputStream();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int b;
        while ((b = in.read()) != -1) {
            out.write(b);
        }
        return new String(out.toByteArray(), StandardCharsets.UTF_8);
    }

    /**
     * Main method to test the BiliTicketDemo class.
     * 
     * @param args The command line arguments (not used).
     */
    public static void main(String[] args) {
        try {
            System.out.println(getBiliTicket("")); // use empty CSRF here
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

JavaScript (Node.js)

const crypto = require('crypto');

/**
 * Generate HMAC-SHA256 signature
 * @param {string} key     The key string to use for the HMAC-SHA256 hash
 * @param {string} message The message string to hash
 * @returns {string} The HMAC-SHA256 signature as a hex string
 */
function hmacSha256(key, message) {
    const hmac = crypto.createHmac('sha256', key);
    hmac.update(message);
    return hmac.digest('hex');
}

/**
 * Get Bilibili web ticket
 * @param {string} csrf    CSRF token, can be empty or null
 * @returns {Promise<any>} Promise of the ticket response in JSON format
 */
async function getBiliTicket(csrf) {
    const ts = Math.floor(Date.now() / 1000);
    const hexSign = hmacSha256('XgwSnGZ1p', \`ts\${ts}\`);
    const url = 'https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket';
    const params = new URLSearchParams({
        key_id: 'ec02',
        hexsign: hexSign,
        'context[ts]': ts,
        csrf: csrf || ''
    });
    const response = await fetch(\`\${url}?\${params.toString()}\`, {
        method: 'POST',
        headers: {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
        }
    });
    if (!response.ok) {
        throw new Error(\`HTTP error! status: \${response.status}\`);
    }
    return response.json();
}

(async () => {
    try {
        const ticketResponse = await getBiliTicket(''); // use empty CSRF here
        console.log(ticketResponse);
    } catch (e) {
        console.error('Failed to get BiliTicket:', e);
    }
})();
`,12))])}const u=t(D,[["render",y]]),v=JSON.parse('{"path":"/docs/misc/sign/bili_ticket.html","title":"BiliTicket","lang":"zh-CN","frontmatter":{},"git":{"updatedTime":1738952932000,"contributors":[{"name":"z0z0r4","username":"z0z0r4","email":"z0z0r4@outlook.com","commits":1,"url":"https://github.com/z0z0r4"},{"name":"SessionHu","username":"SessionHu","email":"102411014+SessionHu@users.noreply.github.com","commits":3,"url":"https://github.com/SessionHu"}],"changelog":[{"hash":"7282aa2e358977efb84063a3ef67ccb6ed3d705f","time":1738952932000,"email":"102411014+SessionHu@users.noreply.github.com","author":"SessionHu","message":"fix(bili_ticket.md): referer desc & nodejs code"},{"hash":"60a0c5d1a2f44fe61335da04571305fa7727a968","time":1724238159000,"email":"102411014+SessionHu@users.noreply.github.com","author":"Session小胡","message":"feat: 各种接口补充与错误修正 (#1066)"},{"hash":"18c1efbc102ae6b44c8f5314c90e5e64f0d926cd","time":1721909032000,"email":"102411014+SessionHu@users.noreply.github.com","author":"Session小胡","message":"feat: bili_ticket 算法 Java 实现 及 信息补充 及 错误修正 (#1061)"},{"hash":"180b8ce908d1abe02816c0ea89ddd1cfe6b3d7e6","time":1704853641000,"email":"z0z0r4@outlook.com","author":"z0z0r4","message":"feat: bili_ticket (#932)"}]},"filePathRelative":"docs/misc/sign/bili_ticket.md"}');export{u as comp,v as data};