js rsa sign使用筆記(加密,解密,簽名,驗簽)


你將會收獲:

  1. js如何加密, 解密
  2. js如何簽名, 驗簽
  3. js和Java交互如何相互解密, 驗簽(重點)

通過谷歌, 發現jsrsasign庫使用者較多. 查看api發現這個庫功能很健全. 本文使用方法, 是結合網上千篇一律的博文, 加上我自己查看源碼總結出來的.

  • 公用代碼:
  // 公鑰 let pk="-----BEGIN PUBLIC KEY-----\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD3XSdz1MnzazBEN5KOfTx0IyVJ\n" + "Z5wb57isrCuHDhnYXwtmdhQalgII0fozeeFpMpAvlnmHC1kpW7XVGvZnLx3bWbCE\n" + "bf+pMSW4kmQuI+5cxRUJbCl7sdaODBrINgERHPICVC18AJLThEVMHyjuR6Jn4zQm\n" + "yYNbReSktY/BrFTvMQIDAQAB\n" + "-----END PUBLIC KEY-----"; // 私鑰 let priK = "-----BEGIN PRIVATE KEY-----\n" + "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAPddJ3PUyfNrMEQ3\n" + "ko59PHQjJUlnnBvnuKysK4cOGdhfC2Z2FBqWAgjR+jN54WkykC+WeYcLWSlbtdUa\n" + "9mcvHdtZsIRt/6kxJbiSZC4j7lzFFQlsKXux1o4MGsg2AREc8gJULXwAktOERUwf\n" + "KO5HomfjNCbJg1tF5KS1j8GsVO8xAgMBAAECgYEA6eG1JMrj63jEmStmMb1txG1a\n" + "mu4Q5z2QGgtr2HVXsIIlGEq6tWxyHf7TL4qkuz9onuYKn8n2Eqm44fZtVaBx+5ES\n" + "zRpIvlTvaxmVu0HZ1hYAzUw1XyRnXNMKpL5tT4GCjm8+QGPzlGxgXI1sNg8r9Jaw\n" + "9zRUYeA6LQR9RIMkHWUCQQD8QojjVoGjtiunoh/N8iplhUszZIavAEvmDIE+kVy+\n" + "pA7hvlukLw6JMc7cfTcnHyxDo9iHVIzrWlTuKRq9KWVLAkEA+wgJS2sgtldnCVn6\n" + "tJKFVwsHrWhMIU29msPPbNuWUD23BcKE/vehIyFu1ahNA/TiM40PEnzprQ5JfPxU\n" + "16S78wJANTfMLTnYy7Lo7sqTLx2BuD0wqjzw9QZ4/KVytsJv8IAn65P/PVn4FRV+\n" + "8KEx+3zmF7b/PT2nJRe/hycAzxtmlQJBAMrFwQxEqpXfoAEzx4lY2ZBn/nmaR/SW\n" + "4VNEXCbocVC7qT1j1R5HVMgV13uKiTtq8dUGWmhqsi7x3XayNK5ECPUCQQDZaAN6\n" + "tvIHApz9OLsXSw0jZirQ6KEYdharXbIVDy1W1sVE3lzLbqLdFp1bxAHQIvsYS5PM\n" + "A9veSJh372RLJKkj\n" + "-----END PRIVATE KEY-----"; // 原文 var src = "好厲害"; 

jsrsasign加密和解密

加密

  1. 傳入pem標准格式的秘鑰字符串, 解析生成秘鑰實例: RSAKey. 標准的pem格式秘鑰含有開始標記結束標記, 如本文使用的秘鑰: -----BEGIN xxx-----, -----END xxx-----. 至於xxx的具體內容不是太重要, 代碼里自動通過正則清洗掉頭和尾標記, 所以真的寫成-----BEGIN xxx-----也沒有關系.
  2. 調用encrypt方法, 傳入明文和公鑰實例, 加密后的返回值是16進制字符串.
  3. 所以, 需要將其轉為常用的Base64編碼. 如果為了方便放在URL上, 建議使用使用hextob64u(enc), 它會將+替換成-,/替換成_,去掉尾部補全的=. 不建議使用encodeURIComponent, 這種編碼方式會更大程度上擴大原數據的體積(Base64只會增加1/3, 而url采用的16進制方式, 會增加1倍, 具體原因可另外谷歌).

解密

