手机号+平台ID和appsecret的简单签名算法
一个基于手机号、平台ID和appsecret的简单签名算法的PHP实现,包含签名生成和验证的完整设计思路
首先,我们设计一个简单且安全的签名流程,核心步骤和参数如下:
步骤 | 关键参数/操作 | 说明 |
---|---|---|
1. 参数排序 | ksort($params) | 确保参数顺序一致 |
2. 构造待签名字符串 | 手机号 、平台ID 、随机数(Nonce) 、时间戳 | 包含身份、时效性和唯一性要素 |
3. 生成签名 | hash_hmac (使用SHA-256) | 利用密钥生成不可伪造的签名 |
4. 验证签名 | 重新计算并比对签名、检查时间戳和随机数 | 服务端验证请求的合法性 |
PHP代码实现
1. 签名生成类 (SignatureGenerator
)
这个类用于在客户端或调用方生成签名。
<?phpclass SignatureGenerator
{private $appSecret;public function __construct($appSecret){$this->appSecret = $appSecret;}/*** 生成签名* @param string $appId 平台ID* @param string $phone 手机号* @param int $timestamp 时间戳(秒)* @param string $nonce 随机字符串* @return string 返回Base64编码的签名*/public function generateSignature($appId, $phone, $timestamp, $nonce){// 1. 准备待签名参数(排除sign本身)$params = ['appid' => $appId,'phone' => $phone,'timestamp' => $timestamp,'nonce' => $nonce];// 2. 按参数名的ASCII码字典序排序ksort($params);// 3. 拼接键值对,形成字符串A$stringA = '';foreach ($params as $key => $value) {// 空值参数通常不参与签名,但这里所有参数都是必需的$stringA .= $key . '=' . $value . '&';}$stringA = rtrim($stringA, '&'); // 去掉末尾的'&'// 4. 在字符串A最后拼接上AppSecret,得到待签名字符串stringSignTemp$stringSignTemp = $stringA . '&key=' . $this->appSecret;// 5. 使用HMAC-SHA256算法计算签名$hash = hash_hmac('sha256', $stringSignTemp, $this->appSecret, true);// 6. 对二进制签名进行Base64编码$signature = base64_encode($hash);return $signature;}/*** 生成带签名的完整请求参数数组*/public function generateSignedRequest($appId, $phone){$timestamp = time();$nonce = bin2hex(random_bytes(8)); // 生成16位随机字符串$signature = $this->generateSignature($appId, $phone, $timestamp, $nonce);$requestParams = ['appid' => $appId,'phone' => $phone,'timestamp' => $timestamp,'nonce' => $nonce,'sign' => $signature // 将签名加入请求参数];return $requestParams;}
}?>
2. 签名验证类 (SignatureVerifier
)
这个类用于在服务端验证收到的请求签名是否合法。
<?phpclass SignatureVerifier
{private $appSecret;private $timestampTolerance = 300; // 时间戳容差,默认5分钟(300秒)public function __construct($appSecret){$this->appSecret = $appSecret;}/*** 验证签名* @param array $requestParams 收到的所有请求参数(必须包含appid, phone, timestamp, nonce, sign)* @return bool 验证通过返回true,失败返回false*/public function verifySignature($requestParams){// 1. 检查必要参数是否存在if (!isset($requestParams['appid'], $requestParams['phone'], $requestParams['timestamp'], $requestParams['nonce'], $requestParams['sign'])) {return false;}$incomingSignature = $requestParams['sign'];// 从参数中移除签名,因为签名本身不参与签名计算unset($requestParams['sign']);// 2. 验证时间戳有效性(防止重放攻击)$currentTime = time();$requestTime = $requestParams['timestamp'];if (abs($currentTime - $requestTime) > $this->timestampTolerance) {return false; // 请求已过期}// 3. (可选但推荐)检查Nonce唯一性,防止重复请求// 通常需要借助缓存(如Redis)记录近期使用过的nonce// 此处简化示例,实际生产环境需要实现// if ($this->isNonceUsed($requestParams['nonce'])) { return false; }// 4. 根据收到的参数重新计算签名$generator = new SignatureGenerator($this->appSecret);$calculatedSignature = $generator->generateSignature($requestParams['appid'],$requestParams['phone'],$requestParams['timestamp'],$requestParams['nonce']);// 5. 使用hash_equals防止时序攻击,比较计算出的签名和收到的签名是否一致return hash_equals($calculatedSignature, $incomingSignature);}/*** 设置时间戳容差(秒)*/public function setTimestampTolerance($tolerance){$this->timestampTolerance = $tolerance;}// 示例方法:检查nonce是否已被使用(需结合缓存系统如Redis实现)// private function isNonceUsed($nonce) {// // 伪代码:检查这个nonce在缓存中是否存在,若存在则返回true(已使用)// // 若不存在,则将其存入缓存并设置过期时间(例如5分钟),然后返回false// }
}?>
3. 完整使用示例
下面的代码演示了如何在实际的API调用和接收中使用上述类。
<?php// ==================== 调用方(客户端) ====================
// 假设双方共享的密钥,需要严格保密
$sharedAppSecret = "YourSuperSecretKey123!";// 1. 实例化签名生成器
$signatureGenerator = new SignatureGenerator($sharedAppSecret);// 2. 业务参数
$appId = "100001"; // 平台ID
$userPhone = "13800138000"; // 用户手机号// 3. 生成带签名的请求参数数组
$signedRequestData = $signatureGenerator->generateSignedRequest($appId, $userPhone);echo "生成的请求参数(可用于HTTP GET/POST请求):\n";
print_r($signedRequestData);
// 输出类似:
// Array
// (
// [appid] => 100001
// [phone] => 13800138000
// [timestamp] => 1728835200
// [nonce] => 5a8f1e9d4c7b2a61
// [sign] => abc123...Base64编码的签名...
// )// 可以将 $signedRequestData 作为参数发起HTTP请求
// 例如: http://api.example.com/service?appid=100001&phone=13800138000×tamp=1728835200&nonce=5a8f1e9d4c7b2a61&sign=abc123...// ==================== 服务端 ====================
// 1. 实例化签名验证器(使用相同的密钥)
$signatureVerifier = new SignatureVerifier($sharedAppSecret);// 2. 模拟接收到的请求数据(通常是 $_GET 或 $_POST)
$receivedRequestData = $signedRequestData; // 这里直接用上面生成的数据模拟// 3. 进行签名验证
$isValid = $signatureVerifier->verifySignature($receivedRequestData);if ($isValid) {echo "✓ 签名验证成功,请求合法,处理业务逻辑...\n";// ... 这里执行您的业务代码 ...
} else {echo "✗ 签名验证失败,请求可能被篡改或已过期,拒绝处理。\n";http_response_code(401); // 返回未授权状态码
}?>
关键要点说明
-
安全性
- 密钥管理:
AppSecret
是核心机密,应妥善保管,切勿在客户端或网络中明文传输。建议使用环境变量或安全的配置中心存储。 - 算法选择:使用 SHA-256 等强哈希算法,比 MD5 更安全。
- 防重放攻击:通过 时间戳 和 随机数 (Nonce) 机制有效防止请求被重复使用。
- 签名比较:使用
hash_equals
进行比较,以避免时序攻击。
- 密钥管理:
-
参数处理
- 参数排序:必须严格按照字典序排序,确保服务端和客户端计算签名时参数的顺序一致。
- 参数编码:注意参数值的原始格式,通常使用原始值进行签名计算,而不是URL编码后的值。如果进行HTTP请求,在生成签名后需要对整个请求URL(包括签名参数)进行编码。