nodejs rsa分段签名和验签-和php同步

javascript admin 2634℃ 0评论

最近在弄桌面版的APP,用到了nodejs,服务器正好用到的接口验签又是php的,所以需要兼容php做相互签名和验签。本来想用第三方库的,比如node-rsa.这个用了之后发现本身可以签名和验签但是在php中却解不出来,同样php的签名它的方法却解不出来。换了一个node-ursa,结果装了老半天,不是要装net work 4.5.1就是要装python还有visual studio 后来好不容易装好了却在编译的时候启动不了程序报 Path nust a string.同时这两个库都不支持分段加密。于是我决定用nodejs自带的库(crypto)来写。下面就是代码.有什么疑问欢迎提问。

SignBase.js

var qs = require('querystring');

export  default class SignBase{
    /**
     * 对象按键排序
     * @param object obj
     * @param boolean desc
     * @return object
     */
    static sortObjectByKey(obj,desc){
        var keys = Object.keys(obj);
        var returnObj = {};
        keys = keys.sort();
        if(desc){
            keys = keys.reverse();
        }
        for(var i = 0 , len = keys.length ; i < len ; i++){
            returnObj[keys[i]] = obj[keys[i]];
        }
        return returnObj;
    }
    /**
     * 将字符串转化为查询字符串
     * @param object json
     * @return str
     */
    static jsonToSearch(json){
        var str = "";
        for(var key in json){
            if(json.hasOwnProperty(key)){
                str += key + '=' + this.stripslashes(json[key])+'&';
            }
        }
        //把最后的&去掉
        if(str){
            str = str.substring(0,str.length -1);
        }
        return str;
    }
    /**
     *除去待签名参数数组中的空值和签名参数
     *data json
     */
    static paramFilter(para){
        var para_filter = new Object();
        for (var key in para){
            if(para[key] === undefined || (this.isString(para[key]) && para[key].length === 0)){
                continue;
            }
            else{
                para_filter[key] = para[key];
            }
        }
        return para_filter;
    }
    /**
     *如果存在转义字符,那么去掉转义
     *str string
     */
    static stripslashes(str){
        return str;
        if(typeof str !== "string"){
            return str;
        }
        return str.replace(/[\\"&}{\+]/g,"");
    }
    static extend(target,source){
        target = target || {};
        source = source || {};
        for(var key in source){
            if(source.hasOwnProperty(key)){
                target[key] = source[key];
            }
        }
        return target;
    }
    static toString(text){
        return text.toString();
    }
    static noop(){};
    /**
     *拾取对象指定的key
     * obj object
     * keyArr Arr
     * return object
     */
    static pick(obj,keyArr){
        if(!keyArr instanceof Array){
            keyArr = [keyArr];
        }
        var result = {};
        for(var i = 0 , len = keyArr.length ; i < len; i++){
            if(obj.hasOwnProperty(keyArr[i])){
                result[keyArr[i]] = obj[keyArr[i]];
            }
        }
        return result;
    }
    /**
     * 把对象所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串,并对字符串做urlencode编码
     * @param para 需要拼接的对象
     * return 拼接完成以后的字符串
     */
    static createLinkstringUrlencode(para){
        return qs.stringify(para);
    }
    /**
     * 将连连支付返回的字符串转化为json
     * @param str string
     * return object
     */
    static resStringToJSON(str){
        return eval('('+decodeURIComponent(str)+')');
    }
    static clone(json){
        var result = {};
        for(var key in json){
            result[key] = json[key];
        }
        return result;
    }
    static encryptStr(json){
        var cloneJson = this.clone(json);
        cloneJson = this.paramFilter(cloneJson);
        cloneJson = this.sortObjectByKey(cloneJson);
        return this.jsonToSearch(cloneJson);
    }

    static isFunction(fn){
        return this.isType(fn,'Function');
    }
    static isObject(obj){
        return this.isType(obj,'Object');
    }
    static isArray(arr){
        return this.isType(arr,'Array');
    }
    static isString(str){
        return this.isType(str,'String');
    }
    static isType(obj,type){
        return Object.prototype.toString.call(obj) === '[object '+type+']';
    }
    static getMD5KeyFill(md5key){
        return '&key='+md5key;
    }
    static checkKeyLen(key,len){
        if(!isString(key)){
            return false;
        }
        return key.length === len;
    }

//rsa util

    /**
     @param string key
     格式化rsa的私钥,64位长度为一行
     @return string
     */
    static formatRSAKey(key){
        var len = key.length;
        var privateLen = 64;//private key 64 length one line
        var space = Math.floor(len/privateLen);
        var flag = len%privateLen === 0 ? true : false;
        var str = "";
        for(var i = 0 ; i < space ; i++){
            str += key.substr(i*privateLen,privateLen) + '\r\n';
        }
        if(!flag){
            str += key.substring(space*privateLen) + '\r\n';
        }
        return str;
    }
    /**
     @param string key rsa的私钥
     返回标准格式的rsa的私钥
     @return string
     */
    static getRSAPrivateKey(key){
        return this.getRSAPrivateKeyPrefix() + this.formatRSAKey(key) + this.getRSAPrivateKeySuffix();
    }
    /**
     获取rsa私钥的前缀
     @return string
     */
    static getRSAPrivateKeyPrefix(){
        return '-----BEGIN PRIVATE KEY-----\r\n';
    }
    /**
     获取rsa私钥的后缀
     @return string
     */
    static getRSAPrivateKeySuffix(){
        return '-----END PRIVATE KEY-----';
    }
    /**
     @param string key rsa的私钥
     返回标准格式的rsa的公钥
     @return string
     */
    static getRSAPublicKey(key){
        return this.getRSAPublickKeyPrefix() + this.formatRSAKey(key) + this.getRSAPublicKeySuffix();
    }
    /**
     获取rsa公钥的前缀
     @return string
     */
    static getRSAPublickKeyPrefix(){
        return '-----BEGIN PUBLIC KEY-----\r\n';
    }
    /**
     获取rsa公钥的后缀
     @return string
     */
    static getRSAPublicKeySuffix(){
        return '-----END PUBLIC KEY-----';
    }


//index util

    static getStr(formatToStr){
        var str = '';
        if(this.isObject(formatToStr)){
            str = this.encryptStr(formatToStr);
        }else if(this.isString(formatToStr)){
            str = formatToStr;
        }
        return str;
    }

    static getSignType(sign_type){
        return (this.isString(sign_type) ? sign_type.toUpperCase(sign_type) : 'MD5');
    }

    static searchToJson(searchStr){
        if(this.isObject(searchStr)){
            return searchStr;
        }
        if(!this.isString(searchStr)){
            return {};
        }
        var returnObj = {};
        var arr = searchStr.split('&');
        for(var i = 0 , len = arr.length ; i < len ; i++){
            var temp = arr[i].split('=');
            returnObj[temp[0]] = temp[1] || '';
        }
        return returnObj;
    }
};

SignUtil.js

var crypto = require('crypto');
var base = require('./SignBase');

var public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCqIwVbt+mffQqoN5iDjDQDQbZ" +
    "MJM4IINfc5v.....................Eya/TExUnfZkzk" +
    "........................PQSOjn8IclgKKgC40TrwiIy" +
    "j63VdtMMmaYXvE1C7wIDAQAB";//公钥
var private_key = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMKojBVu36Z99Cqg" +
    "3mIOMNANBtkwk...........................r" +
    "9..................................." +
    "qALjROvCIjKPrdV20wyZphe8TULvAgMBAAECgYAxpN6lELPFOFYvEPvmMgxM4yWd" +
    "+n9xTwly..........................DzD9pRpMUI+SkvQp6" +
    "lxGiMhoe4wqIH8I6ci4.................jaTu/GsBtrw/N08H1LqEuuA" +
    "EQcQ2xmznXt+Gs......................hWFSsgHylRbYG52L" +
    "1AfP2blx/3nr9j5Nvv6/CDMtfz21cCscLrS1vQwjOaMCQQDdvX8StIZvOjAmtBXi" +
    "PVn2R3O9dIgoIf93xQ+MmWKu2EQqyyvf0FmmQdTELjMoktsbM0TB8nw8wcLbnAcl" +
    "1f5FAkBqrSSZyaKTRq...................rUZgCN+6k6qf6HD/eGwDf" +
    "YD5KZFm7GVyt6SNeg1k0VJRxX4MrAkEAnAits9buxrQ2j/WndJVhiclL66Rl73/F" +
    "Cl3UDEcfRxQOG+LuU8eE..................plNesDTCXuCP1QJAHVfSgsAD" +
    "pB0EqSsXsAJBwx/OiqsH......................XLw5u2xgbP1BwwzCyV+" +
    "FAqsdu9e5aQi4A==";//私钥
/**
 * ykuang
 * 2016-08-22 参考nodejs原生库
 *  https://nodejs.org/api/crypto.html#crypto_crypto_publicencrypt_public_key_buffer
 *
 */
/**RSA签名
 * plaintext签名数据(需要先排序,然后拼接)
 * key商户私钥
 * 签名用商户私钥,必须是没有经过pkcs8转换的私钥
 * 最后的签名,需要用base64编码
 * return Sign签名
 */
export default class SignUtil {
    /**
     ## RSA加密数据/分段加密(node>0.12)
     * @param:plaintext:明文
     * @param:publickKey:公钥
     * 最后的签名,需要用base64编码
     * 私钥采用1024位
     * return base64 encrypt data
     */
    static encrypt(plaintext, publickKey = '') {
        if (base.isObject(plaintext)) {
            var object = base.sortObjectByKey(plaintext);
            plaintext = base.createLinkstringUrlencode(base.paramFilter(object));
        }
        if (publickKey == '') {
            publickKey = public_key;
        }
        var str = '';
        if (base.isFunction(crypto.publicEncrypt)) {
            try {
                var keySizeBytes = 1024;
                var buffer = new Buffer(plaintext);
                var maxBufferSize = keySizeBytes / 8 - 11;
                var bytesDecrypted = 0;
                var encryptedBuffersList = [];

                while (bytesDecrypted < buffer.length) {
                    //calculates next maximun length for temporary buffer and creates it
                    var amountToCopy = Math.min(maxBufferSize, buffer.length - bytesDecrypted);
                    var tempBuffer = new Buffer(amountToCopy);

                    //copies next chunk of data to the temporary buffer
                    buffer.copy(tempBuffer, 0, bytesDecrypted, bytesDecrypted + amountToCopy);

                    //encrypts and stores current chunk
                    var encryptedBuffer = crypto.publicEncrypt({
                        key: base.getRSAPublicKey(publickKey),
                        padding: crypto.constants.RSA_PKCS1_PADDING
                    }, tempBuffer);
                    str += encryptedBuffer;
                    encryptedBuffersList.push(encryptedBuffer);
                    bytesDecrypted += amountToCopy;
                }
                /*  //padding
                 str = crypto.publicEncrypt({
                 key: base.getRSAPublicKey(publickKey),
                 padding: crypto.constants.RSA_PKCS1_PADDING
                 }, buffer);*/
                str = Buffer.concat(encryptedBuffersList).toString('base64');
            } catch (e) {
                console.log('rsa encrypt error : ', e);
            }
        } else {
            console.error('rsa encrypt must need node version >= 0.12');
        }
        return str;
    };

    /**
     ## RSA解密数据/分段解密(node>0.12)
     * @param:ciphertext:明文,base64
     * @param:publickKey:私钥
     * return 原始明文
     */
    static decrypt(ciphertext, privateKey = '') {
        if (privateKey == '') {
            privateKey = private_key;
        }
        var str = '';
        if (base.isFunction(crypto.privateDecrypt)) {
            try {
                var encryptedBuffer = new Buffer(ciphertext, 'base64');
                var decryptedBuffers = [];
                var keySizeBytes = 1024 / 8;
                //if the clear text was encrypted with a key of size N, the encrypted
                //result is a string formed by the concatenation of strings of N bytes long,
                //so we can find out how many substrings there are by diving the final result
                //size per N
                var totalBuffers = encryptedBuffer.length / keySizeBytes;
                //decrypts each buffer and stores result buffer in an array
                for (var i = 0; i < totalBuffers; i++) {
                    //copies next buffer chunk to be decrypted in a temp buffer
                    var tempBuffer = new Buffer(keySizeBytes);
                    encryptedBuffer.copy(tempBuffer, 0, i * keySizeBytes, (i + 1) * keySizeBytes);
                    //decrypts and stores current chunk
                    var decryptedBuffer = crypto.privateDecrypt({
                        key: base.getRSAPrivateKey(privateKey),
                        padding: crypto.constants.RSA_PKCS1_PADDING
                    }, new Buffer(tempBuffer));
                    decryptedBuffers.push(decryptedBuffer);
                }
                //concatenates all decrypted buffers and returns the corresponding String
                return Buffer.concat(decryptedBuffers).toString('utf8');
            } catch (e) {
                console.error('rsa decrypt error : ', e);
            }
        } else {
            console.error('rsa decrypt must need node version >= 0.12');
        }
        return str;
    };

    /**RSA验签 公钥验签
     * plaintext待签名数据(需要先排序,然后拼接)
     * sign需要验签的签名,需要base64_decode解码
     * key银通公钥
     * 验签用连连支付公钥
     * return 验签是否通过 bool值
     */
    static verify(plaintext, sign, nPayPublickKey, algorithm) {
        var flag = false;
        try {
            algorithm = algorithm || "RSA-SHA1";
            var verifier = crypto.createVerify(algorithm);
            verifier.update(plaintext);
            flag = verifier.verify(base.getRSAPublicKey(nPayPublickKey), sign, "base64");
        } catch (e) {
            console.error('rsaSign verify error : ', e);
        }
        return flag;
    };

    /**
     * 私钥加密签名
     * @param plaintext
     * @param merchantPrivateKey
     * @param algorithm
     * @returns {string}
     */
    static sign(plaintext, merchantPrivateKey, algorithm) {
        var str = '';
        try {
            algorithm = algorithm || "RSA-SHA1";
            var RSA = crypto.createSign(algorithm);
            var pem = base.getRSAPrivateKey(merchantPrivateKey);
            RSA.update(plaintext);
            str = RSA.sign(pem, 'base64');
        } catch (e) {
            console.error('rsaSign sign error : ', e);
        }
        return str;
    }
}

用法:

import SignUtil from '../../service/rsa/SignUtil'; //路径要对
SignUtil.encrypt(params)//params 可以是object也可以是字符串


加载中...