详解PHP接口签名验证
工作中,我们时刻都会和接口打交道,有的是调取他人的接口,有的是为他人提供接口,在这过程中肯定都离不开签名验证。本文将详细介绍PHP接口签名验证。
目录
概览
常用验证
单向散列加密
对称加密
非对称加密
密钥安全管理
接口调试工具
在线接口文档
扩展
小结
概览
在设计签名验证的时候,一定要满足以下几点:
可变性:每次的签名必须是不一样的。
时效性:每次请求的时效性,过期作废。
唯一性:每次的签名是唯一的。
完整性:能够对传入数据进行验证,防止篡改。
下面主要分享一些工作中常用的加解密的方法。
常用验证
举例:/api/login?username=xxx&password=xxx&sign=xxx
发送方和接收方约定一个加密的盐值,进行生成签名。
示例代码:
上面使用到了 MD5 方法,MD5 属于单向散列加密。
//创建签名
private function _createSign()
{
$strSalt = '1scv6zfS1wLaWN';
$stral = '';
if ($this->params) {
$params = $this->params;
ksort($params);
$strval = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
}
return md5(md5($strSalt).md5($stral));
}
//验证签名
if ($_GET['sign'] != $this->_createSign()) {
echo 'Invalid Sign.';
}
单向散列加密
定义
把任意长的输入串变化成固定长的输出串,并且由输出串难以得到输入串,这种方法称为单项散列加密。
常用算法
MD5
SHA
MAC
CRC
优点
以 MD5 为例。
方便存储:加密后都是固定大小(32位)的字符串,能够分配固定大小的空间存储。
损耗低:加密/加密对于性能的损耗微乎其微。
文件加密:只需要32位字符串就能对一个巨大的文件验证其完整性。
不可逆:大多数的情况下不可逆,具有良好的安全性。
缺点
存在暴力解的可能性,最好通过加盐值的方式提高安全性。
应用场景
用于敏感数据,比如用户密码,请求参数,文件加密等。
推荐密码的存储方式
password_hash()使用足够强度的单向散列算法创建密码的哈希(hash)。
//密码加密
$password = '123456';
$strPwdHash = password_hash($password, PASSWORD_DEFAULT);
//密码验证
if (password_verify($password, $strPwdHash)) {
//Success
} else {
//Fail
}
PHP 手册地址:
http://php.net/manual/zh/function.password-hash.php
对称加密
定义
同一个密钥可以同时用作数据的加密和解密,这种方法称为对称加密。
常用算法
DES
AES
AES 是 DES 的升级版,密钥长度更长,选择更多,也更灵活,安全性更高,速度更快。
优点
算法公开、计算量小、加密速度快、加密效率高。
缺点
发送方和接收方必须商定好密钥,然后使双方都能保存好密钥,密钥管理成为双方的负担。
应用场景
相对大一点的数据量或关键数据的加密。
AES
AES 加密类库在网上很容易找得到,请注意类库中的mcrypt_encrypt和mcrypt_decrypt方法
在 PHP7.2 版本中已经被弃用了,在新版本中使用openssl_encrypt和openssl_decrypt两个方法。
示例代码(类库):
class Aes
{
/**
加解密方法
*/
protected $method;
/**
加解密的密钥
*/
protected $secret_key;
/**
加解密的向量
*/
protected $iv;
/**
* var int $options
*/
protected $options;
/**
构造函数
密钥
加密方式
向量
* @param int $options
*/
public function __construct($key = '', $method = 'AES-128-CBC', $iv = '', $options = OPENSSL_RAW_DATA)
{
$this->secret_key = isset($key) ? $key : 'CWq3g0hgl7Ao2OKI';
$this->method = in_array($method, openssl_get_cipher_methods()) ? $method : 'AES-128-CBC';
$this->iv = $iv;
$this->options = in_array($options, [OPENSSL_RAW_DATA, OPENSSL_ZERO_PADDING]) ? $options : OPENSSL_RAW_DATA;
}
/**
加密
加密的数据
* @return string
*/
public function encrypt($data = '')
{
return base64_encode(openssl_encrypt($data, $this->method, $this->secret_key, $this->options, $this->iv));
}
/**
解密
解密的数据
* @return string
*/
public function decrypt($data = '')
{
return openssl_decrypt(base64_decode($data), $this->method, $this->secret_key, $this->options, $this->iv);
}
}
示例代码:
$aes = new Aes('HFu8Z5SjAT7CudQc');
$encrypted = $aes->encrypt('锄禾日当午');
echo '加密前:锄禾日当午<br>加密后:', $encrypted, '<hr>';
$decrypted = $aes->decrypt($encrypted);
echo '加密后:', $encrypted, '<br>解密后:', $decrypted;
运行结果:
非对称加密
定义
需要两个密钥来进行加密和解密,这两个秘钥分别是公钥(public key)和私钥(private key),这种方法称为非对称加密。
常用算法
RSA
优点
与对称加密相比,安全性更好,加解密需要不同的密钥,公钥和私钥都可进行相互的加解密。
缺点
加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
应用场景
适合于对安全性要求很高的场景,适合加密少量数据,比如支付数据、登录数据等。
算法名称 | 标准名称 | 备注 |
RSA2 | SHA256WithRSA | 强制要求RSA密钥的长度至少为2048 |
RSA | SHA1WithRSA | 对RSA密钥的长度不限制,推荐使用2048位以上 |
RSA2 比 RSA 有更强的安全能力。
蚂蚁金服,新浪微博 都在使用 RSA2 算法。
创建公钥和私钥:获取代码中的公钥和私钥 如果参数是2048的话 传输数据最多是255个字符 超出的话方法返回null
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
执行上面命令,会生成private_key.pem和public_key.pem两个文件。
示例代码(类库):
<?php
class Rsa2
{
private static $PUBLIC_KEY ='-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsoQc7PmSTUIJuvgqS6wL
Fq1FRvq1o/5ZdDhMIfvmWHUauUKC6wV5YVJiaMUCsSG+c7td3ZZ1SgsbNRNmNJwV
3kmtQ8gCCNKyn22GDxaDWRFqnPZjNe97FEcNDzuXntBN2KV10rB6CtM/r9yFdCRM
upasQjIfkE4tT+7Ie08wg3n2R/Q/FPmNunF83MFzvCL+hK2x4KUsxfV6EyFpnuIJ
tuDqz37J4U2JXoFl0jdI9peSw2Qm+8BIE+wdkGJ4Iavyl3BovjrgOIw4HwJI/jTP
iQ1aB70SCma5jmDWre0frFzcYcokJdC/DAcxVHHPImOoa+opZ0jcUe7xQNfFiUDd
CwIDAQAB
-----END PUBLIC KEY-----';
private static $PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAsoQc7PmSTUIJuvgqS6wLFq1FRvq1o/5ZdDhMIfvmWHUauUKC
6wV5YVJiaMUCsSG+c7td3ZZ1SgsbNRNmNJwV3kmtQ8gCCNKyn22GDxaDWRFqnPZj
Ne97FEcNDzuXntBN2KV10rB6CtM/r9yFdCRMupasQjIfkE4tT+7Ie08wg3n2R/Q/
FPmNunF83MFzvCL+hK2x4KUsxfV6EyFpnuIJtuDqz37J4U2JXoFl0jdI9peSw2Qm
+8BIE+wdkGJ4Iavyl3BovjrgOIw4HwJI/jTPiQ1aB70SCma5jmDWre0frFzcYcok
JdC/DAcxVHHPImOoa+opZ0jcUe7xQNfFiUDdCwIDAQABAoIBABAyQFWXxxhPV4ap
IV1APLlP+ysXcAdy+ja7LN0gd6Oq7EPGrw4Xrw2PZfJPfZEImLjzVO7Xychv/YXx
PqKjTrCeSqQNr3GRfDdOvoz74+re06duC05HbglG8P5iz7zO9WEkke4NqOT2iZ6x
CUKiUHvVU8eluItlLGNGLNZTNvmWnl8z+WRubA/FPr4atDD/1iLTmOpZSHkIBr9j
mFgjqJmFfU9sXwb9lnxZpamtPXVAI2Fw1hVntswERXyAle3vok7AQp4xWVCjMsuq
cOl+ws98cGbrVoxeLwBAWP9eCa+omaQRw2TycFVW1I59tdfWOyGtye6xs1aIw+rj
/kOw+MECgYEA2ftRxdy26i9VRdbNfrKJFk5Wtsmm8KGB7ia95USkoEw7Xfhyt+G3
X8k/pt5EBgOPjRGg8nsyANIWBjZyEMOuH3vxCGlj+PNf8SyJBBPLBMdwZ1rh3TgQ
hNisFKYTqzZZEgSPZvt9Jf+H6rTL4f1C5BH+jMuPOqx8+poXre8VNrECgYEA0aaw
Zb3ojF85jkdrSmI0mEZ0GLahHdGX+tsm/o03RCrAHy9SZsGl8Vti5aUJQaL3zoRf
xT1mZsjO4SfoNHRmAVS8MihotLmnx+Zzj6kSNOrOHuBcODFIXqA1AWssgQu+rPjw
XUvUcmVPk4wtGhzxy9i34JzyShBjIiagApr6dnsCgYBUWKIArPH+Vghp+L8VSmIv
RmrZWdgF/oi3LTiHkXMeL8E9EFjbfDSnlMjTeefaZ+BRFsQvb6oelSw+hSzH/d/P
gPb4aUds2keCm9f8wufo1n9RDuKc5gfGT8zuRIER2/rFNQyELcX0QRdHo2PM82HB
vJDBzDaE3p+RnLSS8hNEUQKBgQDRhsAgzq2NLNY+5g3KZ1+dGDyLPmrDfGdh5IiS
OtwTGo6sHAVEgSCxmN28GTapDJCRKybz3ytqaiKoT9P4KKSRi/gSQOR0Sl/unti7
qQNzM4AQx7Yiys5DE+YlptDxn6VPBfRjqZeHRM1E7wFcabUWP1918iUL9Fi/Fs8w
C2dp5QKBgFjcKNQKJfCtUJ6Maz/Ub0R0of89HwDwgSH4vzkdAgXZI7m0aQjXsD69
77YbDBTHd4IK5B3qYuLzWxuq6402aFoPBp2ux1+JUDIFn1XhTMtHwZ2N86oaKtzO
drZZZifNEx3paZdcpqcg4UegGpr4nLWmGFaVC3L0JxXNW5uShE3h
-----END RSA PRIVATE KEY-----';
/**
获取私钥
* @return bool|resource
*/
private static function getPrivateKey()
{
$privateKey = self::$PRIVATE_KEY;
return openssl_pkey_get_private($privateKey);
}
/**
获取公钥
* @return bool|resource
*/
private static function getPublicKey()
{
$publicKey = self::$PUBLIC_KEY;
return openssl_pkey_get_public($publicKey);
}
/**
私钥加密
* @param string $data
* @return null|string
*/
public static function privateEncrypt($data = '')
{
if (!is_string($data)) {
return null;
}
return openssl_private_encrypt($data,$encrypted,self::getPrivateKey()) ? base64_encode($encrypted) : null;
}
/**
公钥加密
* @param string $data
* @return null|string
*/
public static function publicEncrypt($data = '')
{
if (!is_string($data)) {
return null;
}
return openssl_public_encrypt($data,$encrypted,self::getPublicKey()) ? base64_encode($encrypted) : null;
}
/**
私钥解密
* @param string $encrypted
* @return null
*/
public static function privateDecrypt($encrypted = '')
{
if (!is_string($encrypted)) {
return null;
}
return (openssl_private_decrypt(base64_decode($encrypted), $decrypted, self::getPrivateKey())) ? $decrypted : null;
}
/**
公钥解密
* @param string $encrypted
* @return null
*/
public static function publicDecrypt($encrypted = '')
{
if (!is_string($encrypted)) {
return null;
}
return (openssl_public_decrypt(base64_decode($encrypted), $decrypted, self::getPublicKey())) ? $decrypted : null;
}
/**
创建签名
数据
* @return null|string
*/
public function createSign($data = '')
{
if (!is_string($data)) {
return null;
}
return openssl_sign($data, $sign, self::getPrivateKey(), OPENSSL_ALGO_SHA256) ? base64_encode($sign) : null;
}
/**
验证签名
数据
签名
* @return bool
*/
public function verifySign($data = '', $sign = '')
{
if (!is_string($sign) || !is_string($sign)) {
return false;
}
return (bool)openssl_verify($data, base64_decode($sign), self::getPublicKey(), OPENSSL_ALGO_SHA256);
}
}
$rsa2 = new Rsa2();
$data=[
'a'=>1,
];
for($i=0;$i<32;$i++){
$data[]=$i;
}
$str = json_encode($data);
echo strlen($str);
$privateEncrypt = $rsa2->privateEncrypt($str);
echo '私钥加密后:'.$privateEncrypt."\n";
$publicDecrypt = $rsa2->publicDecrypt($privateEncrypt);
echo '公钥解密后:'.$publicDecrypt."\n";
$publicEncrypt = $rsa2->publicEncrypt($str);
echo '公钥加密后:'.$publicEncrypt."\n";
$privateDecrypt = $rsa2->privateDecrypt($publicEncrypt);
echo '私钥解密后:'.$privateDecrypt."\n";
$sign = $rsa2->createSign($str);
echo '生成签名:'.$privateEncrypt."\n";
$status = $rsa2->verifySign($str, $sign);
echo '验证签名:'.($status ? '成功' : '失败') ;
运行结果:
部分数据截图如下:
D:\BtSoft\php\56\php.exe C:\Users\Administrator\AppData\Roaming\JetBrains\PhpStorm2021.3\scratches\scratch_5.php
221私钥加密后:BIqhU3obn+nBe2MTAxpAp4X6/RdGe9GTaJPZbZc8yp7VAgxjdv7MTq9iLJ8tzsxmQSBRg0gsiFTAQvN7ojL1MJHK1oeBlcflcZr5lFQdiMM9nLe1n+cBulG5Gen5d6YNgsFVHz/QgTfCm9wTYxKWdbYXfvs0IKxc9XIqVV5zHKzq6fUsvgcnOvwRiHWKW02vRwmH3BIXWeJ8MI3f0gJApgeadP5i/wBqdvfGwH5FzMQpHvpM64QTJKIBnw82+XFBkIlx4HnrXh5PhXxcbRxY6PTlnnYskWe1SupAmx/IP/snEMUB4KlMwZQjncxRgyyk/T9Wb12iEh171FiX48285A==
公钥解密后:{"a":1,"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"28":1,"29":1,"30":1,"31":1}
公钥加密后:IADbrpxFXFVa7lDg5g5FPqFGPgB2GtIGYylXy1M76EQSOAYYTUvGnvwf0kAyKsglsBVeC01HFa+r9YcAQ7jqCLye55cBpdbp067/ZhpE5a8tZcVyO+1twNv9UA5GOSqi/5w9TkPVIULB5paTWmxd7g4whFNtd0T9+eZTaFgcuI54SNecSUxO+BIb1ZbNkMVrNbyPYBZ3eIycbUYHQ3ncrP91WrqSlRCvedVgN+JuU5ekuKSiTiAV+mgSkwSN0I6Q0syUcRVqSAL9tevBZIxuYvMzKaclURg2h9WGnOtefJmRcjx53aqIKSUdyF0o8TJzWflVJMC+08hwtAyBK7A6ww==
私钥解密后:{"a":1,"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"28":1,"29":1,"30":1,"31":1}
生成签名:BIqhU3obn+nBe2MTAxpAp4X6/RdGe9GTaJPZbZc8yp7VAgxjdv7MTq9iLJ8tzsxmQSBRg0gsiFTAQvN7ojL1MJHK1oeBlcflcZr5lFQdiMM9nLe1n+cBulG5Gen5d6YNgsFVHz/QgTfCm9wTYxKWdbYXfvs0IKxc9XIqVV5zHKzq6fUsvgcnOvwRiHWKW02vRwmH3BIXWeJ8MI3f0gJApgeadP5i/wBqdvfGwH5FzMQpHvpM64QTJKIBnw82+XFBkIlx4HnrXh5PhXxcbRxY6PTlnnYskWe1SupAmx/IP/snEMUB4KlMwZQjncxRgyyk/T9Wb12iEh171FiX48285A==
验证签名:成功
进程已结束,退出代码0
JS-RSA
JSEncrypt:用于执行OpenSSL RSA加密、解密和密钥生成的Javascript库。
Git源:https://github.com/travist/jsencrypt
应用场景:
我们在做 WEB 的登录功能时一般是通过 Form 提交或 Ajax 方式提交到服务器进行验证的。
为了防止抓包,登录密码肯定要先进行一次加密(RSA),再提交到服务器进行验证。
一些大公司都在使用,比如淘宝、京东、新浪 等。
示例代码就不提供了,Git上提供的代码是非常完善的。
密钥安全管理
这些加密技术,能够达到安全加密效果的前提是密钥的保密性。
实际工作中,不同环境的密钥都应该不同(开发环境、预发布环境、正式环境)。
那么,应该如何安全保存密钥呢?
环境变量
将密钥设置到环境变量中,每次从环境变量中加载。
配置中心
将密钥存放到配置中心,统一进行管理。
密钥过期策略
设置密钥有效期,比如一个月进行重置一次。
在这里希望大佬提供新的思路
接口调试工具
Postman
一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome插件。
这个不用多介绍,大家肯定都使用过。
SocketLog
Git源:https://github.com/luofei614/SocketLog
解决的痛点:
正在运行的API有Bug,不能在文件中使用var_dump进行调试,因为会影响到client的调用。将日志写到文件中,查看也不是很方便。
我们在二次开发一个新系统的时候,想查看执行了哪些Sql语句及程序的warning,notice等错误信息。
SocketLog,可以解决以上问题,它通过WebSocket将调试日志输出到浏览器的console中。
使用方法
安装、配置Chrome插件
SocketLog服务端安装
PHP中用SocketLog调试
配置日志类型和相关参数
在线接口文档
接口开发完毕,需要给请求方提供接口文档,文档的编写现在大部分都使用Markdown格式。
也有一些开源的系统,可以下载并安装到自己的服务器上。
也有一些在线的系统,可以在线使用同时也支持离线导出。
根据自己的情况,选择适合自己的文档平台吧。
常用的接口文档平台:
eolinker
Apizza
Yapi
RAP2
DOClever
扩展
一、在 HTTP 和 RPC 的选择上,可能会有一些疑问,RPC框架配置比较复杂,明明用HTTP能实现为什么要选择RPC?
下面简单的介绍下 HTTP 与 RPC 的区别。
传输协议:
HTTP 基于 HTTP 协议。
RPC 即可以 HTTP 协议,也可以 TCP 协议。
HTTP 也是 RPC 实现的一种方式。
性能消耗:
HTTP 大部分基于 JSON 实现的,序列化需要时间和性能。
RPC 可以基于二进制进行传输,消耗性能少一点。
推荐一个像 JSON ,但比 JSON 传输更快占用更少的新型序列化类库MessagePack。
官网地址:https://msgpack.org/
还有一些服务治理、负载均衡配置的区别。
使用场景:
比如浏览器接口、APP接口、第三方接口,推荐使用 HTTP。
比如集团内部的服务调用,推荐使用 RPC。
RPC 比 HTTP 性能消耗低,传输效率高,服务治理也方便。
推荐使用的 RPC 框架:Thrift。
二、动态令牌
简单介绍下几种动态令牌,感兴趣的可以深入了解下。
OTP:One-Time Password 一次性密码。
HOTP:HMAC-based One-Time Password 基于HMAC算法加密的一次性密码。
TOTP:Time-based One-Time Password 基于时间戳算法的一次性密码。
使用场景:
公司VPN登录双因素验证
服务器登录动态密码验证
网银、网络游戏的实体动态口令牌
银行转账动态密码
小结
本文讲了设计签名验证需要满足的一些条件:可变性、时效性、唯一性、完整性。
还讲了一些加密方法:单向散列加密、对称加密、非对称加密,同时分析了各种加密方法的优缺点,大家可以根据自己的业务特点进行自由选择。
提供了 Aes、Rsa 相关代码示例。
分享了可以编写接口文档的在线系统。
分享了开发过程中使用的接口调试工具。
扩展中分析了 HTTP 和 RPC 的区别,动态令牌的介绍等。
以上就是详解PHP接口签名验证的详细内容,更多关于PHP接口签名验证的资料请关注脚本之家其它相关文章!您可能感兴趣的文章:
PHP开发API接口签名生成及验证操作示例
浅谈PHP SHA1withRSA加密生成签名及验签
php实现往pdf中加数字签名操作示例【附源码下载】
PHP实现的MD5结合RSA签名算法实例
用PHP去掉文件头的Unicode签名(BOM)方法
PHP实现RSA签名生成订单功能【支付宝示例】