• openssl+前端jsrsa签名+后端nodejs验签


    内容如标题所示,总体分为三个部分:

    一、win10下安装openssl,然后通过openssl工具生成RSA的公钥和私钥

    (1)win10下安装openssl需要的工具有:VS2013,Perl,nasm,openssl源码

    其中,VS2013的安装、注册和激活请自行百度,ActivePerl、nasm和openssl源码也请自行下载安装,ActivePerl好说(执行perl example.pl,若提示:Hello from ActivePerl! 则说明Perl安装成功),nasm我选的是nasm-2.11.02-installer.exe,openssl的部分版本在后面配置的时候会报错,我最后选的是openssl-1.0.2j.tar.gz (SHA256) (PGP sign) (SHA1)。

    (2)设置环境变量

    打开我的电脑->属性->高级系统设置->环境变量,找到Path系统变量,点击编辑,添加C:Program Files (x86)Microsoft Visual Studio 12.0VCin;C:/Perl64/bin;C:/nasm;c:/windows/system32

    我是按默认指示全都放在C盘,可以根据情况自己修改安装路径,安装Perl的时候它会在Path中添加C:/Perl64/site/bin,里面有一个dmake.exe运行文件,这里我把它复制到自己手动添加的C:/Perl64/bin目录下面,除此之外,还要将C:/nasm下面的name.exe和ndisasm.exe拷贝至C:Program Files (x86)Microsoft Visual Studio 12.0VCin,设置完环境变量记得重启电脑才能生效。

    (3)执行命令

    打开Visual Studio的开发人员命令提示(找了半天,原来放在C:Program Files (x86)Microsoft Visual Studio 12.0Common7ToolsShortcuts),并进入openssl的目录(我的放在C盘根目录),执行命令perl Configure VC-WIN32 --prefix=C:/openssl     注意:这里的prefix是用来指定安装目录。然后运行 msdo_nasm 来创建Makefile文件,运行 nmake -f ms tdll.mak 进行编译生成openssl动态库(如果报错Cannot open include file: 'windows.h',需要先定位到C:Program Files (x86)Microsoft Visual Studio 12.0VCin运行 vcvars32.bat 来设置VC命令行编译的环境变量),然后耐心等待。。。接着运行 nmake -f ms/ntdll.mak test 来测试,若显示passed all tests,就可以运行 nmake -f ms tdll.mak install 来安装编译后的openssl到之前指定的目录。查看安装结果,即在 C:openssl 下有三个文件夹bin、lib和include,其中bin目录下包括openssl.exe(openssl指令程序)、ssleay32.dll(ssl协议动态库)、libeay32.dll(密码算法库),lib目录下包括ssleay32.lib,libeay32.lib,include目录则包括了OpenSSL开发设计的头文件。另外,如果之前你已经编译出错了,请先清除:nmake -f ms tdll.mak clean。

    (4)生成RSA的公钥和私钥

    打开bin文件夹下面的openssl.exe,运行 genrsa -out rsa_private_key.pem 1024 在当前目录下生成一个名为rsa_private_key的pem格式文件,用记事本方式打开它,可以看到-----BEGIN RSA PRIVATE KEY-----开头,-----END RSA PRIVATE KEY-----结尾的没有换行的字符串,这个就是原始的私钥;

    接着运行 pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM –nocrypt -out PKCS8_rsa_private_key.pem 把刚才的私钥转换成PKCS8格式,转换后的结果可以在命令行中看到,也可以在当前目录下生成的一个名为PKCS8_rsa_private_key的pem格式文件里看到;

    最后运行 rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem 生成公钥,可以在当前目录下生成的一个名为rsa_public_key的pem格式文件里看到,注意这里依据的是转换之前的原始私钥。

    二、前端jsrsa签名

    文字看得累,还是直接上代码吧。。。

    // 引入非对称加密rsa的前台签名文件(github上有,我下的版本是6.2.2)
    import Jsrsasign from "jsrsasign";
    // 导入的Jsrsasign模块里面有很多实用的对象,对应不同的方法
    console.log(Jsrsasign)
    
    // 引入私钥文件(一般java后台产生密匙对中的私钥是放在pem文件里,可以把里面的内容(字符串格式,带有头和尾,换行用)取出来放进js文件,然后用module.exports导出来)
    import PrivateKey from "privateKey.js";
    
    // 实例化rsa
    var rsa=new Jsrsasign.RSAKey();
    
    // 传入私钥
    // 默认传入的私钥是PKCS#1的格式,所以采用readPrivateKeyFromPEMString(keyPEM)这个方法
    // rsa.readPrivateKeyFromPEMString(PrivateKey);
    // 如果后台生产出来的私钥是PKCS#8的格式,就不能用readPrivateKeyFromPEMString(keyPEM)这个方法
    rsa=Jsrsasign.KEYUTIL.getKey(key);
    
    // 对要签名的json数据进行排序、拼接成用=和&连接起来的URL的参数形式,参数依次是要排序的json对象、是否倒序(默认为false)
    function jsonURLParams(json,reverse){
        // 创建一个空数组
        var jsonArr = [];
        // 往空数组里面导入json对象
        for(var i in json){
            var obj = {}
            obj[i] = json[i];
            jsonArr.push(obj);
        }
        // 数组长度小于2  或 不是json格式数据
        if(jsonArr.length < 2 || typeof jsonArr[0] !== "object") return jsonArr;
        // 数字类型排序        
        if(typeof getKey(jsonArr[0]) === "number") {
            jsonArr.sort(function(x, y) { return getKey(x) - getKey(y)});
        }
        // 字符串类型排序
        if(typeof getKey(jsonArr[0]) === "string") {
            // 按字符编码的顺序来排序
            jsonArr.sort(function(x, y) {
           var lenX = getKey(x).length,lenY = getKey(y).length,len = (lenX <= lenY) ? lenX : lenY;
           for (var i = 0; i < len; i++) {
            if (getKey(x).charCodeAt(i) != getKey(y).charCodeAt(i)) {
              return getKey(x).charCodeAt(i) - getKey(y).charCodeAt(i);
            }
            if (i == len - 1) {
              return getKey(x).length - getKey(y).length;
            }
           }
         } }
    // 倒序 if(reverse) { jsonArr.reverse(); } // 创建一个空字符串 var jsonString = ""; for(var i in jsonArr){ if(i < jsonArr.length - 1){ jsonString += getKey(jsonArr[i]) + "=" + jsonArr[i][getKey(jsonArr[i])] + "&" }else{ jsonString += getKey(jsonArr[i]) + "=" + jsonArr[i][getKey(jsonArr[i])] } } // 封装函数获取json的key function getKey(json){ for(var i in json){ return i; } } return jsonString; } var signParams = jsonURLParams(data) // 选择哪种hash算法(散列生成一个报文摘要,目的是防篡改) var hashAlg="sha1"; // 进行签名(对生成的报文摘要进行私钥加密,目的是身份验证) var sign=rsa.signString(signParams,hashAlg); // 将签名结果转成base64编码格式 sign=Jsrsasign.hex2b64(sign); // 一般是将签名后的参数放进需要传递的json数据的末尾,对应的字段名为signture data["signature"]=sign;

    三、后端nodejs签名(使用的express框架4.14.0)

    (1)先简单地封装了两个函数,记得要先引入JavaScript加密库crypto.js:

    // 封装公钥单行字符串转换成PEM编码格式字符串的函数
    function keyPem(str, insert_str, sn) {
        var newstr = "";
        for (var i = 0; i < str.length; i += sn) {
            var tmp = str.substring(i, i + sn);
            newstr += tmp + insert_str;
        }
        return newstr;
    }
    
    // 封装验签函数,data是前端传上来的json数据,sign是前端生成的签名字符串,key是验签用的公钥
    function verifySign(data,sign,key){
        // 注意如果传上来的json数据里包含签名键值对,需要先将其剔除
        delete data["signature"];
        // 如果前端签名之前对上传数据进行了排序和字符串拼接,那么这里也要作同样操作,即要保持签名和验签的源数据相同,继续调用前面封装好的jsonURLParams函数
        var data = jsonURLParams(data);
    
        // 如果sign取的是公钥文件头尾之间的一段字符串,需要先将其拼接回原来的PEM编码格式
        // var key = keyPem(key,"
    ",64);
        // key = '-----BEGIN PUBLIC KEY-----
    ' + key + '-----END PUBLIC KEY-----';
    
        console.log("需要验证签名的数据:"+ data);
        console.log("进行验证签名的公钥:
    " + key);
    
        // 选择与前端签名相匹配的hash算法
        var verifier = crypto.createVerify('RSA-SHA1');
    
        // 防止中文乱码
        verifier.update(new Buffer(data, 'utf-8'));
    
        // 输出验签结果,如果前端签名后使用base64编码,这里也要作同样操作
        return verifier.verify(key, sign, 'base64');    
    }

    (2)在需要验签的地方调用上述函数

        // 获取上传json数据
        var data = req.body;
        // 获取上传json数据中的签名字符串
        var sign = data["signature"];
        // 获取openssl生成的PEM编码格式的公钥
        var key = "-----BEGIN PUBLIC KEY-----
    "+
        "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAH6/6+YVYA2FmF6H8uBmivR20
    "+
        "ZbArDlDj1xG12w52XC47xTHrVp+PRufOUnUG58oNRO1SyD3ViZ6EzUclfVC/e8SS
    "+
        "6y6/4wDYAsNke1tWH+M52O7S5ICfiULm6fLULc9rXxbZz6AT1PtD/JdRUKBtAGTx
    "+
        "C+sR6OyH2UaqLra3qQIDAQAB
    "+
        "-----END PUBLIC KEY-----";   
        // var key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAH6/6+YVYA2FmF6H8uBmivR20ZbArDlDj1xG12w52XC47xTHrVp+PRufOUnUG58oNRO1SyD3ViZ6EzUclfVC/e8SS6y6/4wDYAsNke1tWH+M52O7S5ICfiULm6fLULc9rXxbZz6AT1PtD/JdRUKBtAGTxC+sR6OyH2UaqLra3qQIDAQAB";
        // 将三个参数传入封装好的验签函数
        var result = verifySign(data,sign,key);
  • 相关阅读:
    Java:多线程<一>
    Java:Exception
    Java: 内部类
    Ubuntu安装jdk
    ubuntu搜狗拼音安装
    录音-树莓派USB摄像头话筒
    leetcode 最小栈
    leetcode 编辑距离 动态规划
    leetcode 最小覆盖字串
    leetcode 最长上升子序列 动态规划
  • 原文地址:https://www.cnblogs.com/brandonhulala/p/6195815.html
Copyright © 2020-2023  润新知