1

前言

secret_id:     密钥的Id
secret_key:    密钥的Key
SHA1:          签名方式
hash_hmac:     php hash函数
hash_hmac_file:生成密钥

1.生成secret_key

 hash_hmac_file('SHA1','signature.txt','secret');

2.生成签名

base64_encode(hash_hmac('SHA1', $init, $secret_key, true).$init);

3.签名比对

base64_decode($initSign);
base64_encode(hash_hmac('SHA1', $init, $secret_key, true).$init);

4.网站验证(sha1.html)

访问 sha1.html

5.代码

5.1 HashHmacSha1.php

<?php
class HashHmacSha1
{
    /** code
     * @var int
     */
    protected $code = 200;

    /** message
     * @var string
     */
    protected $message = 'success';

    /** 获取处理code
     * @return int
     */
    public function getCode()
    {
        return $this->code;
    }

    /** 获取处理message
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }

    /** 产生随机字符串,不长于32位
     * @param int $length
     * @return string
     */
    public function getNonceStr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789!@#%^&*;:-=_+,.";
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }

    /** 生成sha1的secret
     * @other 绑定生成的 内容,secret_key,secret_id
     * @return string
     */
    public function signSha1Create()
    {
        $nonce = $this->getNonceStr();
        $rand  = rand();
        $time  = time();
        $content = "The signature : nonce={$nonce}; rand={$rand}; time={$time}, by sok yo!";
        //生成签名内容
        file_put_contents('signature.txt', $content);
        $signature = hash_hmac_file('SHA1','signature.txt','secret');
        unlink('signature.txt');
        return $signature;
    }

    /** sha1数据加密
     * @param $secret_id
     * @param $secret_key
     * @return string
     */
    public function signSha1Encrypt($secret_id,$secret_key)
    {
        $time = time();
        // 业务逻辑,向参数列表填入参数(可抽离)
        $arg_list = array(
            "secretId" => $secret_id,
            "currentTimeStamp" => $time,
            "expireTime" => $time+86400,
            "random" =>rand(),
        );
        $init = http_build_query($arg_list);
        // 计算签名
        // raw_output = false ; 53e55273e57c3cf6b7c16e5840479566be3aa0d2,16进制小写字符串格式(40个字符)
        // raw_output = true  ; �� Ej���۔����̠��,原始二进制数据(20个字符)
        // base64_encode 设计此种编码是为了使二进制数据可以通过非纯 8-bit 的传输层传输(防止特殊字符在传输过程中被转义)。
        // base64_encode 数据要比原始数据多占用 33% 左右的空间。
        $signature = base64_encode(hash_hmac('SHA1', $init, $secret_key, true).$init);
        // JS CryptoJS
        return $signature ;
    }

    /** sha1数据解密
     * @param $initSign
     * @param $secretId
     * @param $secretKey
     * @return bool
     */
    public function signSha1Decrypt($initSign,$secretId,$secretKey)
    {
        //先做 base64 解码,拿到数据
        $sign     = base64_decode($initSign);
        //前 5 个单元是 sha1 ,这里的代码单元默认为:编码+16进制(0041),5个单元 = 5 * 4(字节) = 20(字节)
        $signStr  = substr($sign,20);
        //数组形式
        parse_str($signStr,$signArr);
        //逻辑判断(可抽离)
        $mustKey = ['secretId','currentTimeStamp','expireTime','random'];
        foreach ($mustKey as $k) {
            if(!isset($signArr[$k])) {
                $this->code = 400;
                $this->message = 'miss '.$k;
                return false;
            }
        }
        //secretId
        if((string)$signArr['secretId'] !== (string)$secretId) {
            $this->code = 400;
            $this->message = 'error secretId';
            return false;
        }
        //时间
        if($signArr['currentTimeStamp'] > $signArr['expireTime']) {
            $this->code = 400;
            $this->message = 'error currentTimeStamp';
            return false;
        }
        //过期
        if($signArr['expireTime'] < time()) {
            $this->code = 400;
            $this->message = 'expireTime expired';
            return false;
        }
        // 计算签名
        $signature = base64_encode(hash_hmac('SHA1', $signStr, $secretKey, true).$signStr);
        if($signature === $initSign) {
            $this->code = 200;
            $this->message = 'success';
            return true;
        }else{
            $this->code = 400;
            $this->message = 'error signature';
            return false;
        }
    }
}

5.2 sha1.html


