美文网首页
node.js Rsa解码过程(以酷派支付校验为例)

node.js Rsa解码过程(以酷派支付校验为例)

作者: ape_caesar | 来源:发表于2018-12-21 17:48 被阅读0次

    引言

    最近我在写一个接入各个渠道商的游戏聚合sdk, 主要就是进行登录的校验以及支付相关验签, 和支付回调接口。其中涉及很多校验的代码,这里就简单提一下, 更多要写一下酷派的rsa解码, 简直不要太恶心

    md5/hmac

    大部分渠道商的签名校验都是以md5/hmac来进行的

    例子1

    假如现在小米接收到了用户的充值, 然后异步通知我们sdk服务器用户的充值,他发出以下数据

    {
         "data": {
              "status": "SUCCESS",
              "cp_order_no": "mc_ud8293jdf",
              "orderid": "xiaomi_ud8293jdf",
              "price":  12,
              "sumit_time": "20181211078455",
              "productid": "mc_2342323",
              "extra": "155",
          },
          "sign": "df789eufd8f923jhd829jfd98"
    }
    

    来告知充值ok了, 为了防止有人恶意篡改数据造成损失, 我们需要对传输的数据进行校验。
    一般的校验方式很简单, 就是讲传输数据按照key的ASCII顺序排列,从a到z的顺序, 将key=value这样的一对用&或者用空字符串来连接成一个baseStr, 将上列数据处理后就是这样的

    cp_order_no=mc_ud8293jdf&extra=155&orderid=xiaomi_ud8293jdf&.....sumit_time=20181211078455
    

    如果用md5
    sign大概就是md(baseStr+appkey), 这里有可能要hex编码或者base64编码格式

    appkey是应用接入渠道(小米,应用宝等)分配的,私密的东西

    用nodejs代码表示就是

    const data = `${baseStr}&${appkey}` 
    crypto.createHash('md5').update(data).digest('hex')
    // 或者
    crypto.createHash('md5').update(data).digest('base64')
    

    如果用hmac
    一般就是

    crypto.createHmac('sha1', appkey).update(baseStr).digest('hex')
    // 或者
    crypto.createHmac('sha1', appkey).update(baseStr).digest('base64')
    

    再讲自己计算出的sign和渠道商传递过来的sign进行对比, 如果不同就说明数据有问题。
    这都相当简单, 主要有些可能的urlencode或者忘记加""符号等等的小错误, 多多调试的ok了

    接下来主要讲讲恶心的酷派校验
    有不少的渠道商使用rsa进行校验的, 比如华为, 同样很简单
    例如使用RSAWITHSHA1

    const verify = crypto.createVerify('RSA-SHA1');
    verify.update(Buffer.from(_baseStr, 'utf8'));
    // key是渠道商给的Rsa public key 可以是一个.pem文件
    return verify.verify(key, sign, 'base64');
    

    一般也就是这样就ok了
    但是酷派的就比较特殊, 他的文档简而言之就一句话:你自己去看我给的代码示例, 他给的demo只有java php c++ net, 就是没有node的, 所以我就慢慢把他java的代码翻译过来, 以下是过程:
    他给数据是这样的

    const reqJson = `{\"exorderno\":\"iVk4eRZknftx4vAJm5VE\",\"transid\":\"02115061814204200016\",\"waresid\":1,${nbsp
            }\"appid\":\"3000962200\",\"feetype\":0,\"money\":1,\"count\":1,\"result\":0,\"transtype\":0,${nbsp
            }\"transtime\":\"2015-06-18 14:20:59\",\"cpprivate\":\"cp private info!!\",\"paytype\":401}`;
    const _sign = '56b10877c6ecf3fa3c4805ca8b6f26a8 5fd39828d76b54faf8a034e4d509150b 2519141767960a2e1bfd27b04dbcc8b2';
    

    reqJson是返回数据
    然后假设我们appkey是这样的

    const appkey = `RkIwNTlFM0Y5RTEzNTA5NDcxNEMxMkY1OTREQUQxM0VFNEEwRTI2N01UZ3hNamd6T0RRek1ERTVOR${nbsp
            }Gd4T0RreU9Ua3JNVGsxTlRBME5EQXlNakF5TmpRM056RTVPRE13TkRZNE5ESTJOekUxTWpVMk5EUXdOREEz`;
    

    接下来一些东西, 我也看不懂, 酷派Demo代码中说base64.decode(appkey)就可以得到类似23942398+2342389479239428的一个字符串,然后+号前的是privatekey, +号后面的是modkey,(什么!rsa秘钥的私密就得到了!?)

    // 获取privatekey和modkey
    String decodeBaseStr = Base64.decode(key);
    

    然后我用nodejs的base64.decode方法试了几遍都不行, 方法如下

    const str = Buffer.from(key, 'base64').toString()
    

    卡了我好一会之后我才发现他的Base64.decode是自己写的方法,点进去后看到他的decode方法原来是这样的

    // java
    if (cryptoStr.length() < 40)
            return "";
    try {
        String tempStr = new String(decode(cryptoStr.getBytes("UTF-8")));
        String result = tempStr.substring(40, tempStr.length());
        return new String(decode(result.getBytes("UTF-8")));
    } catch (java.lang.ArrayIndexOutOfBoundsException ex) {
        return "";
    }
    

    这尼玛我被骗了, 然后我按照他的这个鬼用nodejs写完是这样的

    // nodejs
    const _str1 = Buffer.from(key, 'base64').toString().substring(40);
    const _str2 = Buffer.from(_str2, 'base64').toString();
    // _str2 : 18128384301948189299+195504402202647719830468426715256440407
    

    就这样我就拿到privatekeymod

    稍微了解过rsa加密算法的人都有印象
    rsa的加密解密公式大概是这样的

    rsa.jpg

    这里C1就是酷派给的_sign按' '分割后得到暗文, d就是得到的privatekeyn就是我们得到的mod, 按照这个计算式就可以得到明文M1 M2 M3了, 上面图片中的数都比较小, 可是我们得到的数可是很大的,如同一亿的一亿次方再模99123123482348230942390这样的,妈的我一个不是科班的不太懂这个东西该怎么解, 而且这个数是不是太大了。
    后来查来查去看到说有啥指数循环节的东东可以将指数降低来运算, 像酷派Demo里是java BigInteger有方法powmod,可以直接得结果, 我查了一会后来在Stack Overflow找到nodejs的big-integer库也有powmod方法,开心!

    然后java的byteArray又然我卡了半天

    // java
    private static byte[][] dencodeRSA(BigInteger[] encodeM, BigInteger d,
                BigInteger n) {
            byte[][] dencodeM = new byte[encodeM.length][];
            int i;
            int lung = encodeM.length;
            for (i = 0; i < lung; i++) {
                dencodeM[i] = encodeM[i].modPow(d, n).toByteArray();
            }
            return dencodeM;
        }
    

    这里取模后的很大的数他转为bypteArrry, 之后用new String(bypteArrry, 'UTF-8')就得到结果了
    我直接用

    // nodejs
    bigInt.modPow(_p, _m).toString()
    

    得到的结果不一样
    我是直接转为字符串了, 他是转byteArray再转utf8格式的字符串, 我想了半天node的Buffer怎么整这个问题。
    Buffer有个方法Buffer.from(array<integer>).toString('utf8')是可以用字节数组转为字符串的, 可是我得不到字节数组。
    然后我又看了一会big-integer库的方法, 终于找到他有个toArray(radix: number)方法, 参数是可以指定进制, 而字节的radix就是256啊, 然后我输入进去, 阿哈!, 就得到了一个字节数组了, 再将其放入到Buffer.from()里, 终于成功得到了与他的java一样的结果了。

    完整代码

    /**
     * 解密酷派rsa
     * @param sign 酷派传递的sign
     * @param p 通过appkey解得的privatekey
     * @param m 通过appkey解得的mode
     */
    function decrypt(sign: string, p: string, m: string): string{
        const keys = sign.split(' ');
        if (!_.isArray(keys)) {
            throw new BadParamsException('密文不符合要求!!!');
        }
        const bigIntArr = keys.map(item => {
            return bigint(item, 16);
        });
        const _p = bigint(p);
        const _m = bigint(m);
        let str = '';
        bigIntArr.forEach(item => {
            const tmp = item.modPow(_p, _m).toArray(256);
            str = str + Buffer.from(tmp.value).toString('utf8')
            .replace(/\r/g, '')
            .replace(/\n/g, '');
        });
        return str.trim();
    }
    

    虽然酷派这个手机品牌最近也不火了, 但是接入他渠道还真是不简单。 希望在他的渠道能多挣点钱,别然我太伤心;

    相关文章

      网友评论

          本文标题:node.js Rsa解码过程(以酷派支付校验为例)

          本文链接:https://www.haomeiwen.com/subject/oesvcqtx.html