★ 安全规范

约定: 双方借助[支付宝 RSA 密钥生成工具](https://docs.open.alipay.com/291/105971/)生成公钥私钥对。 1. 下载相应环境工具并安装后即可使用,以 `MAC_OSX` 界面为例,如下图所示: ![](https://gw.alipayobjects.com/zos/skylark-tools/public/files/5ddd818696ade00b7d1ecccc742e5cc5.jpeg?x-oss-process=image/resize,w_2000) 2. 在生成工具中,密钥长度选择 `RSA2`,密钥格式选择 `PKCS8`。点击`生成密钥`后,工具会自动生成公钥和私钥,如下图所示: ![](https://gw.alipayobjects.com/zos/skylark-tools/public/files/48b5e0535e94193f2301df17a89dd222.jpeg) 注意: - 私钥(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)); } } ```