內容如標題所示,總體分為三個部分:
一、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.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,設置完環境變量記得重啟電腦才能生效。
(3)執行命令
打開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。
(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,"\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);
