打通java服务端与IOS客户端RSA+AES双向认证

打通java服务端与IOS客户端RSA+AES双向认证

  • 服务端代码
    • 需要注意的点

服务端代码

talking is cheap,show my code!

AESUtil.java
import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.UUID;

/**

  • @Author: WindyHu

  • @Date: 2019/5/16 17:14

  • @Description:
    */
    public class AESUtil {

    /**

    • 定义偏移量:因为IOS端使用的工具类用到这个。1、需要16个字节的字符串(随机即可);2、各端需要保持一致
      */
      private final static String IV_STRING = “bcdefghijklmnopq”;
      private static final String charset = “UTF-8”;

    /**

    • 生成128位AES秘钥,并Base64编码
    • 此处需要注意,因与IOS端差异,这里只允许使用随机字符串生成key,不允许使用keyGenorator生成;ios端亦是如此。
    • @return
    • @throws Exception
      */
      public static String getAESStrKey() throws NoSuchAlgorithmException, UnsupportedEncodingException {
      UUID uuid = UUID.randomUUID();
      String aesKey = java.util.Base64.getEncoder().encodeToString(uuid.toString().getBytes()).substring(2, 18);
      return aesKey;
      }

    /**

    • 加密
    • @param content
    • @param key
    • @return
    • @throws Exception
      */
      public static String aesEncryptString(String content, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchProviderException {
      byte[] contentBytes = content.getBytes(charset);
      byte[] keyBytes = key.getBytes(charset);
      byte[] encryptedBytes = aesEncryptBytes(contentBytes, keyBytes);
      return byte2Base64(encryptedBytes);
      }

    /**

    • 解密
    • @param source
    • @param key
    • @return
    • @throws Exception
      */
      public static String aesDecryptString(String content, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, NoSuchProviderException {
      byte[] encryptedBytes = base642Byte(content);
      byte[] keyBytes = base642Byte(key);
      byte[] decryptedBytes = aesDecryptBytes(encryptedBytes, keyBytes);
      return new String(decryptedBytes, charset);
      }

    public static byte[] aesEncryptBytes(byte[] contentBytes, byte[] keyBytes) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchProviderException {
    return cipherOperation(contentBytes, keyBytes, Cipher.ENCRYPT_MODE);
    }

    public static byte[] aesDecryptBytes(byte[] contentBytes, byte[] keyBytes) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchProviderException {
    return cipherOperation(contentBytes, keyBytes, Cipher.DECRYPT_MODE);
    }

    /**

    • 执行加密/解密

    • @param contentBytes

    • @param keyBytes

    • @param mode

    • @return

    • @throws UnsupportedEncodingException

    • @throws NoSuchAlgorithmException

    • @throws NoSuchPaddingException

    • @throws InvalidKeyException

    • @throws InvalidAlgorithmParameterException

    • @throws IllegalBlockSizeException

    • @throws BadPaddingException

    • @throws NoSuchProviderException
      */
      private static byte[] cipherOperation(byte[] contentBytes, byte[] keyBytes, int mode) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
      SecretKeySpec secretKey = new SecretKeySpec(keyBytes, “AES”);
      byte[] initParam = IV_STRING.getBytes(charset);
      IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);

      Cipher cipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”);
      cipher.init(mode, secretKey, ivParameterSpec);

      return cipher.doFinal(contentBytes);
      }

    /**

    • 字节数组转Base64编码
    • @param bytes
    • @return
      */
      public static String byte2Base64(byte[] bytes) {
      return Base64.encodeBase64String(bytes);
      }

    /**

    • Base64编码转字节数组
    • @param base64Key
    • @return
    • @throws IOException
      */
      public static byte[] base642Byte(String base64Key) throws IOException {
      return Base64.decodeBase64(base64Key);
      }
      }

RSAUtils.java

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.IOException;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**

  • @Author: WindyHu

  • @Date: 2019/5/16 17:16

  • @Description:
    /
    public class RSAUtil {
    /
    *

    • 生成秘钥对
    • @return
    • @throws Exception
      */
      public static KeyPair getKeyPair() throws Exception {
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(“RSA”);
      keyPairGenerator.initialize(2048);
      KeyPair keyPair = keyPairGenerator.generateKeyPair();
      return keyPair;
      }

    /**

    • 获取公钥(Base64编码)
    • @param keyPair
    • @return
      */
      public static String getPublicKey(KeyPair keyPair){
      PublicKey publicKey = keyPair.getPublic();
      byte[] bytes = publicKey.getEncoded();
      return byte2Base64(bytes);
      }

    /**

    • 获取私钥(Base64编码)
    • @param keyPair
    • @return
      */
      public static String getPrivateKey(KeyPair keyPair){
      PrivateKey privateKey = keyPair.getPrivate();
      byte[] bytes = privateKey.getEncoded();
      return byte2Base64(bytes);
      }

    /**

    • 将Base64编码后的公钥转换成PublicKey对象
    • @param pubStr
    • @return
    • @throws Exception
      */
      public static PublicKey string2PublicKey(String pubStr) throws Exception{
      byte[] keyBytes = base642Byte(pubStr);
      X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
      KeyFactory keyFactory = KeyFactory.getInstance(“RSA”);
      PublicKey publicKey = keyFactory.generatePublic(keySpec);
      return publicKey;
      }

    /**

    • 将Base64编码后的私钥转换成PrivateKey对象
    • @param priStr
    • @return
    • @throws Exception
      */
      public static PrivateKey string2PrivateKey(String priStr) throws Exception{
      byte[] keyBytes = base642Byte(priStr);
      PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
      KeyFactory keyFactory = KeyFactory.getInstance(“RSA”);
      PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
      return privateKey;
      }

    /**

    • 公钥加密
    • @param content
    • @param publicKey
    • @return
    • @throws Exception
      */
      public static byte[] publicEncrypt(byte[] content, PublicKey publicKey) throws Exception{
      Cipher cipher = Cipher.getInstance(“RSA”);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      byte[] bytes = cipher.doFinal(content);
      return bytes;
      }

    /**

    • 私钥解密
    • @param content
    • @param privateKey
    • @return
    • @throws Exception
      */
      public static byte[] privateDecrypt(byte[] content, PrivateKey privateKey) throws Exception{
      Cipher cipher = Cipher.getInstance(“RSA”);
      cipher.init(Cipher.DECRYPT_MODE, privateKey);
      byte[] bytes = cipher.doFinal(content);
      return bytes;
      }

    /**

    • 字节数组转Base64编码
    • @param bytes
    • @return
      */
      public static String byte2Base64(byte[] bytes){
      return Base64.encodeBase64String(bytes);
      }

    /**

    • Base64编码转字节数组
    • @param base64Key
    • @return
    • @throws IOException
      */
      public static byte[] base642Byte(String base64Key) throws IOException {
      return Base64.decodeBase64(base64Key);
      }
      }

