import{_ as r,c,b as n,a as p,d as a,e,w as o,r as D,o as t}from"./app-Dgsdh8A6.js";const i={};function y(d,s){const l=D("RouteLink");return t(),c("div",null,[s[16]||(s[16]=n("h1",{id:"app-api-签名与鉴权",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#app-api-签名与鉴权"},[n("span",null,"APP API 签名与鉴权")])],-1)),s[17]||(s[17]=n("h2",{id:"app-api-签名特性",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#app-api-签名特性"},[n("span",null,"APP API 签名特性")])],-1)),n("p",null,[s[1]||(s[1]=a("部分客户端专用的 REST API 存在基于参数签名的鉴权,需要使用规定的")),s[2]||(s[2]=n("code",null,"appkey",-1)),s[3]||(s[3]=a("及其对应的")),s[4]||(s[4]=n("code",null,"appsec",-1)),s[5]||(s[5]=a("与原始请求参数进行签名计算,部分")),s[6]||(s[6]=n("code",null,"AppKey",-1)),s[7]||(s[7]=a("及与之对应的")),s[8]||(s[8]=n("code",null,"AppSec",-1)),s[9]||(s[9]=a("已经被公开:见该文档 ")),e(l,{to:"/docs/misc/sign/APPKey.html"},{default:o(()=>s[0]||(s[0]=[a("APPKey")])),_:1,__:[0]})]),s[18]||(s[18]=p('

APP API 签名算法

  1. 首先为参数中添加appkey字段
  2. 然后按照参数的 Key 重新排序
  3. 再对这个 Key-Value 进行 url query 序列化,并拼接与之对应的appsec (盐) 进行 md5 Hash 运算(32-bit 字符小写),该 hash 便是 API 签名
  4. 最后在参数尾部增添sign字段,它的 Value 为上一步计算所得的 hash,一并作为表单或 Query 提交

Demo

该 Demo 提供 PythonJavaTS/JSSwiftC++ 语言例程

使用 appkey = 1d8b6e7d45233436, appsec = 560c52ccd288fed045859ed18bffd973 对如下 params 参数进行签名

',6)),n("p",null,[s[11]||(s[11]=a("上述示例")),s[12]||(s[12]=n("code",null,"appkey",-1)),s[13]||(s[13]=a("、")),s[14]||(s[14]=n("code",null,"AppSec",-1)),s[15]||(s[15]=a("均来自文档 ")),e(l,{to:"/docs/misc/sign/APPKey.html"},{default:o(()=>s[10]||(s[10]=[a("APPKey")])),_:1,__:[10]})]),s[19]||(s[19]=p(`

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

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

TypeScript/JavaScript

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

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

CplusPlus

需要 c++ 23 标准库,cprcryptoppnlohmann/json 等依赖

#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);
}
{"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
`,17))])}const u=r(i,[["render",y]]),v=JSON.parse('{"path":"/docs/misc/sign/APP.html","title":"APP API 签名与鉴权","lang":"zh-CN","frontmatter":{},"git":{"updatedTime":1745052268000,"contributors":[{"name":"SocialSisterYi","username":"SocialSisterYi","email":"1440239038@qq.com","commits":1,"url":"https://github.com/SocialSisterYi"},{"name":"cctyl","username":"cctyl","email":"34262992+cctyl@users.noreply.github.com","commits":1,"url":"https://github.com/cctyl"},{"name":"LaMerChiang","username":"LaMerChiang","email":"catlair@qq.com","commits":1,"url":"https://github.com/LaMerChiang"},{"name":"Lightning-Lion","username":"Lightning-Lion","email":"85005672+Lightning-Lion@users.noreply.github.com","commits":1,"url":"https://github.com/Lightning-Lion"},{"name":"YuHuanTin","username":"YuHuanTin","email":"51024916+YuHuanTin@users.noreply.github.com","commits":1,"url":"https://github.com/YuHuanTin"},{"name":"SessionHu","username":"SessionHu","email":"102411014+SessionHu@users.noreply.github.com","commits":1,"url":"https://github.com/SessionHu"}],"changelog":[{"hash":"0c42c00036ec457a4786431ba9944daf72c96fc0","time":1745052268000,"email":"102411014+SessionHu@users.noreply.github.com","author":"SessionHu","message":"fix(misc/sign/APP.md): broken demo links"},{"hash":"e7ab2d770b423a891ed7e663d94bfe1f0f59501d","time":1718245563000,"email":"51024916+YuHuanTin@users.noreply.github.com","author":"YuHuanTin","message":"wbi、av2bv、bv2av 的 c++ 实现 (#1035)"},{"hash":"faa3d97c263907ca8c492093d91406e27fc106ed","time":1703135741000,"email":"85005672+Lightning-Lion@users.noreply.github.com","author":"Lightning-Lion","message":"Update APP.md (#911)"},{"hash":"5603b1acbd02f33e54616c8a5e0ae7061a426c8e","time":1695047571000,"email":"catlair@qq.com","author":"LaMerChiang","message":"Update APP.md (#819)"},{"hash":"901e787fc655962b88dac9896b25f41d796cd2c9","time":1688262289000,"email":"34262992+cctyl@users.noreply.github.com","author":"cctyl","message":"feature: App Sign 添加java版本实现 (#729)"},{"hash":"05ac3d5e2a9e28be3bf129ae8c78ffdbebaa161c","time":1684805901000,"email":"1440239038@qq.com","author":"SocialSisterYi","message":"添加文档【Wbi 接口签名】,修改目录结构"}]},"filePathRelative":"docs/misc/sign/APP.md"}');export{u as comp,v as data};