提要
三方支付系统的交易离不开银行的支持,而银行作为外部系统,银行方如何保证接收到的交易请求是可信的,我方又如何保证发送到银行方的信息中途不会被人拦截或者篡改,并且接收到的响应是由银行方返回的呢,本文从渠道角度讲述信息加密和数字签名对支付交易安全的重要性
为什么需要加密
-
防止信息泄露
如果使用明文通信,明文数据会经过中间代理服务器、路由器、通信服务运营商等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了,不法分子会收集银行卡信息到黑市进行售卖从而获得暴利。而我们的用户则会经常收到骚扰电话或短信,对日常生活照成不利影响
-
防止数据被篡改
如果信息在传输过程中被劫持,还可以篡改传输的信息,且不被双方察觉,这就是中间人攻击。如果篡改收款交易会造成恶意扣款,给支付公司形象造成恶劣影响,严重的话会遭到央行罚款或吊销支付牌照,如果篡改付款交易则会把我们备付金付款到指定银行卡账号造成资金损失
为什么需要数字签名
- 防重放攻击
- 防止数据被伪造
- 防止数据被篡改
- 防止数据抵赖
- 对数据进行多重加密
- 实现客户的身份认证 虽然数字签名有这么多作用,但最重要的作用是防抵赖,因为私钥的唯一性,使用私钥签名的数据肯定是私钥所有者发送的,所以无法抵赖
加密&加签有哪些方式
说完了加密和数字签名的好处,接下来我们分析下各种算法的优缺点
常用算法
- MD5、SHA(不可逆)
- DES、3DES、AES、SM4(对称,可逆)
- RSA、DSA、SM2(非对称,可逆)
算法对比
- 对称加密算法(加解密密钥相同)
| 名称 | 密钥长度 | 运算速度 | 安全性 | 资源消耗 |
|---|---|---|---|---|
| DES | 56位 | 较快 | 低 | 中 |
| 3DES | 112位或168位 | 慢 | 中 | 高 |
| AES | 128、192、256位 | 快 | 高 | 低 |
- 非对称算法(加密密钥和解密密钥不同)
| 名称 | 成熟度 | 安全性(取决于密钥长度) | 运算速度 | 资源消耗 |
|---|---|---|---|---|
| RSA | 高 | 高 | 慢 | 高 |
| DSA | 高 | 高 | 慢 | 只能用于数字签名 |
| ECC | 低 | 高 | 快 | 低(计算量小,存储空间占用小,带宽要求低) |
- 散列算法比较
| 名称 | 安全性 | 速度 |
|---|---|---|
| SHA-1 | 高 | 慢 |
| MD5 | 中 | 快 |
- 对称与非对称算法比较
| 名称 | 密钥管理 | 安全性 | 速度 |
|---|---|---|---|
| 对称算法 | 比较难,不适合互联网,一般用于内部系统 | 中 | 快好几个数量级(软件加解密速度至少快100倍,每秒可以加解密数M比特数据),适合大数据量的加解密处理 |
| 非对称算法 | 密钥容易管理 | 高 | 慢,适合小数据量加解密或数据签名 |
(具体每种算法原理和区别请详见:加解密与数字签名初探,本文我们只讨论具体的用法)
用法
散列算法
散列算法一般用于身份鉴权这种信息类交易,这种交易只是认证鉴权要素的正确性不会出现资金损失,所以会优先使用速度快的算法,安全性要求偏低。为了增加哈希类算法的安全性往往会用到加盐的方式 吉信银行卡鉴权和身份鉴权、聚合数据银行卡鉴权和身份鉴权等均使用MD方式作为数字签名,过程如下(本文所有示例均以机构和银行为例讲解)
- signKey为加盐串,由双方共同约定
- 原始报文+signKey经过MD5运算生成签名串sign1
- 机构把sign1和原始报文发送给银行
- 银行接收到原始报文后,使用双方约定的signKey进行MD5运算生成sign2
- 比较sign1和sign是否相同,相同则验签通过继续业务处理,不相同则交易失败
java实现代码如下:
/**
* @Title: md5Sign
* @Description: MD5签名
* @param reqMap
* @param mercKey
* @return
* @throws Exception
*/
public static String md5Sign(Map<String,String> reqMap,String mercKey)throws BusinessException {
long startTime = System.currentTimeMillis();
StringBuffer signSrc = new StringBuffer();
List<String> keys = new ArrayList<String>(reqMap.keySet());
Collections.sort(keys);
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = reqMap.get(key);
if (key != null && !"".equals(key) && value != null && !"sign".equals(key)) {
signSrc.append(key + "=" + value);
}
}
String sign = DigestUtils.md5Hex(signSrc.toString() + mercKey);
reqMap.put("sign", sign);
LOGGER.info("add md5Sign cost:{}", System.currentTimeMillis() - startTime);
return sign;
}
对称密钥
对称密钥的优点是速度快,但是安全性低,所以一般只用在信息类交易中,原因同样是不会有资金损失并且速度快,性能高,我们以支付清算协会身份鉴权为例,使用AES算法过程如下:
- 对称密钥由双方共同约定
- 原始报文使用密钥经过aes算法加密得到加密报文
- 机构把加密报文发送给银行
- 银行接收到报文后,使用双方约定的密钥进行aes解密操作,解密成功则可以继续业务处理,解密不成功则交易失败
aes加密java实现代码如下:
/**
* AES加密
* @param data
* @return
* @throws Exception
*/
public static String encryptData(String data, SecretKeySpec key) throws Exception {
Cipher cipher = Cipher.getInstance(EncryptModeEnum.AES_ECB_PKCS5Padding.getCode()); // 创建密码器
cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
return Base64Utils.encodeToString(cipher.doFinal(data.getBytes()));
}
非对称密钥
非对称密钥的特点是安全性高但是速度慢,通常我们在资金类的交易中使用来确保资金安全,下面我们 以银联微信扫码交易为例,过程如下:
- 密钥互换,机构把公钥交给银行,而银行也把公钥交给机构
- 原始报文使用银行公钥进行加密,再使用机构私钥对报文密文加签,最后把报文密文和签名数据发送给银行
- 银行接收密文和签名数据,使用机构公钥验签,验签成功后再使用银行私钥解密密文得到明文
机构使用银行提供的公钥加密,使用己方的私钥加签,交易报文发送到银行端,银行使用自己的私钥解密,使用机构提供的公钥验签,因为只有私钥才能解密配对公钥加密的数据,所以保证了交易数据的安全,而私钥只有一个,如果银行使用机构的公钥验签成功则说明此交易是机构发送的,这样既做到了报文安全又做到了防抵赖
综上所述,使用非对称密钥保证了对外通信的交易安全,但此方案不是一个完美的方案,对称密钥安全性低但性能高,非对称密钥安全性高单性能低,那么有没有一种算法既安全性高又性能高呢,下面我们讨论下对称+非对称组合的方式
对称+非对称密钥
对称+非对称组合方式的设计思想是尽量使用非对称密钥加密更重要,数据量更小的数据,但是交易中敏感信息是固定的不会减少,怎么办呢?答案就是我们用非对称密钥的公钥加密对称密钥,再用对称密钥加密报文,对端接收到报文后再用私钥解密出对称密钥,再用对称密钥解密报文,这样既安全性高,又提升了性能 下面我们以网联协议支付交易为例,过程如下: 加密加签过程:
- 生成随机串作为对称密钥
- 使用对称密钥串加密敏感字段生成报文密文
- 使用银行公钥加密对称密钥串生成密钥密文
- 使用机构私钥加签报文密文和密钥密文生成签名数据
解密验签过程
- 使用机构公钥验证签名数据,成功则继续
- 使用银行私钥解密对称密钥密文生成密钥明文
- 使用密钥明文解密报文密文生成明文报文
以aes算法为例,代码示例如下:
public static String getEncryptData(String sensitveStr, String keyStr) throws BusinessException {
if (StringUtils.isBlank(sensitveStr)) {
return StringUtils.EMPTY;
}
byte[] sensitveBytes;
byte[] keyBytes;
String encryptData = null;
try {
sensitveBytes = sensitveStr.getBytes(PubStrEnum.CHARSET_UTF8.getCode());
keyBytes = keyStr.getBytes(PubStrEnum.CHARSET_UTF8.getCode());
encryptData = new String(Base64.encodeBase64((SignUtil
.AESEncryptEpcc(sensitveBytes, keyBytes, "AES",
AES_ECB_PKCS5PADDING))), PubStrEnum.CHARSET_UTF8.getCode());
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw new BusinessException(ByteRetCodeEnum.CN010017, e);
}
return encryptData;
}
以rsa算法为例,代码示例如下:
public static String getEncryKey(String keyStr, String orgCode, String orgSubCode) throws BusinessException {
String encrtptKey;
try {
//加密密钥字符串组成规则:01|密钥字符串 。 01-AES 02-SM4
keyStr = "01" + StringConstant.PIPE + keyStr;
byte[] keyBytes = keyStr.getBytes(PubStrEnum.CHARSET_UTF8.getCode());
encrtptKey = new String(Base64.encodeBase64(SignUtil
.RSAEncrypt(keyBytes, KeyFactory.getPubkey(orgCode, orgSubCode), 2048, 11,
RSA_ECB_PKCS1PADDING)), PubStrEnum.CHARSET_UTF8.getCode());
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw new BusinessException(ByteRetCodeEnum.CN010017, e);
}
return encrtptKey;
}
密钥安全
既然公私钥这么重要,万一泄露了怎么办呢?如果公私钥泄露了落在不法分子手中,那就相当于掌握了对接银行的钥匙,部分交易只需要知道卡号和姓名就能支付和转账,不需要支付密码 所以密钥保管是重中之重,怎样做到密钥安全呢,做到如下几点能大大提升密钥安全
- 密钥接手的人越少越好,最好专人管理,并且管理人操作需要其他人员配合开通权限或者密码才可以操作
- 密钥可以加密后存储,同时需要网络隔离,这样即使有人拿到了密钥也需要解密才能使用
- 每隔一段时间需要替换密钥,这样即使密钥泄露损失也会很小
- 使用登录token配合密钥使用,即使密钥泄露也增加了使用难度