HttpEncryptUtil.java
import com.cusc.common.config.exceptionhandle.ResultEnum;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.sf.json.JSONObject;

import java.security.PrivateKey;
import java.security.PublicKey;

/**

  • @Author: WindyHu
  • @Date: 2019/5/16 17:55
  • @Description:
    */

public class HttpEncryptUtil {

/**
 * 服务器加密响应给APP的内容
 *
 * @param appPublicKeyStr
 * @param aesKeyStr
 * @param content
 * @return
 * @throws Exception
 */
public static String serverEncrypt(String content) throws Exception {
    //将Base64编码后的APP公钥转换成PublicKey对象
    PublicKey appPublicKey = RSAUtil.string2PublicKey(KeyUtil.APP_PULICK_KEY_STR);
    //每次都随机生成AES秘钥
    String aesKeyStr = AESUtil.getAESStrKey();
    //用APP公钥加密AES秘钥
    byte[] encryptAesKey = RSAUtil.publicEncrypt(aesKeyStr.getBytes(), appPublicKey);
    //用AES秘钥加密响应内容
    String encryptStr = AESUtil.aesEncryptString(content, aesKeyStr);
    JSONObject result = new JSONObject();
    result.put("ak", RSAUtil.byte2Base64(encryptAesKey).replaceAll("\r\n", ""));
    result.put("ct", encryptStr.replaceAll("\r\n", ""));
    return result.toString();
}




/**
 * 服务器解密APP的请求内容
 *
 * @param content
 * @return
 * @throws Exception
 */
public static String serverDecrypt(String content) {
    JSONObject result = JSONObject.fromObject(content);
    String encryptAesKeyStr = (String) result.get("ak");
    String encryptContent = (String) result.get("ct");
    JSONObject result2 = new JSONObject();
    try {
        //将Base64编码后的Server私钥转换成PrivateKey对象
        PrivateKey serverPrivateKey = RSAUtil.string2PrivateKey(KeyUtil.SERVER_PRIVATE_KEY);
        //用Server私钥解密AES秘钥
        byte[] aesKeyBytes = RSAUtil.privateDecrypt(RSAUtil.base642Byte(encryptAesKeyStr), serverPrivateKey);
        //用AES秘钥解密请求内容
        String request = AESUtil.aesDecryptString(encryptContent,AESUtil.byte2Base64(aesKeyBytes));
        result2.put("ak", new String(aesKeyBytes));
        result2.put("ct", request);
        result2.put("code", ResultEnum.SUCCESS.getCode());
    } catch (Exception e) {
        result2.put("code", ResultEnum.SIGN_ERROR.getCode());
        e.printStackTrace();
    }
    return result2.toString();
}

}

需要注意的点

因为涉及到跨语言,虽然底层算法是一致的,但是中间经过封装过后的工具类还是有些许的差别,稍不注意就会跳坑,很难爬上来!
在我用这套代码时候,RSA两端加密解密很顺利,只要公私钥给对,就可以解,没怎么踩坑。关键问题出在AES这块加密解密,两端解就是互相解不出来,下面我总结了几点主要原因:

  1. AES的key值生成方式: 两端均不允许使用Jar包中或者IOS库中自带的generator去生成key。类似于以下这种是不被允许的:
    public static String genKeyAES() throws Exception{
    KeyGenerator keyGen = KeyGenerator.getInstance(“AES”);
    keyGen.init(128);
    SecretKey key = keyGen.generateKey();
    String base64Str = byte2Base64(key.getEncoded());
    return base64Str;
    }
    只能采用random的方式去生成,上面AESUtil.java中有写出;
  2. **IV值:**因为IOS算法中使用到了这个变量,因此java端也需要有这个变量参与计算,自己定义一个常量和客户端保持一致即可,需要16个字节;
  3. **填充方式:**由于IOS端默认采用的是PKCS7Padding填充,查阅资料发现java端PKCS5Padding和IOS端PKCS7Padding是一致的,因此java端需要采用PKCS5Padding填充。但是默认jdk默认的security库里面是不支持这种填充方式的,需要去oracle官方下载两个对应jdk版本的jar包,替换jdk中security目录下的两个jar即可,我这里给出jdk8的对应jar包下载地址;
    https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
    以上!
    第一次写,写得不好的地方请多谅解!

文章参考:https://www.jianshu.com/p/df828a57cb8f

你可能感兴趣的:(RSA+AES)