运之盟开放平台


开放平台-请求加解密

<h1>4.签名加解密</h1> <p>在Java中,签名和加解密是常见的安全操作,用于确保数据的完整性和保密性以及解决接口安全上的欺 骗和否认问题。本文档将介绍如何在Java中执行签名和加解密操作。</p> <h2>4.1 加密器</h2> <p>我们使用 ECC(椭圆曲线非对称加密) 作为签名以及加解密方式,相比传统RSA加密有更短的密文和签 名,与更高的安全级别以及性能。</p> <ul> <li>依赖库:</li> </ul> <pre><code> &amp;lt;!-- ECC 加密器 --&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.bouncycastle&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;bcprov-jdk15on&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;1.70&amp;lt;/version&amp;gt; &amp;lt;!-- 选择适合的版本 --&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;!-- fast json --&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;com.alibaba&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;fastjson&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;!-- hutool 工具包 --&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;cn.hutool&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;hutool-all&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt; </code></pre> <h2>4.2 密钥交换</h2> <p><code>密钥交换是一种协议或机制,用于安全地共享密钥,以便在加密通信中使用。</code></p> <p><code>客户端和服务端各持有一对ECC密钥对, 并且互相向对方发送公钥。</code> </p> <p><code>客户端私钥(sign_secret_key:)用于生成签名, 服务端使用对应公钥(sign_public_key:)进行验证。</code></p> <p><code>服务端公钥(ivs_public_key)用于客户端对敏感数据加密(如身份证, 密码等), 服务端使用私钥进行解密。</code></p> <h3>如何使用ECC生成密钥</h3> <pre><code> /** * 生成ECC密钥对。 */ @SneakyThrows public static Pair&amp;lt;String, String&amp;gt; generateECCKeyPair() { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(&amp;quot;EC&amp;quot;); ECGenParameterSpec ecSpec = new ECGenParameterSpec(&amp;quot;secp256r1&amp;quot;); keyPairGenerator.initialize(ecSpec, new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // key :公钥 value : 私钥 // 为了方便持久化密钥, 使用Base64 对密钥进行转换String。 // 当使用真实的密钥时, 需要从 Base64 String 转换回密钥 // 转 base64 return Pair.of(Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()), Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded())); } /** * 字符串到私钥 * @param keyStr 关键str * @return {@link PrivateKey} * @throws Exception 异常 */ public static PrivateKey privateKeyFromString(String keyStr) { byte[] decodedKey = Base64.getDecoder().decode(keyStr); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance(&amp;quot;EC&amp;quot;); return keyFactory.generatePrivate(spec); } /** * 来自字符串公钥 * * @param keyStr 关键str * @return {@link PublicKey} */ public static PublicKey publicKeyFromString(String keyStr) { byte[] decodedKey = Base64.getDecoder().decode(keyStr); X509EncodedKeySpec spec = new X509EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance(&amp;quot;EC&amp;quot;); return keyFactory.generatePublic(spec); } </code></pre> <h2>4.3 签名</h2> <p>数字签名是一种用于验证数据完整性和来源的技术,通常用于解决接口安全上的。我们可以使用以下步 骤进行ECC签名</p> <h3>签名请求规则</h3> <ul> <li>请求方法: POST</li> <li>请求头:</li> </ul> <p><code>Content-Type : application/json</code></p> <p><code>X-Nonce : 18位long类型的随机数, 用于生成签名和防止重放攻击。</code></p> <p><code>X-Timestamp : 当前时间戳(13位,精确到毫秒), 用于生成签名和防止重放攻击。</code></p> <p><code>X-Signature : 签名</code></p> <h3>签名生成规则</h3> <ul> <li>签名所需要的参数如下:</li> </ul> <p><code>data 请求体body, json 类型, 需要将 json 内参数按照字典序( a-z )的方式进行排序后使用其 byte[] 进行生成签名。</code></p> <p><code>nonce : 18位long类型的随机数, 和 请求头 X-Nonce 保持一致。</code></p> <p><code>timestamp : 当前时间戳(13位,精确到毫秒), 和 请求头 X-Timestamp 保持一致。</code></p> <h3>生成签名示例代码如下:</h3> <pre><code> /** * 签名 * @param skStr 私钥字符串(sign_secret_key) * @param data 数据 * @param nonce 现时标志 * @param timestamp 时间戳 * @return {@link String} base64 的签名 * @throws NoSuchAlgorithmException 没有这样算法例外 * @throws InvalidKeyException 无效密钥异常 * @throws SignatureException */ @SneakyThrows public static String sign(String skStr, byte[] data, long nonce, long timestamp) { // 私钥 base64 String 转真实 私钥 PrivateKey privateKey = privateKeyFromString(skStr); Signature signature = Signature.getInstance(&amp;quot;SHA256withECDSA&amp;quot;); signature.initSign(privateKey); // 将原始数据、时间戳和nonce结合在一起进行签名 signature.update(data); signature.update(ByteUtil.longToBytes(timestamp)); signature.update(ByteUtil.longToBytes(nonce)); // 使用Base64 将真实签名转换位字符串 return Base64.getEncoder().encodeToString(signature.sign()); } </code></pre> <h3>Nonce 生成示例代码如下:</h3> <pre><code> public static long generateNonce() { long minimum = 100000000000000000L; // 10^17 long maximum = 999999999999999999L; // Just below 10^18 return minimum + ((long) (new SecureRandom().nextDouble() * (maximum - minimum))); }</code></pre> <h3>json 排序示例代码如下(基于 fastjson):</h3> <pre><code> /** * 按键排序json * 不要碰这个代码!谁碰我杀谁 * @param jsonStr json str * @return {@link String} */ public static String sortJsonByKey(String jsonStr) { Object json = JSON.parse(jsonStr); Object sortedJson = sortObjectByKey(json); return JSON.toJSONString(sortedJson); } /** * 递归 按键排序json对象 * 不要碰这个代码!谁碰我杀谁 * @param json json * @return {@link Object} */ private static Object sortObjectByKey(Object json) { if (json instanceof JSONObject) { JSONObject jsonObject = (JSONObject) json; Map&amp;lt;String, Object&amp;gt; sortedMap = new LinkedHashMap&amp;lt;&amp;gt;(); jsonObject.keySet().stream() .sorted() // 默认的字符串排序,即字典序 (a-z) .forEach(key -&amp;gt; sortedMap.put(key, sortObjectByKey(jsonObject.get(key)))); return sortedMap; } else if (json instanceof JSONArray) { JSONArray jsonArray = (JSONArray) json; for (int i = 0; i &amp;lt; jsonArray.size(); i++) { jsonArray.set(i, sortObjectByKey(jsonArray.get(i))); } return jsonArray; } return json; }</code></pre> <h3>签名生成示例:</h3> <pre><code> public static void main(String[] args) { // 植入三方ecc 加密库 Security.addProvider(new BouncyCastleProvider()); // 获取验签秘钥 Pair&amp;lt;String, String&amp;gt; keyPairSign = generateECCKeyPair(); System.out.println(&amp;quot;keyPairSign: &amp;quot; + keyPairSign.getKey() + &amp;quot;\n keyPairSignValue :&amp;quot; + keyPairSign.getValue()); long nonce = generateNonce(); long timestamp = System.currentTimeMillis(); System.out.println(&amp;quot;nonce: &amp;quot; + nonce + &amp;quot;\n timestamp : &amp;quot; + timestamp); String json = &amp;quot;{\n&amp;quot; + &amp;quot; \&amp;quot;idCardNum\&amp;quot;: \&amp;quot;123123\&amp;quot;,\n&amp;quot; + &amp;quot; \&amp;quot;password\&amp;quot;: \&amp;quot;123123\&amp;quot;,\n&amp;quot; + &amp;quot; \&amp;quot;username\&amp;quot;: \&amp;quot;123123\&amp;quot;\n&amp;quot; + &amp;quot;}&amp;quot;; String sortJson = sortJsonByKey(json); System.out.println(sortJson); String sign = sign(keyPairSign.getValue(), sortJson.getBytes(), nonce, timestamp); System.out.println(&amp;quot;sign: &amp;quot; + sign); }</code></pre> <h1>5. 敏感数据加解密(包括你方请求参数和我方回调参数)</h1> <ul> <li> <p>加解密用于保护敏感数据的机密性, 也可以一定程度上解决接口安全的窃听和伪造问题。</p> </li> <li> <p>我们可以使用 以下步骤进行ECC加解密:</p> </li> <li>客户端使用服务端公钥对请求中的<strong>敏感字段进行加密</strong>,得到新的请求包体,注意是<strong>对敏感字段加密</strong>,而 非整个原始请求。 </li> </ul> <p>目前的敏感字段如下:(可能会新增)</p> <pre><code> idCardNum phone password idCardNo idCard qualificationCardNo bankIdCardNo legalPersonIdCard bankPhone adminPhone financeContactTel businessContactTel userPhone payeePhone driverPhone oldPayPwd newPayPwd confirmPayPwd</code></pre> <h2>5.1 加密方法示例如下:</h2> <pre><code> @SneakyThrows public static String encrypt(String pkStr, byte[] plaintext) { PublicKey publicKey = publicKeyFromString(pkStr); Cipher cipher = Cipher.getInstance(&amp;quot;ECIES&amp;quot;); cipher.init(Cipher.ENCRYPT_MODE, publicKey); return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext)); }</code></pre> <h2>5.2 加密代码示例如下:</h2> <pre><code> public static void main(String[] args) { // 植入三方ecc 加密库 Security.addProvider(new BouncyCastleProvider()); // 获取ecc加解密秘钥 Pair&amp;lt;String, String&amp;gt; keyPairCrypto = generateECCKeyPair(); System.out.println(&amp;quot;keyPairCryptoKey : &amp;quot; + keyPairCrypto.getKey() + &amp;quot;\nkeyPairCryptoValue :&amp;quot; + keyPairCrypto.getValue()); String json = &amp;quot;{\n&amp;quot; + &amp;quot; \&amp;quot;idCardNum\&amp;quot;:\&amp;quot;&amp;quot; + encrypt(keyPairCrypto.getKey(), &amp;quot;123456&amp;quot;.getBytes()) + &amp;quot;\&amp;quot;,\n&amp;quot; + &amp;quot; \&amp;quot;password\&amp;quot;:\&amp;quot;&amp;quot; + encrypt(keyPairCrypto.getKey(), &amp;quot;123456&amp;quot;.getBytes()) + &amp;quot;\&amp;quot;,\n&amp;quot; + &amp;quot; \&amp;quot;username\&amp;quot;:\&amp;quot;123456\&amp;quot;\n&amp;quot; + &amp;quot;}&amp;quot;; System.out.println(json); }</code></pre>

页面列表

ITEM_HTML