基本類似加密流程.

    // 加密 // 讀取解析pem格式的秘鑰, 生成秘鑰實例 (RSAKey) var pub = KEYUTIL.getKey(pk); var enc = KJUR.crypto.Cipher.encrypt(src,pub); // console.log(enc); // console.log(hextob64(enc)); // 解密 var prv = KEYUTIL.getKey(priK); var dec = KJUR.crypto.Cipher.decrypt(enc,prv); console.log("jsrsasign decrypt: "+dec); 

jsrsasign簽名和驗簽

通用流程

RSA簽名驗簽基本流程如下, 當然, 都會被封裝成兩個方法搞定: 簽名和驗簽.
簽名:

  1. 指定一款摘要算法, 如sha1對原文哈希.
  2. 上述哈希前面填補上摘要算法標識, 便於驗簽時識別用的什么算法.
  3. 用rsa私鑰對上述哈希加密.
  4. 完成簽名.

驗簽:

  1. 用rsa公鑰對簽名解密, 得到摘要.
  2. 原文取摘要.
  3. 對比兩個摘要, 一樣則驗簽通過, 否則驗簽不通過.

使用jsrsasign簽名驗簽

簽名

網上資料很多比較雷同, 在簽名時代碼開起來比較麻煩.
這里先給出大家通常步驟, 最后給出我自己看源碼總結簡化調用方式.

方式1: 創建秘鑰實例 -> 構建Signature實例 -> 傳入秘鑰實例, 初始化 -> 簽名
    // 方式1: 先建立 key 對象, 構建 signature 實例, 傳入 key 初始化 -> 簽名 var key = KEYUTIL.getKey(priK); console.log(key); // 創建 Signature 對象 let signature=new KJUR.crypto.Signature({alg:"SHA1withRSA"}); // 傳入key實例, 初始化signature實例 signature.init(key); // 傳入待簽明文 signature.updateString(src); // 簽名, 得到16進制字符結果 let a = signature.sign(); let sign = hextob64(a); console.log(sign); 
方式2: 我的簡化方式: 方式1的基礎上, 去掉顯示讀取私鑰, 去掉初始化步驟(init(..))
    // 創建 Signature 對象 let signature=new KJUR.crypto.Signature({alg:"SHA1withRSA",prvkeypem:priK}); //!這里指定 私鑰 pem! signature.updateString(src); let a = signature.sign(); let sign = hextob64(a); console.log(sign); 

驗簽

注意點看注釋.

    // 驗簽 // !要重新new 一個Signature, 否則, 取摘要和簽名時取得摘要不一樣, 導致驗簽誤報失敗(原因不明)! let signatureVf = new KJUR.crypto.Signature({alg:"SHA1withRSA",prvkeypem:pk}); signatureVf.updateString(src); // !接受的參數是16進制字符串! let b = signatureVf.verify(b64tohex(sign)); console.log("jsrsasign verify: "+b); 

jsrsasign和Java交互

這是很關鍵的, 任何js插件在好用, 如果和Java不能兼容, 也是白搭. 之前就是過jsencrypt.js庫, 但是發現Java在簽名驗簽時貌似不兼容.

    // 解密Java的密文 var prv = KEYUTIL.getKey(priK); // Java加密的密文(Base64Url) let encJava = "8S2KlcygY8eUvq_Dzro81IQd6oA5fxW9l9hsy8iOvtByMMJI1wKedO5sR_pJmJFYEZl6wfD4BQ-FzvSYftnO5xO8kJaHNtnrFE7R0mqpLIkf6aN02K4F9zWLad3emFTN8Ze_GqooVaa0oX6XHqpDFBQJF3kUB6cfS9mDJNq_boE"; // 解密 / Base64Url -> 16進制 / 私鑰實例 var dec4Java = KJUR.crypto.Cipher.decrypt(b64utohex(encJava), prv); console.log("jsrsasign decrypt 4 java: "+dec4Java); // 驗證Java的簽名 // 構建Signature實例 // 這里 prvkeypem 放公鑰pem看起來有點怪, 但是這是可行的, 內部還是使用的上文經常出現的 KEYUTIL.getKey(pk) 來生成公鑰實例的 var sign4Java = new KJUR.crypto.Signature({alg:"SHA1withRSA",prvkeypem:pk}); sign4Java.updateString(src); // Java生成簽名 var signByJava = "O6uEQFPPEmRfEiZcLQjMB7yYLpO2ohmCJvn95Izu8LveUWqFtoYJbvWRYwKCCV-Z3iurjpEw5nExvHQghwoYIxpB7p97G29WXWhfiaA0AUNlxDM2cOus-CIAq-Kyqee7vDsewp6ixaHThu0CxoPFGpBTpo5kuOFlPFR6CRS3Q9M"; var b2 = sign4Java.verify(b64utohex(signByJava)); console.log("jsrsasign verify 4 java: " + b2); 

