OpenSDK签名开发手册

# OpenSDK分享签名开发手册

# 签名算法

开发者在配置公钥或证书时会指定签名算法。平台支持如下两种签名算法,可以在管理中心-应用详情-API安全功能中进行配置,详见安全鉴权模式介绍

  • RSAwithSHA256
  • SM2withSM3´

# 签名生成

签名串生成规则 Key1=Base64(Value1)&Key2=Base64(Value2)...

其中Key为各待签名字段,Value为待签名数据。Key保留大小写,所有字段按Key的字典序排序(根据ASCII码值从小到大排序)。Value需进行标准Base64编码(无换行符\n)。若Value内包含Unicode字符,需先进行UTF-8编码。

签名串生成后,使用配置的签名算法进行签名。

若算法为RSAwithSHA256,需使用PSS填充方式,指定salt_length为32。(PSS签名中包含随机因子,因此每次签名结果都不同,只能验签)

# 分享接口字段

OpenSDK涉及的分享接口可参考开发手册

涉及图片展示的分享接口,对应的消息对象中新增了imgDataHashthumbDataHashhdAlbumThumbFileHash等图片哈希字段,需要开发者计算图片对应的哈希值。

开发者生成消息签名后,需填入消息对象的 msgSignature 字段。

thumbDataHash等图片哈希字段与msgSignature字段增加在WXMediaMessage,与thumbData同级,其他新增字段增加在对应消息对象里。

# 各消息对象待签名字段

  1. 文字类型分享 WXTextObject

    字段名 说明 备注
    appid 当前appid 无需填入消息对象
    text 待分享内容
  2. 图片类型分享 WXImageObject

    字段名 说明 备注
    appid 当前appid 无需填入消息对象
    imgDataHash 图片SHA256 新增字段,用小写十六进制串表示
  3. 音乐类型分享 WXMusicObject (废弃)

  4. 视频类型分享 WXVideoObject

    字段名 说明 备注
    appid 当前appid 无需填入消息对象
    title 消息标题
    description 消息描述
    thumbDataHash 缩略图SHA256 新增字段,用小写十六进制串表示
    videoUrl 视频链接
    videoLowBandUrl 供低带宽的环境下使用的视频链接
  5. 网页类型分享 WXWebpageObject

    字段名 说明 备注
    appid 当前appid 无需填入消息对象
    title 消息标题
    description 消息描述
    thumbDataHash 缩略图SHA256 新增字段,用小写十六进制串表示
    webpageUrl html 链接
  6. 小程序类型分享 WXMiniProgramObject

    字段名 说明 备注
    appid 当前appid 无需填入消息对象
    title 消息标题
    description 消息描述
    thumbDataHash 缩略图SHA256 新增字段,用小写十六进制串表示
    userName 小程序的原始 id
    path 小程序的 path
  7. 音乐视频类型分享 WXMusicVideoObject

    字段名 说明 备注
    appid 当前appid 无需填入消息对象
    title 消息标题
    description 消息描述
    thumbDataHash 缩略图SHA256 新增字段,用小写十六进制串表示
    musicUrl 音频网页的URL
    musicDataUrl 音频数据的URL
    singerName 歌手名
    duration 歌曲时长
    hdAlbumThumbFileHash 高清专辑图SHA256 新增字段,用小写十六进制串表示
    albumName 音乐专辑名
    musicGenre 音乐流派
    issueDate 发行时间
    identification 音乐标识符

# 示例

以网页类型分享为例,对分享数据签名。

# RSAwithSHA256

签名使用PSS填充方式,需要指定salt长度为32。(PSS签名中包含随机因子,因此每次签名结果都会变化)

私钥信息

