2023-05-23 09:38:21 +08:00
# APP API 签名与鉴权
## APP API 签名特性
部分客户端专用的 REST API 存在基于参数签名的鉴权,需要使用规定的`appkey` 及其对应的`appsec` 与原始请求参数进行签名计算,部分`AppKey` 及与之对应的`AppSec` 已经被公开:见该文档 [APPKey ](APPKey.md )
- 不同 `appkey` 对应不同的 app (如客户端、概念版、必剪、漫画、bililink等)
- 不同平台同 app 也会存在不同的 `appkey` (如安卓端、ios端、TV端等)
- 同平台同 app 下不同功能也会存在不同的 `appkey` (如登录专用、取流专用等)
- 不同版本的客户端的 `appkey` 也可能不同
- **appkey与appsec一一对应**
## APP API 签名算法
1. 首先为参数中添加`appkey` 字段
2. 然后按照参数的 Key 重新排序
3. 再对这个 Key-Value 进行 url query 序列化,并拼接与之对应的`appsec` (盐) 进行 **md5 Hash 运算** ( 32-bit 字符小写),该 hash 便是 API 签名
4. 最后在参数尾部增添`sign` 字段,它的 Value 为上一步计算所得的 hash,一并作为表单或 Query 提交
## Demo
2024-06-13 10:26:03 +08:00
该 Demo 提供 [Python ](#Python )、[Java ](#Java )、[TS/JS ](#TypeScript/JavaScript )、[Swift ](#Swift )、[C++ ](#CplusPlus ) 语言例程
2023-05-23 09:38:21 +08:00
使用 appkey = `1d8b6e7d45233436` , appsec = `560c52ccd288fed045859ed18bffd973` 对如下 `params` 参数进行签名
上述示例`appkey` 、`AppSec` 均来自文档 [APPKey ](APPKey.md )
### Python
```python
import hashlib
import urllib.parse
def appsign ( params , appkey , appsec ):
'为请求参数进行 APP 签名'
params . update ({ 'appkey' : appkey })
params = dict ( sorted ( params . items ())) # 按照 key 重排参数
query = urllib . parse . urlencode ( params ) # 序列化参数
sign = hashlib . md5 (( query + appsec ) . encode ()) . hexdigest () # 计算 api 签名
params . update ({ 'sign' : sign })
return params
appkey = '1d8b6e7d45233436'
appsec = '560c52ccd288fed045859ed18bffd973'
params = {
'id' : 114514 ,
'str' : '1919810' ,
'test' : 'いいよ,こいよ' ,
}
signed_params = appsign ( params , appkey , appsec )
query = urllib . parse . urlencode ( signed_params )
print ( signed_params )
print ( query )
```
输出内容分别是进行 APP 签名的后参数的 key-Value 以及 url query 形式
```
{'appkey': '1d8b6e7d45233436', 'id': 114514, 'str': '1919810', 'test': 'いいよ,こいよ', 'sign': '01479cf20504d865519ac50f33ba3a7d'}
appkey=1d8b6e7d45233436&id=114514&str=1919810&test=%E3%81%84%E3%81%84%E3%82%88%EF%BC%8C%E3%81%93%E3%81%84%E3%82%88&sign=01479cf20504d865519ac50f33ba3a7d
```
2023-07-01 18:44:49 -07:00
### Java
```java
package io.github.cctyl ;
import java.nio.charset.StandardCharsets ;
import java.util.HashMap ;
import java.util.Map ;
import java.security.MessageDigest ;
import java.security.NoSuchAlgorithmException ;
import java.net.URLEncoder ;
import java.util.TreeMap ;
/**
* @author cctyl
*/
public class AppSigner {
private static final String APP_KEY = "1d8b6e7d45233436" ;
private static final String APP_SEC = "560c52ccd288fed045859ed18bffd973" ;
public static String appSign ( Map < String , String > params ) {
// 为请求参数进行 APP 签名
params . put ( "appkey" , APP_KEY );
// 按照 key 重排参数
Map < String , String > sortedParams = new TreeMap <> ( params );
// 序列化参数
StringBuilder queryBuilder = new StringBuilder ();
for ( Map . Entry < String , String > entry : sortedParams . entrySet ()) {
if ( queryBuilder . length () > 0 ) {
queryBuilder . append ( '&' );
}
queryBuilder
. append ( URLEncoder . encode ( entry . getKey (), StandardCharsets . UTF_8 ))
. append ( '=' )
. append ( URLEncoder . encode ( entry . getValue (), StandardCharsets . UTF_8 ));
}
return generateMD5 ( queryBuilder . append ( APP_SEC ). toString ());
}
private static String generateMD5 ( String input ) {
try {
MessageDigest md = MessageDigest . getInstance ( "MD5" );
byte [] digest = md . digest ( input . getBytes ());
StringBuilder sb = new StringBuilder ();
for ( byte b : digest ) {
sb . append ( String . format ( "%02x" , b ));
}
return sb . toString ();
} catch ( NoSuchAlgorithmException e ) {
e . printStackTrace ();
}
return null ;
}
public static void main ( String [] args ) {
Map < String , String > params = new HashMap <> ();
params . put ( "id" , "114514" );
params . put ( "str" , "1919810" );
params . put ( "test" , "いいよ,こいよ" );
System . out . println ( appSign ( params ));
}
}
```
输出结果为:01479cf20504d865519ac50f33ba3a7d
2023-09-18 22:32:51 +08:00
### TypeScript/JavaScript
```typescript
import { createHash } from 'node:crypto'
type Params = Record < string , any >
const md5 = ( str : string ) => createHash ( 'md5' ). update ( str ). digest ( 'hex' )
/**
* 为请求参数进行 APP 签名
*/
export function appSign ( params : Params , appkey : string , appsec : string ) {
params . appkey = appkey
const searchParams = new URLSearchParams ( params )
searchParams . sort ()
return md5 ( searchParams . toString () + appsec )
}
console . log (
appSign (
{
id : 114514 ,
str : '1919810' ,
test : 'いいよ,こいよ' ,
},
'1d8b6e7d45233436' ,
'560c52ccd288fed045859ed18bffd973' ,
),
'01479cf20504d865519ac50f33ba3a7d' ,
)
```
输出结果为:01479cf20504d865519ac50f33ba3a7d
2023-12-21 13:15:41 +08:00
### Swift
```swift
import Foundation
import CommonCrypto
//Swift标准库没有MD5函数,所以我们要自己实现一个
func MD5 ( string : String ) -> String {
let length = Int ( CC_MD5_DIGEST_LENGTH )
var digest = [ UInt8 ]( repeating : 0 , count : length )
if let d = string . data ( using : . utf8 ) {
_ = d . withUnsafeBytes { body -> String in
CC_MD5 ( body . baseAddress , CC_LONG ( d . count ), & digest )
return ""
}
}
return ( 0. .< length ). reduce ( "" ) {
$0 + String ( format : "%02x" , digest [ $1 ])
}
}
func appSign ( params : [ String : String ], appKey : String , appSec : String ) -> String {
var signedParams = params
signedParams [ "appkey" ] = appKey
let sortedParams = signedParams . sorted { $0 . key < $1 . key }
//在制作成query时,需要显式addingPercentEncoding转换
let query = sortedParams . map { " \( $0 . key ) = \( $0 . value . addingPercentEncoding ( withAllowedCharacters : . urlQueryAllowed ) ! ) " }. joined ( separator : "&" )
let sign = MD5 ( string : query + appSec )
return sign
}
//testSign
let appKey = "1d8b6e7d45233436"
let appSec = "560c52ccd288fed045859ed18bffd973"
let signResult = appSign ( params : [
"id" : "114514" ,
"str" : "1919810" ,
"test" : "いいよ,こいよ" ,
], appKey : appKey , appSec : appSec )
print ( signResult )
```
输出结果为:01479cf20504d865519ac50f33ba3a7d
2024-06-13 10:26:03 +08:00
### CplusPlus
需要 c++ 23 标准库,[cpr ](https://github.com/libcpr/cpr )、[cryptopp ](https://github.com/weidai11/cryptopp )、[nlohmann/json ](https://github.com/nlohmann/json ) 等依赖
```c++
#include <print> // std::println
/// thrid party libraries
#include <cpr/cpr.h> // cpr::util::urlEncode()
#include <cryptopp/md5.h>
#include <cryptopp/hex.h>
#include <nlohmann/json.hpp>
/*
* 注意,假定不会发生错误!
*/
/* 获取 md5 hex(lower) */
std::string Get_md5_hex(const std::string &Input_str) {
CryptoPP::Weak1::MD5 hash;
std::string md5_hex;
CryptoPP::StringSource ss(Input_str, true,
new CryptoPP::HashFilter(hash,
new CryptoPP::HexEncoder(
new CryptoPP::StringSink(md5_hex)
)
)
);
std::ranges::for_each(md5_hex, [](char &x) { x = std::tolower(x); });
return md5_hex;
}
/* 将 json 转换为 url 编码字符串 */
std::string Json_to_url_encode_str(const nlohmann::json &Json) {
std::string encode_str;
for (const auto &[key, value]: Json.items()) {
encode_str.append(key).append("=").append(cpr::util::urlEncode(value.is_string() ? value.get<std::string>() : to_string(value))).append("&");
}
// remove the last '&'
encode_str.resize(encode_str.size() - 1, '\0');
return encode_str;
}
std::string App_sign(nlohmann::json &Params, const std::string &App_key, const std::string &App_sec) {
Params["appkey"] = App_key;
Params["sign"] = Get_md5_hex(Json_to_url_encode_str(Params) + App_sec);
return Json_to_url_encode_str(Params);
}
int main() {
nlohmann::json Params;
Params["id"] = 114514;
Params["str"] = "1919810";
Params["test"] = "いいよ,こいよ";
constexpr auto App_key = "1d8b6e7d45233436";
constexpr auto App_sec = "560c52ccd288fed045859ed18bffd973";
std::string sign = App_sign(Params, App_key, App_sec);
std::println("{}", to_string(Params));
std::println("{}", sign);
}
` ``
` ``text
{"appkey":"1d8b6e7d45233436","id":114514,"sign":"01479cf20504d865519ac50f33ba3a7d","str":"1919810","test":"いいよ,こいよ"}
appkey=1d8b6e7d45233436&id=114514&sign=01479cf20504d865519ac50f33ba3a7d&str=1919810&test=%E3%81%84%E3%81%84%E3%82%88%EF%BC%8C%E3%81%93%E3%81%84%E3%82%88
` ``