openssl+前端jsrsa簽名+後端nodejs驗籤

內容如標題所示,整體分爲三個部分:前端

1、win10下安裝openssl,而後經過openssl工具生成RSA的公鑰和私鑰java

(1)win10下安裝openssl須要的工具備:VS2013,Perl,nasm,openssl源碼node

其中,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)。git

(2)設置環境變量github

打開個人電腦->屬性->高級系統設置->環境變量,找到Path系統變量,點擊編輯,添加C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin;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.0\VC\bin,設置完環境變量記得重啓電腦才能生效。express

(3)執行命令json

打開Visual Studio的開發人員命令提示(找了半天,原來放在C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts),並進入openssl的目錄(個人放在C盤根目錄),執行命令perl Configure VC-WIN32 --prefix=C:/openssl     注意:這裏的prefix是用來指定安裝目錄。而後運行 ms\do_nasm 來建立Makefile文件,運行 nmake -f ms\ntdll.mak 進行編譯生成openssl動態庫(若是報錯Cannot open include file: 'windows.h',須要先定位到C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin運行 vcvars32.bat 來設置VC命令行編譯的環境變量),而後耐心等待。。。接着運行 nmake -f ms/ntdll.mak test 來測試,若顯示passed all tests,就能夠運行 nmake -f ms\ntdll.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\ntdll.mak clean。windows

(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格式文件裏看到,注意這裏依據的是轉換以前的原始私鑰。

2、前端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;

3、後端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,"\n",64);
    // key = '-----BEGIN PUBLIC KEY-----\n' + key + '-----END PUBLIC KEY-----';

    console.log("須要驗證簽名的數據:"+ data);
    console.log("進行驗證簽名的公鑰:\n" + 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-----\n"+
    "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAH6/6+YVYA2FmF6H8uBmivR20\n"+
    "ZbArDlDj1xG12w52XC47xTHrVp+PRufOUnUG58oNRO1SyD3ViZ6EzUclfVC/e8SS\n"+
    "6y6/4wDYAsNke1tWH+M52O7S5ICfiULm6fLULc9rXxbZz6AT1PtD/JdRUKBtAGTx\n"+
    "C+sR6OyH2UaqLra3qQIDAQAB\n"+
    "-----END PUBLIC KEY-----";   
    // var key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAH6/6+YVYA2FmF6H8uBmivR20ZbArDlDj1xG12w52XC47xTHrVp+PRufOUnUG58oNRO1SyD3ViZ6EzUclfVC/e8SS6y6/4wDYAsNke1tWH+M52O7S5ICfiULm6fLULc9rXxbZz6AT1PtD/JdRUKBtAGTxC+sR6OyH2UaqLra3qQIDAQAB";
    // 將三個參數傳入封裝好的驗籤函數
    var result = verifySign(data,sign,key);
相關文章
相關標籤/搜索