{
    "Appid": "wxba6223c06417af7b",
    "Sn": "97845f6ed842ea860df6fdf65941ff56",
    "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA3FoQOmOl5/CF5hF7ta4EzCy2LaU3Eu2k9DBwQ73J82I53Sx9\nLAgM1DH3IsYohRRx/BESfbdDI2powvr6QYKVIC+4Yavwg7gzhZRxWWmT1HruEADC\nZAgkUCu+9Il/9FPuitPSoIpBd07NqdkkRe82NBOfrKTdhge/5zd457fl7J81Q5VT\nIxO8vvq7FSw7k6Jtv+eOjR6SZOWbbUO7f9r4UuUkXmvdGv21qiqtaO1EMw4tUCEL\nzY73M7NpCH3RorlommYX3P6q0VrkDHrCE0/QMhmHsF+46E+IRcJ3wtEj3p/mO1Vo\nCpEhawC1U728ZUTwWNEii8hPEhcNAZTKaQMaTQIDAQABAoIBAQCXv5p/a5KcyYKc\n75tfgekh5wTLKIVmDqzT0evuauyCJTouO+4z/ZNAKuzEUO0kwPDCo8s1MpkU8boV\n1Ru1M8WZNePnt65aN+ebbaAl8FRzNvltoeg9VXIUmBvYcjzhOVAE4V2jW7M8A9QU\nzUpyswuED6OeFKfOHtYk2In2IipAqhfbyc6gn7uZSWTQsoO6hGBRQ7Ejx+vgwrbx\nZKVZ7UXbPHD0lOEPraA3PH/QUeUKpNwK2NXQoBxWcR283/HxFSAjjSSsGSBKsCnw\nDN55P2FQ0HNi5YrwUNT9190NIXSeygaRy1b+D+yBfm+yE7/qXwHLZCHsjO+2tMSS\n3KGjllTBAoGBAP9FPeYNKZuu5jt9RpZwXCc9E7Iz7bmM7zws6dun6dQH0xVVWFVm\niGIu07eqyB8HNagXseFzoXLV5EQx+3DaB0bAH+ZEpHGJJpAWSLusigssFUFuTvTF\nw+rC5hxOfidMa6+93SU5pWeJb0zJF8PRDaJ3UmwlwpYubF17sT4PD6p9AoGBANz7\nRlhRSFvggJjhEMpek3OIYWrrlRNO2MVcP7i/fGNTHhrw7OHcNGRof54QZ2Y0baL7\n1vHNokbK2mnT+cQXY/gXMmcE/eV4xyRGYiIL9nBdrkLerc43EYPv+evDvgyji6+y\n4np5cKqHrS8F+YzATk82Jt9HgdI2MvfbJTkSbmgRAoGAHNPL9rPb1An/VA6Ery6H\nKaM7Gy/EE+U3ixsjWbvvqxMrIkieDh7jHftdy2sM6Hwe8hmi6+vr+pTvD0h5tbfZ\nhILj11Q/Idc0NKdflVoZyMM0r0vuvLOsuVFDPUUb+AIoUxNk6vREmpmpqQk4ltN/\n763779yfyef6MuBqFrEKut0CgYB9FfsuuOv1nfINF7EybDCZAETsiee7ozEPHnWv\ndSzK6FytMV1VSBmcEI7UgUKWVu0MifOUsiq+WcsihmvmNLtQzoioSeoSP7ix7ulT\njmP0HQMsNPI7PW67uVZFv2pPqy/Bx8dtPlqpHN3KNV6Z7q0lJ2j/kHGK9UUKidDb\nKnS2kQKBgHZ0cYzwh9YnmfXx9mimF57aQQ8aFc9yaeD5/3G2+a/FZcHtYzUdHQ7P\nPS35blD17/NnhunHhuqakbgarH/LIFMHITCVuGQT4xS34kFVjFVhiT3cHfWyBbJ6\nGbQuzzFxz/UKDDKf3/ON41k8UP20Gdvmv/+c6qQjKPayME81elus\n-----END RSA PRIVATE KEY-----",
    "PublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FoQOmOl5/CF5hF7ta4E\nzCy2LaU3Eu2k9DBwQ73J82I53Sx9LAgM1DH3IsYohRRx/BESfbdDI2powvr6QYKV\nIC+4Yavwg7gzhZRxWWmT1HruEADCZAgkUCu+9Il/9FPuitPSoIpBd07NqdkkRe82\nNBOfrKTdhge/5zd457fl7J81Q5VTIxO8vvq7FSw7k6Jtv+eOjR6SZOWbbUO7f9r4\nUuUkXmvdGv21qiqtaO1EMw4tUCELzY73M7NpCH3RorlommYX3P6q0VrkDHrCE0/Q\nMhmHsF+46E+IRcJ3wtEj3p/mO1VoCpEhawC1U728ZUTwWNEii8hPEhcNAZTKaQMa\nTQIDAQAB\n-----END PUBLIC KEY-----"
}