本文測試代碼的運行結果:

jsrsasign signing: O6uEQFPPEmRfEiZcLQjMB7yYLpO2ohmCJvn95Izu8LveUWqFtoYJbvWRYwKCCV+Z3iurjpEw5nExvHQghwoYIxpB7p97G29WXWhfiaA0AUNlxDM2cOus+CIAq+Kyqee7vDsewp6ixaHThu0CxoPFGpBTpo5kuOFlPFR6CRS3Q9M= jsrsasign verify: true jsrsasign decrypt: 好厲害 jsrsasign decrypt 4 java: 好厲害 jsrsasign verify 4 java: true 

附錄: jsrsasign部分方法源碼

本來想講測試用的源文件附上來, 但是這里貌似不支持附件, 所以部分主要的方法代碼. 通過閱讀, 加上了部分注釋, 所以api看起來更容易理解. 另外, 本文調用方式是在頁面引入js方式使用的, 若使用其他框架, 可能調用方式略有區別, 但是核心api是不變的.

/** * * @param l RSAKey / ECDSA / DSA / 標准的pem格式秘鑰Base64字符 * @param k * @param n * @returns {*} */ KEYUTIL.getKey = function (l, k, n) { var G = ASN1HEX, L = G.getChildIdx, v = G.getV, d = G.getVbyList, c = KJUR.crypto, i = c.ECDSA, C = c.DSA, w = RSAKey, M = pemtohex, F = KEYUTIL; ... // 這里通過判斷pem結束標記來判斷傳入的是什么類型的秘鑰字符 if (l.indexOf("-END PUBLIC KEY-") != -1) { var O = pemtohex(l, "PUBLIC KEY"); return F._getKeyFromPublicPKCS8Hex(O) } if (l.indexOf("-END RSA PRIVATE KEY-") != -1 && l.indexOf("4,ENCRYPTED") == -1) { var m = M(l, "RSA PRIVATE KEY"); return F.getKey(m, null, "pkcs5prv") } ... 
/** * * @param {String} e 明文 * @param {RSAKey} f 公鑰 * @param {String} d 算法名稱, 大寫, 如 RSA, 缺省 RSA * @returns {String} 16進制字符串 */ KJUR.crypto.Cipher.encrypt = function (e, f, d) { if (f instanceof RSAKey && f.isPublic) { var c = KJUR.crypto.Cipher.getAlgByKeyAndName(f, d); if (c === "RSA") { return f.encrypt(e) } if (c === "RSAOAEP") { return f.encryptOAEP(e, "sha1") } var b = c.match(/^RSAOAEP(\d+)$/); if (b !== null) { return f.encryptOAEP(e, "sha" + b[1]) } throw"Cipher.encrypt: unsupported algorithm for RSAKey: " + d } else { throw"Cipher.encrypt: unsupported key or algorithm" } }; 
/** * * @param {String} e 16進制密文字符串 * @param {RSAKey} f 私鑰 * @param {String} d 算法名稱, 大寫, 如 RSA, 缺省 RSA * @returns {String} 明文 */ KJUR.crypto.Cipher.decrypt = function (e, f, d) { if (f instanceof RSAKey && f.isPrivate) { var c = KJUR.crypto.Cipher.getAlgByKeyAndName(f, d); if (c === "RSA") { return f.decrypt(e) } if (c === "RSAOAEP") { return f.decryptOAEP(e, "sha1") } var b = c.match(/^RSAOAEP(\d+)$/); if (b !== null) { return f.decryptOAEP(e, "sha" + b[1]) } throw"Cipher.decrypt: unsupported algorithm for RSAKey: " + d } else { throw"Cipher.decrypt: unsupported key or algorithm" } }; 
/** * * @param {Object}o o.alg:算法名稱; o.prov:支持的js文件標識; o.prvkeypem:pem格式秘鑰(base64); * @constructor */ KJUR.crypto.Signature = function (o) { var q = null; ... /**簽名方法*/ this.sign = function () { ... } else { if (this.prvKey instanceof RSAKey && this.pubkeyAlgName === "rsa") { this.hSign = this.prvKey.signWithMessageHash(this.sHashHex, this.mdAlgName) ...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM