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

2018-12-21  本文已影响0人  ape_caesar

引言

最近我在写一个接入各个渠道商的游戏聚合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();
}

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

上一篇下一篇

猜你喜欢

热点阅读