原始分享数据

{
    "title": "OpenSDK分享测试",
    "description": "APP分享到微信内。",
    "webpageUrl": "https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html",
    "thumbData": "\u0089PNG\r\n\u001a..." // 缩略图字节流,原图于下方下载
}

示例缩略图下载

计算缩略图哈希值

thumbDataHash = 12c593c5bd2f0972188eb30e32bee7ad4060be582c2c6826caff89372ca84707

拼接签名串

按字典序对待签名字段排序

appid
description
thumbDataHash
title
webpageUrl

对字段内容进行标准base64编码,若存在中文等Unicode字符,需先进行UTF-8编码

appid = d3hiYTYyMjNjMDY0MTdhZjdi // wxba6223c06417af7b
description = QVBQ5YiG5Lqr5Yiw5b6u5L+h5YaF44CC // APP分享到微信内。
thumbDataHash = MTJjNTkzYzViZDJmMDk3MjE4OGViMzBlMzJiZWU3YWQ0MDYwYmU1ODJjMmM2ODI2Y2FmZjg5MzcyY2E4NDcwNw== // 12c593c5bd2f0972188eb30e32bee7ad4060be582c2c6826caff89372ca84707
title = T3BlblNES+WIhuS6q+a1i+ivlQ== // OpenSDK分享测试
webpageUrl = aHR0cHM6Ly9kZXZlbG9wZXJzLndlaXhpbi5xcS5jb20vZG9jL29wbGF0Zm9ybS9Nb2JpbGVfQXBwL1Jlc291cmNlX0NlbnRlcl9Ib21lcGFnZS5odG1s // https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html

拼接各个字段(末尾无换行符,SHA256为5d39dda9503893e4962a7590effc85f444e5a48aa71d25e44cafab8350003bab

appid=d3hiYTYyMjNjMDY0MTdhZjdi&description=QVBQ5YiG5Lqr5Yiw5b6u5L+h5YaF44CC&thumbDataHash=MTJjNTkzYzViZDJmMDk3MjE4OGViMzBlMzJiZWU3YWQ0MDYwYmU1ODJjMmM2ODI2Y2FmZjg5MzcyY2E4NDcwNw==&title=T3BlblNES+WIhuS6q+a1i+ivlQ==&webpageUrl=aHR0cHM6Ly9kZXZlbG9wZXJzLndlaXhpbi5xcS5jb20vZG9jL29wbGF0Zm9ybS9Nb2JpbGVfQXBwL1Jlc291cmNlX0NlbnRlcl9Ib21lcGFnZS5odG1s

计算签名

使用PSS填充方式计算签名S(内含随机因子每次结果都不同,需使用公钥验证签名)

base64_encode(S) = xQ/bmfmPMXRGyZvGm9sNFHcOjd5CVG9t0/qfZVOQPiHBR/cvalore/eI3PH+mzF/pxhF9JT3pev+NsT/tS8tXf2XGxE8I+b0f5TeG3hXWD1O8xizG4ZS+5ht8RjdwsyxFSriLJnsbS+oA+2E2XLDFjdSjN0JGB9/9Rfa5J6FwxE2pMbxth0EgatDj3Io7MMbKdZExK6GLaqZiWKDNOXk4RPPdULiIqh71sc3uiC955N10CQHyVclOiYSrAcf6Fr//baEI/IzPWeUUghj8uS6ljGCROpLkc9xiEwEd67VwoIWSyzGilNLkvgYh9Gn9iiiqXMTxNiVScSZE32DnJKjVg==

最终分享数据

{
    "title": "OpenSDK分享测试",
    "description": "APP分享到微信内。",
    "webpageUrl": "https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html",
    "thumbData": "\u0089PNG\r\n\u001a...",
    "thumbDataHash": "12c593c5bd2f0972188eb30e32bee7ad4060be582c2c6826caff89372ca84707",
    "msgSignature": "xQ/bmfmPMXRGyZvGm9sNFHcOjd5CVG9t0/qfZVOQPiHBR/cvalore/eI3PH+mzF/pxhF9JT3pev+NsT/tS8tXf2XGxE8I+b0f5TeG3hXWD1O8xizG4ZS+5ht8RjdwsyxFSriLJnsbS+oA+2E2XLDFjdSjN0JGB9/9Rfa5J6FwxE2pMbxth0EgatDj3Io7MMbKdZExK6GLaqZiWKDNOXk4RPPdULiIqh71sc3uiC955N10CQHyVclOiYSrAcf6Fr//baEI/IzPWeUUghj8uS6ljGCROpLkc9xiEwEd67VwoIWSyzGilNLkvgYh9Gn9iiiqXMTxNiVScSZE32DnJKjVg=="
}

示例代码

Node.js

// RSAwithSHA256
const crypto = require("crypto")

// 仅做演示,敏感信息请勿硬编码
function getCtx() {
    let ctx = {
        local_private_key: "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA3FoQOmOl5/CF5hF7ta4EzCy2LaU3Eu2k9DBwQ73J82I53Sx9\nLAgM1DH3IsYohRRx/BESfbdDI2powvr6QYKVIC+4Yavwg7gzhZRxWWmT1HruEADC\nZAgkUCu+9Il/9FPuitPSoIpBd07NqdkkRe82NBOfrKTdhge/5zd457fl7J81Q5VT\nIxO8vvq7FSw7k6Jtv+eOjR6SZOWbbUO7f9r4UuUkXmvdGv21qiqtaO1EMw4tUCEL\nzY73M7NpCH3RorlommYX3P6q0VrkDHrCE0/QMhmHsF+46E+IRcJ3wtEj3p/mO1Vo\nCpEhawC1U728ZUTwWNEii8hPEhcNAZTKaQMaTQIDAQABAoIBAQCXv5p/a5KcyYKc\n75tfgekh5wTLKIVmDqzT0evuauyCJTouO+4z/ZNAKuzEUO0kwPDCo8s1MpkU8boV\n1Ru1M8WZNePnt65aN+ebbaAl8FRzNvltoeg9VXIUmBvYcjzhOVAE4V2jW7M8A9QU\nzUpyswuED6OeFKfOHtYk2In2IipAqhfbyc6gn7uZSWTQsoO6hGBRQ7Ejx+vgwrbx\nZKVZ7UXbPHD0lOEPraA3PH/QUeUKpNwK2NXQoBxWcR283/HxFSAjjSSsGSBKsCnw\nDN55P2FQ0HNi5YrwUNT9190NIXSeygaRy1b+D+yBfm+yE7/qXwHLZCHsjO+2tMSS\n3KGjllTBAoGBAP9FPeYNKZuu5jt9RpZwXCc9E7Iz7bmM7zws6dun6dQH0xVVWFVm\niGIu07eqyB8HNagXseFzoXLV5EQx+3DaB0bAH+ZEpHGJJpAWSLusigssFUFuTvTF\nw+rC5hxOfidMa6+93SU5pWeJb0zJF8PRDaJ3UmwlwpYubF17sT4PD6p9AoGBANz7\nRlhRSFvggJjhEMpek3OIYWrrlRNO2MVcP7i/fGNTHhrw7OHcNGRof54QZ2Y0baL7\n1vHNokbK2mnT+cQXY/gXMmcE/eV4xyRGYiIL9nBdrkLerc43EYPv+evDvgyji6+y\n4np5cKqHrS8F+YzATk82Jt9HgdI2MvfbJTkSbmgRAoGAHNPL9rPb1An/VA6Ery6H\nKaM7Gy/EE+U3ixsjWbvvqxMrIkieDh7jHftdy2sM6Hwe8hmi6+vr+pTvD0h5tbfZ\nhILj11Q/Idc0NKdflVoZyMM0r0vuvLOsuVFDPUUb+AIoUxNk6vREmpmpqQk4ltN/\n763779yfyef6MuBqFrEKut0CgYB9FfsuuOv1nfINF7EybDCZAETsiee7ozEPHnWv\ndSzK6FytMV1VSBmcEI7UgUKWVu0MifOUsiq+WcsihmvmNLtQzoioSeoSP7ix7ulT\njmP0HQMsNPI7PW67uVZFv2pPqy/Bx8dtPlqpHN3KNV6Z7q0lJ2j/kHGK9UUKidDb\nKnS2kQKBgHZ0cYzwh9YnmfXx9mimF57aQQ8aFc9yaeD5/3G2+a/FZcHtYzUdHQ7P\nPS35blD17/NnhunHhuqakbgarH/LIFMHITCVuGQT4xS34kFVjFVhiT3cHfWyBbJ6\nGbQuzzFxz/UKDDKf3/ON41k8UP20Gdvmv/+c6qQjKPayME81elus\n-----END RSA PRIVATE KEY-----",
        local_sn: "97845f6ed842ea860df6fdf65941ff56"
    }
    return ctx
}

function getReq() {
    let req = {
        share_type: "WXWebpageObject",
        appid: "wxba6223c06417af7b",
        title: "OpenSDK分享测试",
        description: "APP分享到微信内。",
        webpageUrl: "https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html",
        thumbDataHash: "12c593c5bd2f0972188eb30e32bee7ad4060be582c2c6826caff89372ca84707"
    }
    return req
}

function getShareParams(share_type) {
    if (typeof share_type !== "string") return undefined
    let share_params = {
        "WXTextObject": ["appid", "text"],
        "WXImageObject": ["appid", "imgDataHash"],
        "WXVideoObject": ["appid", "title", "description", "thumbDataHash", "videoUrl", "videoLowBandUrl"],
        "WXWebpageObject": ["appid", "title", "description", "thumbDataHash", "webpageUrl"],
        // ... 按需配置参数列表
    }
    return share_params[share_type]
}

function getSignature(ctx, req) {
    const { local_private_key } = ctx // 开发者本地信息
    const share_type = req["share_type"]
    const param_list = getShareParams(share_type)
    param_list.sort() // 确保参数列表按字典序排序

    let payload = ""
    for (let i = 0; i < param_list.length; i += 1) {
        const param = param_list[i]
        let value = req[param]
        if (typeof value !== "string") value = ""
        let new_value = Buffer.from(value, "utf-8").toString("base64")
        if (i > 0) payload += "&"
        payload += `${param}=${new_value}`
    }

    // console.log(payload)
    const data_buffer = Buffer.from(payload, 'utf-8')
    const key_obj = {
        key: local_private_key,
        padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
        saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST // salt长度,需与SHA256结果长度(32)一致
    }

    const sig_buffer = ss_buffer = crypto.sign(
        'RSA-SHA256',
        data_buffer,
        key_obj
    )
    const sig = sig_buffer.toString('base64')
    return sig
}

const ctx = getCtx()
const req = getReq()

let res = getSignature(ctx, req)
console.log(res)