<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>hash_hmac sha1 签名校验工具</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://video.qcloud.com/signature/lib/bootstrap.min.css">
    <style>
        .form-value{
            width:74% !important;
        }
        .form-name{
            width:25% !important;
        }
        .form-block{
            width: 100% !important;
        }
        .text-danger{
            color:red;
        }
        .form-inline{
            margin-bottom: 10px;
        }
        .control-label{
            text-align: left !important;
            padding:0 0 0 15px;
        }
        .non-required-form .control-label{
            font-weight: normal;
        }
        .non-required-form{
            display: none;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="text-center">
        <h2>hash_hmac sha1 签名校验工具</h2>
    </div>

    <form class="form-horizontal" style="padding:0 12%" >
        <!-- 签名 -->

        <div class="form-group form-inline">
            <label class="col-sm-3 control-label">签名结果串(必填)<span class="text-danger">*<span></label>
            <div class="col-sm-7">
                <input type="text" class="form-control form-block" name="signature" placeholder="">
            </div>
            <div class="col-sm-2">
                <button type="button" name="button" class="btn btn-success" id="decodeSig" style="width: 100%;">解析签名</button>
            </div>
        </div>



        <div class="form-group form-inline">
            <label class="col-sm-3 control-label">中间结果(Query String)</label>
            <div class="col-sm-9">
                <input type="text" class="form-control form-block" name="queryString" placeholder="" readonly>
            </div>
        </div>



        <!-- 解析结果 -->
        <div class="form-group form-inline required-form">
            <label class="col-sm-3 control-label">Secret ID</label>
            <div class="col-sm-9">
                <input type="text" class="form-control form-name" value="secretId" readonly>
                <input type="text" class="form-control form-value" name="secretId" readonly>
            </div>
        </div>
        <div class="form-group form-inline required-form">
            <label class="col-sm-3 control-label">当前时间戳</label>
            <div class="col-sm-9">
                <input type="text" class="form-control form-name" value="currentTimeStamp" readonly>
                <input type="text" class="form-control form-value" name="currentTimeStamp" readonly>
                <span class="currentTimeStampHuman"></span>
            </div>
        </div>
        <div class="form-group form-inline required-form">
            <label class="col-sm-3 control-label">签名失效时间戳</label>
            <div class="col-sm-9">
                <input type="text" class="form-control form-name" value="expireTime" readonly>
                <input type="text" class="form-control form-value" name="expireTime" readonly>
                <span class="expireTimeHuman"></span>
            </div>
        </div>
        <div class="form-group form-inline required-form">
            <label class="col-sm-3 control-label">随机数</label>
            <div class="col-sm-9">
                <input type="text" class="form-control form-name" value="random" readonly>
                <input type="text" class="form-control form-value" name="random" readonly>
            </div>
        </div>

        <!-- Secret Key -->
        <div class="form-group form-inline">
            <label class="col-sm-3 control-label">Secret Key(必填)<span class="text-danger">*<span></label>
            <div class="col-sm-7">
                <input type="text" class="form-control form-block" name="secretKey" placeholder="">
            </div>
            <div class="col-sm-2">
                <button type="button" name="button" class="btn btn-success" id="checkSecretkey" style="width: 100%;">校验</button>
            </div>

        </div>

        <div class="form-group form-inline">
            <label class="col-sm-3 control-label">签名校验结果</label>
            <div class="col-sm-9">
                <input type="text" class="form-control form-block" name="decodeResult" placeholder="" readonly style="color: red;">
            </div>
        </div>


    </form>

</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://video.qcloud.com/signature/lib/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://video.qcloud.com/signature/lib/bootstrap.min.js"></script>
<script src="https://video.qcloud.com/signature/lib/uploaderh5V3.js" charset="utf-8"></script>
<script>
    var signature,secret_key;
    $("#decodeSig").click(function(){
        signature = $("input[name=signature]").val();
        if(!signature) {
            alert('签名结果串 不能为空');
        } else {
            var signature = $("input[name=signature]").val();
            decodeSignature(signature);
        }
    });

    $("#checkSecretkey").click(function(){
        signature = $("input[name=signature]").val();
        secret_key = $("input[name=secretKey]").val();
        var checkResult;
        if(!secret_key) {
            $("input[name=decodeResult]").val('Secret Key 不能为空');
        } else {
            checkResult = check(signature, secret_key);
            if(checkResult){
                if(checkResult == 'expired') {
                    $("input[name=decodeResult]").val('签名已过期');
                } else if(checkResult == 'notInt') {
                    $("input[name=decodeResult]").val('时间戳和随机数必须为纯数字');
                } else if (checkResult == 'wrongExpireTime') {
                    $("input[name=decodeResult]").val('expireTime 不可小于 currentTimeStamp');
                } else {
                    $("input[name=decodeResult]").val('签名校验通过');
                }
            }else {
                $("input[name=decodeResult]").val('签名校验失败');
            }
        }
    });

    var decodeSignature = function(signature){
        // var sha = CryptoJS.HmacSHA1(argStr, secret_key);
        // sha.concat(CryptoJS.enc.Utf8.parse(argStr))
        // var signature = CryptoJS.enc.Base64.stringify(sha);
        $(".non-required-form").hide();
        $(".required-form .form-value").val("");
        $("input[name=queryString]").val("");

        var queryString = getOriginalStr(signature);
        //将解析的结果填入input
        $("input[name=queryString]").val(queryString);
        var arr = queryString.split("&");
        arr.map(function(item){
            var name = item.split("=")[0].replace(/\./,"_");
            var value = decodeURIComponent(item.split("=")[1]);
            if(!name || !value) {
                alert('签名结果串格式不合法,请检查输入的签名结果串');
                return;
            }
            var input = $("input[name='"+escape(name)+"']");
            if(input.length>=1){
                $(input).val(value);
                $(input).closest(".non-required-form").show();

                if (name === 'currentTimeStamp') {
                    $('.currentTimeStampHuman').text(new Date(value * 1000))
                }
                if (name === 'expireTime') {
                    $('.expireTimeHuman').text(new Date(value * 1000))
                }
            }else{
                var customInput = '<div class="form-group form-inline non-required-form" style="display:block">'
                    + '<label class="col-sm-3 control-label"></label>'
                    + '<div class="col-sm-9">'
                    + '<input type="text" class="form-control form-name" disabled>'
                    + '<input type="text" class="form-control form-value" disabled>'
                    + '</div></div>';
                $(".required-form:last").after(customInput);
                $(".required-form:last").next().find('.form-name').val(name);
                $(".required-form:last").next().find('.form-value').val(value);
            }
        })

    }

    function getOriginalStr(signature) {

        var buffer = CryptoJS.enc.Base64.parse(signature);
        var argBuffer = CryptoJS.lib.WordArray.create(buffer.words.slice(5), buffer.sigBytes-20);
        return argBuffer.toString(CryptoJS.enc.Utf8);

    }

    function check(signature, secret_key) {
        // 先做 base64 解码,拿到整个数据的 buffer
        var buffer = CryptoJS.enc.Base64.parse(signature);
        // 前 5 个单元是 sha1
        var shaBuffer = CryptoJS.lib.WordArray.create(buffer.words.slice(0, 5), 20);
        // 后 5 个单元是 querystring,并且是 UTF8 编码的
        var argBuffer = CryptoJS.lib.WordArray.create(buffer.words.slice(5), buffer.sigBytes-20);
        // 把 querystring 从 UTF8 编码的 buffer 转成 string
        var argStr = argBuffer.toString(CryptoJS.enc.Utf8);
        // 再次用 sk + querystring 计算出 sha
        var sha = CryptoJS.HmacSHA1(argStr, secret_key);
        // 对比两个 sha 是不是一样的

        if(!$("input[name=expireTime]").val()) {
            decodeSignature(signature);
        }
        if(+$("input[name=currentTimeStamp]").val() > +$("input[name=expireTime]").val()) {
            return 'wrongExpireTime';
        }
        // 校验时间是否过期
        if(+$("input[name=expireTime]").val() < Math.floor((+new Date())/1000)) {
            return 'expired';
        }
        if(isNaN($("input[name=random]").val()) || isNaN($("input[name=currentTimeStamp]").val()) || isNaN($("input[name=expireTime]").val())) {
            return 'notInt';
        }


        return sha.toString(CryptoJS.enc.Base64) === shaBuffer.toString(CryptoJS.enc.Base64);
    }

</script>
<script>
    var _mtac = {};
    (function() {
        var mta = document.createElement("script");
        mta.src = "https://pingjs.qq.com/h5/stats.js?v2.0.2";
        mta.setAttribute("name", "MTAH5");
        mta.setAttribute("sid", "500428027");
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(mta, s);
    })();
</script>

</body>
</html>

6.测试

//测试
$model = new HashHmacSha1();

//随机的 secret_id
$secretId  = 'abc1234Def453';

//生成 secret_key
$secretKey = $model->signSha1Create();
//'fe08bd195744e08e289174167b1d53cd638e6f7b';

//生成签名
$sign = $model->signSha1Encrypt($secretId,$secretKey);
//'ZQlqVb8DKt14kDD6TCxWQMQARVxzZWNyZXRJZD1hYmMxMjM0RGVmNDUzJmN1cnJlbnRUaW1lU3RhbXA9MTU2Njg4NzA2MyZleHBpcmVUaW1lPTE1NjY5NzM0NjMmcmFuZG9tPTk4MTEzNjMwNA==';

//比对
$res = $model->signSha1Decrypt($sign,$secretId,$secretKey);
var_dump($res);

7.sha1.html 运行参照

图片描述

感谢阅读!


风尘斯文
306 声望13 粉丝

宠辱不惊,蓄势以待。莫问前路,风尘斯文。


« 上一篇
Nginx配置注解