詳解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簽名生成訂單功能【支付寶示例】