★ 安全规范
约定:
双方借助[支付宝 RSA 密钥生成工具](https://docs.open.alipay.com/291/105971/)生成公钥私钥对。
1. 下载相应环境工具并安装后即可使用,以 `MAC_OSX` 界面为例,如下图所示:

2. 在生成工具中,密钥长度选择 `RSA2`,密钥格式选择 `PKCS8`。点击`生成密钥`后,工具会自动生成公钥和私钥,如下图所示:

注意:
- 私钥(private key)自己保存,用于签名。
- 公钥(public key)交给对方一份,用于对方验证签名。
- `业务方`使用生成工具获得`私钥A`与`公钥B`,并且将`公钥B`给到`支付系统`。
- `支付系统`使用生成工具获得`私钥X`与`公钥Y`,并且将`公钥Y`给到`业务方`。
- 生成的公钥私钥需妥善保管,避免遗失,不要泄露。
报文机制:
- 报文签名机制
> 首先,对报文中 `sign` 字段之外的所有数据按字段名称的 `ASCII` 升序排序,然后以 `&` 作为连接符将字段值拼接成`待签名串`。
`sign 字段外的其他参数,不论是否为必填项,只要参与请求就必须参与签名。`
> 其次,对`待签名串`使用`自己的私钥`和 `SHA-256` 算法做摘要,得到 `Base64` 编码的`签名`。
> 最后,将`签名`放在 `sign` 字段。
- 报文验签机制
> 首先,获取报文中 `sign` 字段作为`签名`。
> 其次,对报文中 `sign` 字段之外的所有数据按字段名称的 `ASCII` 升序排序,然后以 `&` 作为连接符将字段值拼接成`待验签串`。
> 最后,对`签名`和`待验签串`使用`对方的公钥`和 `SHA-256` 算法做签名校验。
排序代码示例:
```
import org.apache.commons.lang3.StringUtils;
public String mapToPlainText(Map<String, String> params) {
// 排序
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
StringBuilder builder = new StringBuilder();
for (String key : keys) {
String value = params.get(key);
if (StringUtils.isBlank(value)) {
value = "";
}
builder.append(key).append('=').append(value).append('&');
}
String linkString = builder.toString();
// 去除最后一个 &
return StringUtils.substring(linkString, 0, linkString.length() - 1);
}
```
签名代码示例:
```
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
public class RsaUtils {
enum SignType {
RSA2
}
enum SignAlgorithm {
SHA256WithRSA
}
private static final String CHARSET = "UTF-8";
public static String sign(String privateKey, String content) throws Exception {
Signature signature = Signature.getInstance(SignAlgorithm.SHA256WithRSA.name());
PrivateKey priKey = getPrivateKeyFromPKCS8(privateKey);
signature.initSign(priKey);
signature.update(content.getBytes(CHARSET));
byte[] signed = signature.sign();
return Base64.getEncoder().encodeToString(signed);
}
public static boolean check(String publicKey, String content, String sign) throws Exception {
Signature signature = Signature.getInstance(SignAlgorithm.SHA256WithRSA.name());
PublicKey pubKey = getPublicKeyFromX509(publicKey);
signature.initVerify(pubKey);
signature.update(content.getBytes(CHARSET));
return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
}
private static PrivateKey getPrivateKeyFromPKCS8(String key) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.getDecoder().decode(key);
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
}
private static PublicKey getPublicKeyFromX509(String key) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.getDecoder().decode(key);
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
}
}
```