zoukankan      html  css  js  c++  java
  • jsrsasign 进行 RSA 加密、解密、签名、验签

    通过谷歌, 发现jsrsasign库使用者较多. 查看api发现这个库功能很健全. 本文使用方法

    • 公用代码:
      // 公钥
       let pk="-----BEGIN PUBLIC KEY-----
    " +
            "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD3XSdz1MnzazBEN5KOfTx0IyVJ
    " +
            "Z5wb57isrCuHDhnYXwtmdhQalgII0fozeeFpMpAvlnmHC1kpW7XVGvZnLx3bWbCE
    " +
            "bf+pMSW4kmQuI+5cxRUJbCl7sdaODBrINgERHPICVC18AJLThEVMHyjuR6Jn4zQm
    " +
            "yYNbReSktY/BrFTvMQIDAQAB
    " +
            "-----END PUBLIC KEY-----";
      // 私钥
        let priK = "-----BEGIN PRIVATE KEY-----
    " +
            "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAPddJ3PUyfNrMEQ3
    " +
            "ko59PHQjJUlnnBvnuKysK4cOGdhfC2Z2FBqWAgjR+jN54WkykC+WeYcLWSlbtdUa
    " +
            "9mcvHdtZsIRt/6kxJbiSZC4j7lzFFQlsKXux1o4MGsg2AREc8gJULXwAktOERUwf
    " +
            "KO5HomfjNCbJg1tF5KS1j8GsVO8xAgMBAAECgYEA6eG1JMrj63jEmStmMb1txG1a
    " +
            "mu4Q5z2QGgtr2HVXsIIlGEq6tWxyHf7TL4qkuz9onuYKn8n2Eqm44fZtVaBx+5ES
    " +
            "zRpIvlTvaxmVu0HZ1hYAzUw1XyRnXNMKpL5tT4GCjm8+QGPzlGxgXI1sNg8r9Jaw
    " +
            "9zRUYeA6LQR9RIMkHWUCQQD8QojjVoGjtiunoh/N8iplhUszZIavAEvmDIE+kVy+
    " +
            "pA7hvlukLw6JMc7cfTcnHyxDo9iHVIzrWlTuKRq9KWVLAkEA+wgJS2sgtldnCVn6
    " +
            "tJKFVwsHrWhMIU29msPPbNuWUD23BcKE/vehIyFu1ahNA/TiM40PEnzprQ5JfPxU
    " +
            "16S78wJANTfMLTnYy7Lo7sqTLx2BuD0wqjzw9QZ4/KVytsJv8IAn65P/PVn4FRV+
    " +
            "8KEx+3zmF7b/PT2nJRe/hycAzxtmlQJBAMrFwQxEqpXfoAEzx4lY2ZBn/nmaR/SW
    " +
            "4VNEXCbocVC7qT1j1R5HVMgV13uKiTtq8dUGWmhqsi7x3XayNK5ECPUCQQDZaAN6
    " +
            "tvIHApz9OLsXSw0jZirQ6KEYdharXbIVDy1W1sVE3lzLbqLdFp1bxAHQIvsYS5PM
    " +
            "A9veSJh372RLJKkj
    " +
            "-----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)
      ...
    


    作者:Nisus_Liu
    链接:https://www.jianshu.com/p/b32fc387d8ad
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    HTML DOM 06 节点关系
    HTML DOM 05 事件(三)
    HTML DOM 05 事件(二)
    HTML DOM 05 事件(一)
    html DOM 04 样式
    html DOM 03 节点的属性
    html DOM 02 获取节点
    html DOM 01 节点概念
    JavaScript 29 计时器
    JavaScript 28 弹出框
  • 原文地址:https://www.cnblogs.com/kgdxpr/p/12651650.html
Copyright © 2011-2022 走看看