Signer.kt
package com.shengzhiai.yugu
import java.nio.charset.StandardCharsets
import java.util.TreeMap
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
/**
* 签名器,算法与平台 `SignatureUtil.signHmacSha256` 一致。
*
* 1. 取本次请求的业务参数,丢弃值为 null 或空串的项。
* 2. 按参数名字典序升序排列,拼成 `k1=v1&k2=v2`,不做 URL 编码。
* 3. 签名值为 `Base64(HMAC_SHA256(payload, secretKey))`,两者都按 UTF-8 编码。
*
* 各接口的业务参数口径见 README 的签名一节:原生整段评测签 `config` 段的原文,
* 兼容接口签实际发送的文本表单字段,语音合成签请求体顶层非空标量,报告查询签空集,
* 实时评测握手签除 `signature` 以外的全部 query 参数。
*/
public object Signer {
private const val HMAC_SHA256 = "HmacSHA256"
/** 按签名规则拼出待签名原文。 */
@JvmStatic
public fun buildPayload(params: Map<String, *>): String {
val sorted = TreeMap<String, String>()
for ((key, raw) in params) {
if (key.isEmpty() || raw == null) continue
val value = raw.toString()
if (value.isEmpty()) continue
sorted[key] = value
}
val sb = StringBuilder()
for ((k, v) in sorted) {
if (sb.isNotEmpty()) sb.append('&')
sb.append(k).append('=').append(v)
}
return sb.toString()
}
/** 计算业务参数的签名值。 */
@JvmStatic
public fun sign(params: Map<String, *>, secretKey: String): String =
hmacSha256Base64(buildPayload(params), secretKey)
/** 对已拼好的原文计算 `Base64(HMAC_SHA256(payload, secretKey))`。 */
@JvmStatic
public fun hmacSha256Base64(payload: String, secretKey: String): String {
val mac = Mac.getInstance(HMAC_SHA256)
mac.init(SecretKeySpec(secretKey.toByteArray(StandardCharsets.UTF_8), HMAC_SHA256))
return Base64Codec.encode(mac.doFinal(payload.toByteArray(StandardCharsets.UTF_8)))
}
}
/**
* Standard Base64 with padding. android.util.Base64 is unavailable in JVM unit tests and
* java.util.Base64 needs API 26, the SDK supports API 21.
*/
internal object Base64Codec {
private const val ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
fun encode(data: ByteArray): String {
val sb = StringBuilder(((data.size + 2) / 3) * 4)
var i = 0
while (i + 2 < data.size) {
val n = ((data[i].toInt() and 0xFF) shl 16) or
((data[i + 1].toInt() and 0xFF) shl 8) or
(data[i + 2].toInt() and 0xFF)
sb.append(ALPHABET[(n ushr 18) and 0x3F])
sb.append(ALPHABET[(n ushr 12) and 0x3F])
sb.append(ALPHABET[(n ushr 6) and 0x3F])
sb.append(ALPHABET[n and 0x3F])
i += 3
}
when (data.size - i) {
1 -> {
val n = (data[i].toInt() and 0xFF) shl 16
sb.append(ALPHABET[(n ushr 18) and 0x3F])
sb.append(ALPHABET[(n ushr 12) and 0x3F])
sb.append("==")
}
2 -> {
val n = ((data[i].toInt() and 0xFF) shl 16) or ((data[i + 1].toInt() and 0xFF) shl 8)
sb.append(ALPHABET[(n ushr 18) and 0x3F])
sb.append(ALPHABET[(n ushr 12) and 0x3F])
sb.append(ALPHABET[(n ushr 6) and 0x3F])
sb.append('=')
}
}
return sb.toString()
}
}