iOS 和 Java 端的 RSA 加密解密

Java 端

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import sun.misc.BASE64Decoder;

import com.sun.org.apache.xml.internal.security.utils.Base64;


public class RSAUtils {

    /**
     * 加密算法RSA
     */
    public static final String KEY_ALGORITHM = "RSA";

    /**
     * 签名算法
     */
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    /**
     * 获取公钥的key
     */
    private static final String PUBLIC_KEY = "RSAPublicKey";

    /**
     * 获取私钥的key
     */
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * <p>
     * 生成密钥对(公钥和私钥)
     * </p>
     * 
     * @return
     * @throws Exception
     */
    public static Map<String, Object> genKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }


    /**
     * <P>
     * 私钥解密
     * </p>
     * 
     * @param encryptedData 已加密数据
     * @param privateKey 私钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] encryptedData, String privatKey)
            throws Exception {
        RSAPrivateKey privateK = null;
         try {
             BASE64Decoder base64Decoder= new BASE64Decoder();
             byte[] buffer= base64Decoder.decodeBuffer(privatKey);
             PKCS8EncodedKeySpec keySpec= new PKCS8EncodedKeySpec(buffer);
             KeyFactory keyFactory= KeyFactory.getInstance("RSA");
             privateK = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
         } catch (NoSuchAlgorithmException e) {
             throw new Exception("无此算法");
         } catch (InvalidKeySpecException e) {
             throw new Exception("私钥非法");
         } catch (IOException e) {
             throw new Exception("私钥数据内容读取错误");
         } catch (NullPointerException e) {
             throw new Exception("私钥数据为空");
         }
         if (privateK == null){
             throw new Exception("解密私钥为空, 请设置");
         }
         Cipher cipher= null;
         try {
             cipher= Cipher.getInstance("RSA/ECB/NOPADDING");
             cipher.init(Cipher.DECRYPT_MODE, privateK);
             byte[] output = cipher.doFinal(encryptedData);
             return output;
         } catch (NoSuchAlgorithmException e) {
             throw new Exception("无此解密算法");
         } catch (NoSuchPaddingException e) {
             e.printStackTrace();
             return null;
         }catch (InvalidKeyException e) {
             throw new Exception("解密私钥非法,请检查");
         } catch (IllegalBlockSizeException e) {
             throw new Exception("密文长度非法");
         } catch (BadPaddingException e) {
             throw new Exception("密文数据已损坏");
         }
    }


    /**
     * <p>
     * 公钥加密
     * </p>
     * 
     * @param data 源数据
     * @param publicKey 公钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, String publicKey)
            throws Exception {
        byte[] keyBytes = Base64.decode(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);

        // 对数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
//        
//        cipher= Cipher.getInstance("RSA");//, new BouncyCastleProvider());  
//        cipher.init(Cipher.ENCRYPT_MODE, publicK);  
//        byte[] output= cipher.doFinal(data);  
//        return output;
    }


    /**
     * <p>
     * 获取私钥
     * </p>
     * 
     * @param keyMap 密钥对
     * @return
     * @throws Exception
     */
    public static String getPrivateKey(Map<String, Object> keyMap)
            throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return Base64.encode(key.getEncoded());
    }

    /**
     * <p>
     * 获取公钥
     * </p>
     * 
     * @param keyMap 密钥对
     * @return
     * @throws Exception
     */
    public static String getPublicKey(Map<String, Object> keyMap)
            throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return Base64.encode(key.getEncoded());
    }

    public static void main(String[] args){
        RsaKeySingleton singleton = RsaKeySingleton.getInstance();
         Map<String, Object> rsaKeyMap = singleton.getRsaKeyMap();

        try {
            String publicKey = RSAUtils.getPublicKey(rsaKeyMap);
            System.out.println("服务端生成公钥\n"+publicKey);
                        String privateKey = RSAUtils.getPrivateKey(rsaKeyMap);
            System.out.println("服务端生成私钥\n"+privateKey);


            String privatekey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMri1ae4r3EId4zJwuVbOTL6Dlu0tLKnGgqFqz974zKlcphncr/ldJfqAPQZXjnAo/ZjC4x7zraxv3Yn7q56MalS4YI41HdDWF+9iBj60eSBM3XwAboJhJyfjFhJXVQgPjofDbaetscrH1vtyETEHa5rJZECHPJIL9z8yJfECZtXAgMBAAECgYAHB8s9j6HMue1inI87YWjfAllDBkUIt8+oM6+WeJWL6L3K2DLawXnD1eEIlGR2fd1wqUf6K0Qjgo9O6d1UgHl+A8PnhTDEs5raL1r2jXaD1esuYcQvI8I7VLmJD0MIFjvU8pf9DWy5AKiVLXP67JzTJWuiIahcD5JuOUMMxYgkQQJBAPN/yjh6JpRyZjMI2L7n3cLlEqdGRyr4AwwGfR2v8MLwHBFRSX0XB7AS/VzNLewVXDSkYCQB2M54dRnXXyhpr6ECQQDVTUiBR0bVgDUJaNQx37wR8l6rE/LuoIrQaOjyLUrfgzDUtDy5t19IXlblnrlwsPxmqdKYnIpSUdzuLxqOq8f3AkA98aRf+bMW0INvODDrX9DqY5v7McWpCK69wGjTxI5bCPoa7bLkP5eVEz+g/BFSnZx3R9mtT0r4O2yOxltOzJQBAkEAyWpx5cmlhKPM8Taw8OuBTz0b0dZDlru4ePYnyc+lHQxnxuLvWLtkWRTkX22TVGt724HPIFbpcrRkt/F41XI2nwJAbWwxIXUKLJe5TG9hUTSokTLxL2vIaktWFBQ155HUXiucb0X++jgiXOStlDUH3FqiVOrOt46nmyjweW7WNMtN5Q==";
            // 在 iOS 端使用公钥加密后的十六进制字符串
            String neskey = "99ce0a31979af129ae9b3f18b4e4a4e56d6c88e4eb657e2d92e771f27decab96fc6423d421daeeac55156bf060dbb70777cbfb004242bbe91e11feefdf70fc1dffe1bf1b3e7a5db254c2be0ca176947134f89f16db300f9d215c8b11725a2274dce09becae7117a4c11572025f2d9bf43bd89a0b6540dccb7e540d750bd1c54a";         

            byte[] neskeyBytes = ByteTools.parseHexStr2Byte(neskey);
            byte[] decryptBytes = RSAUtils.decryptByPrivateKey(neskeyBytes, privatekey);//私钥解密
            String aseKey = new String(decryptBytes,"UTF-8");
            System.err.println("解密后:"+aseKey);

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

运行结果

服务端生成公钥
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjV7rZJZqEnpYTNuo7nr5N4ucFQ+I2d1AVbz9u
wH1HLO5c/SnVT9uZy146OAxp8t77XZWD/q2V6kfC2r3XkoTK1R9zedKMG7BMg8dtqpFM+pyBKF4/
ZvKE1LgEjbgu9d05Jw15ygdbWKiGdLsovB6RJ027iQEGDkUUBVyBF/Lb1QIDAQAB
服务端生成私钥
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKNXutklmoSelhM26juevk3i5wVD
4jZ3UBVvP27AfUcs7lz9KdVP25nLXjo4DGny3vtdlYP+rZXqR8LavdeShMrVH3N50owbsEyDx22q
kUz6nIEoXj9m8oTUuASNuC713TknDXnKB1tYqIZ0uyi8HpEnTbuJAQYORRQFXIEX8tvVAgMBAAEC
gYBEPmIVWU2efPdsnbPtJtyRg+xLWdpIMT8nRWx2JDvzIcWQ2kDBG3U/YyVCI6qkFVGuKaJPHHd/
u/sxYNySFWge/ex1cqmlDJF/+nEhoOiNGpM42cAWNdXfznhwOVpqwnIXv9qRb2fWF84VwllxpS9t
aGClE0Z5fngNrTu00rjmfQJBAOjkINF8lfY35VIiaG0Bl9hghTNhS8nfnaV7KK3Qyrvh3Bw+bqP7
17yoRtdDmy+ny288H674zgpGiponyfuMQmMCQQCzjPDjYJg6aH6RfsLu37Ym0me19p9Rmn5aqgIk
71jP3areTUwSduhfcTkhvhYCQhwx38ys/+GUXWomtSF8cSJnAkBT0X8qRj+mvRlQxUd2LAOdgaeP
BW6EenG/lCLg4+uDUVep5qJlbIeJw7qzUQayUR67qxTwIWXudtL9hmKAoUtnAkEAkNPr7oyY3ewx
w6xrEoFzDSeErlsLMaibqQgfCKnjim/0BrJ03SP61hmfCt+oIxQuZ/BsyVLxI1qiaCyMoTMkmwJA
LY25v3OcrhbVfPzR3EpVvkRCPZ+t3Dj6j5nY1ZNtNXFqQLagV70svSAd0HGr8z+s/WlZtTcT3RUq
TW/RTbs/6w==
解密后:
helloaeshelloaes

iOS端

let message = "helloaeshelloaes"

// 数组转十六进制字符串
    func hexStringFromNSData(bytes: [UInt8])->String{
        var hexString = ""
        for(var i=0;i < bytes.count;i++) {
            var s = NSString(format: "%x", bytes[i])
            if(s.length == 1) {
                s = "0"+(s as String)
            }
            hexString = hexString+(s as String)
        }
        return hexString
    }

    func encryptByString() ->NSString?{
        var publicKey, privateKey: SecKey?

        let parameters = [kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
                          kSecAttrKeySizeInBits as String: 1024]

        // 根据从服务端获取的字符串创建公钥
        let publicKeyString = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCaIOBUFTrCx9kWc80OnpfmF7fa5szu5UWloql6OArXT9fA3vOmWHRxjZlDCA+rkptJSfdUfjd4Gh+gd5FtxDW2wwly7PfbE9wsYgrLgPFENhgCZGN71ftfLuGiN776hVZVNFXM+hATRcsWCvmIOQvGPkZfNC8ApCVjdUw5qsMkYQIDAQAB"


        let rsa = SwiftyRSA()
        publicKey = try! rsa.publicKeyFromPEMString(publicKeyString)

        // 加密
        let blockSize = SecKeyGetBlockSize(publicKey!)
        // 密文
        var messageEncrypted = [UInt8](count: blockSize, repeatedValue: 0)
        var messageEncryptedSize = blockSize

        var status: OSStatus!

        status = SecKeyEncrypt(publicKey!, SecPadding.None, message, message.characters.count, &messageEncrypted, &messageEncryptedSize)

        if status != noErr {
            print("Encryption Error!")
            return nil
        }

        let encryptedHexString = self.hexStringFromNSData(messageEncrypted)
        print("密文:\(encryptedHexString)")
        return encryptedHexString
    }

加密结果:

密文:0d73acec08018f1b05598f3970459ae539543f7ffc5d92d1740e5217b9f9ece9d4f3b7d36a3fa57da80d0a18bbff3757e2afa6561c322af311b89de32e2c5c0bdd9f9a6533608d7c58bc71b87906c655a15243a87ba8d32452ae7ab7993d19201cec33f28889638f88f44003003130e5be8db7e65a48daf553354ecf9a572581

注意

  1. 公钥和密钥是成对出现的,统一在服务端生成。
  2. 首先服务端将生成的公钥字符串,这里是结果 Base64编码后的,发给 iOS 端
  3. iOS 端使用收到的公钥将要加密的内容加密,发给服务端
  4. 服务端将收到的密文使用之前生成的密钥解密

遇到的问题

反过来Java 端使用 iOS 端生成的公钥加密时会报错:

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence

原因是可能是因为生成的公钥不是pksc8的格式,需要使用openssl的命令转换一下,然后再提取去除头和尾以及换行符后字符串作为java版用的rsa公钥

这个是私钥的参考,还没测试

openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt

找到了BBRSACryptor 这个 oc 的实现,而且是一个完整的例子。使用这个例子生成的公钥给 Java 端去加密,再回来解密是可以的。还有另外几个可以再试一下:https://github.com/search?utf8=✓&q=BBRSACryptor&type=Repositories&ref=searchresults

另外,还需要寻找可以在 swift 中使用 openssl 的第三方库

参考资料

SwiftyRSA
InvalidKeySpecException : algid parse error, not a sequence
RSA加密介绍
Heimdall
How do I export a public key that was generated using SecKeyGeneratePair to be used on a server?
Seckey from public key string from server in Swift
RSA Encrypt and Decrypt in IOS and JAVA
Generate base64 url-encoded X.509 format 2048-bit RSA public key with Swift? 使用Heimdall实现
RSA: encrypt in iOS, decrypt in Java 仅供参考
java与IOS之间的RSA加解密
pure swift for RSA fuction
Java中使用OpenSSL生成的RSA公私钥进行数据加解密
iOS RSA加密提升篇(分段加密)
iOS中使用RSA对数据进行加密解密

2016-07-25 14:31506