转载自:http://blog.csdn.net/jungle_pig/article/details/72621237
说明:关于Base64转码,请参考我的另一篇博文:http://blog.csdn.net/jungle_pig/article/details/71172985
1.RSA简介
RSA是一种非对称加密算法,加密和解密使用不同的密钥。通信双方各握有一对密钥(称为公钥和私钥)中的一把,己方密钥加密的数据,只有对方密钥能够解密。RSA基于一个数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困 难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可提供给任何人使用,私钥则为自己所有,供解密之用。
假使甲方握有公钥,乙方握有私钥,则通信过程如下:
甲方发送数据给乙方。
已方发送数据给甲方。
2. ☆ Android中的使用
产生秘钥对,进而得到公钥和私钥:
- /*
- 产生密钥对
- @param keyLength
- 密钥长度,小于1024长度的密钥已经被证实是不安全的,通常设置为1024或者2048,建议2048
- */
- public static KeyPair generateRSAKeyPair(int keyLength){
- KeyPair keyPair = null;
- try {
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
- //设置密钥长度
- keyPairGenerator.initialize(keyLength);
- //产生密钥对
- keyPair = keyPairGenerator.generateKeyPair();
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- }
- return keyPair;
- }
- KeyPair keyPair = RSAUtil.generateRSAKeyPair(2048);
- PublicKey publicKey = keyPair.getPublic();
- PrivateKey privateKey = keyPair.getPrivate();
先写一个加解密通用方法:
- /*
- 加密或解密数据的通用方法
- @param srcData
- 待处理的数据
- @param key
- 公钥或者私钥
- @param mode
- 指定是加密还是解密,值为Cipher.ENCRYPT_MODE或者Cipher.DECRYPT_MODE
- */
- private static byte[] processData(byte[] srcData, Key key,int mode){
- //用来保存处理结果
- byte[] resultBytes = null;
- try {
- //构建Cipher对象,需要传入一个字符串,格式必须为"algorithm/mode/padding"或者"algorithm/",意为"算法/加密模式/填充方式"
- Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
- //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥
- cipher.init(mode,key);
- //处理数据
- resultBytes = cipher.doFinal(srcData);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (NoSuchPaddingException e) {
- e.printStackTrace();
- } catch (InvalidKeyException e) {
- e.printStackTrace();
- } catch (BadPaddingException e) {
- e.printStackTrace();
- } catch (IllegalBlockSizeException e) {
- e.printStackTrace();
- }
- return resultBytes;
- }
注意:获取Cipher实例时,传入的字符串并不是任意的,例如Cipher.getInstance("RSA"),首先,格式要固定,必须为"algorithm/mode/padding"或者"algorithm",意为"算法/加密模式/填充方式"。其次,需要Android支持该组合模式。如何知道android是否支持呢,Cipher的Api文档给出了答案,下面列出关于RSA的所有合法形式:
好了,咱们分情况进行加解密操作。
情况A:公钥加密,私钥解密。
A - 1 公钥加密:
- /*
- 使用公钥加密数据,结果用Base64转码
- */
- public static String encryptDataByPublicKey(byte[] srcData, PublicKey publicKey){
- byte[] resultBytes = processData(srcData,publicKey,Cipher.ENCRYPT_MODE);
- return Base64.encodeToString(resultBytes,Base64.DEFAULT);
- }
A - 2 私钥解密:
- /*
- 使用私钥解密,返回解码数据
- */
- public static byte[] decryptDataByPrivate(String encryptedData, PrivateKey privateKey){
- byte[] bytes = Base64.decode(encryptedData,Base64.DEFAULT);
- return processData(bytes,privateKey,Cipher.DECRYPT_MODE);
- }
- /*
- 使用私钥进行解密,解密数据转换为字符串,使用utf-8编码格式
- */
- public static String decryptedToStrByPrivate(String encryptedData, PrivateKey privateKey){
- return new String(decryptDataByPrivate(encryptedData,privateKey));
- }
- /*
- 使用私钥解密,解密数据转换为字符串,并指定字符集
- */
- public static String decryptedToStrByPrivate(String encryptedData, PrivateKey privateKey,String charset){
- try {
- return new String(decryptDataByPrivate(encryptedData,privateKey),charset);
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- return null;
- }
情况B:私钥加密,公钥解密。
B - 1 私钥加密:
- /*
- 使用私钥加密,结果用Base64转码
- */
- public static String encryptDataByPrivateKey(byte[] srcData,PrivateKey privateKey){
- byte[] resultBytes = processData(srcData,privateKey,Cipher.ENCRYPT_MODE);
- return Base64.encodeToString(resultBytes,Base64.DEFAULT);
- }
- /*
- 使用公钥解密,返回解密数据
- */
- public static byte[] decryptDataByPublicKey(String encryptedData,PublicKey publicKey){
- byte[] bytes = Base64.decode(encryptedData,Base64.DEFAULT);
- return processData(bytes,publicKey,Cipher.DECRYPT_MODE);
- }
- /*
- 使用公钥解密,结果转换为字符串,使用默认字符集utf-8
- */
- public static String decryptedToStrByPublicKey(String encryptedData,PublicKey publicKey){
- return new String(decryptDataByPublicKey(encryptedData,publicKey));
- }
- /*
- 使用公钥解密,结果转换为字符串,使用指定字符集
- */
- public static String decryptedToStrByPublicKey(String encryptedData,PublicKey publicKey,String charset){
- try {
- return new String(decryptDataByPublicKey(encryptedData,publicKey),charset);
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- return null;
- }
通常,我们通过OpenSSL工具生成密钥对字符串,但是过程有点复杂,做过支付宝支付的小伙伴肯定知道,支付宝支付时需要RSA验签,所以支付宝官方提供了一键生成RSA密钥对的工具,下面对该工具进行介绍,如果你对OpenSSL生成密钥对感兴趣,可查看文档:https://docs.open.alipay.com/291/106130。
3.利用工具一键生成密钥对
访问支付宝官网,进入如下页面,该页面的URL:https://docs.open.alipay.com/291/105971
点击红圈标注的连接,下载工具,并解压:
进入文件夹,双击RSA签名验签工具.bat运行程序:
程序运行界面:
选择密钥格式和密钥长度,点击生成密钥,即可生成密钥对。点击打开密钥文件路径可找到密钥存储位置:
4.将字符串密钥转换为密钥对象
- /*
- 将字符串形式的公钥转换为公钥对象
- */
- public static PublicKey keyStrToPublicKey(String publicKeyStr){
- PublicKey publicKey = null;
- byte[] keyBytes = Base64.decode(publicKeyStr,sBase64Mode);
- X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
- try {
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- publicKey = keyFactory.generatePublic(keySpec);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (InvalidKeySpecException e) {
- e.printStackTrace();
- }
- return publicKey;
- }
- /*
- 将字符串形式的私钥,转换为私钥对象
- */
- public static PrivateKey keyStrToPrivate(String privateKeyStr){
- PrivateKey privateKey = null;
- byte[] keyBytes = Base64.decode(privateKeyStr,sBase64Mode);
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
- try {
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- privateKey = keyFactory.generatePrivate(keySpec);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (InvalidKeySpecException e) {
- e.printStackTrace();
- }
- return privateKey;
- }
- import android.util.Base64;
- import java.io.UnsupportedEncodingException;
- 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.PrivateKey;
- import java.security.PublicKey;
- import java.security.spec.InvalidKeySpecException;
- import java.security.spec.PKCS8EncodedKeySpec;
- import java.security.spec.X509EncodedKeySpec;
- import javax.crypto.BadPaddingException;
- import javax.crypto.Cipher;
- import javax.crypto.IllegalBlockSizeException;
- import javax.crypto.NoSuchPaddingException;
- /**
- * Created by 朱志强 on 2017/5/22.
- */
- public class RSAUtil {
- //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding"
- private static String sTransform = "RSA/NONE/PKCS1Padding";
- //进行Base64转码时的flag设置,默认为Base64.DEFAULT
- private static int sBase64Mode = Base64.DEFAULT;
- //初始化方法,设置参数
- public static void init(String transform,int base64Mode){
- sTransform = transform;
- sBase64Mode = base64Mode;
- }
- /*
- 产生密钥对
- @param keyLength
- 密钥长度,小于1024长度的密钥已经被证实是不安全的,通常设置为1024或者2048,建议2048
- */
- public static KeyPair generateRSAKeyPair(int keyLength){
- KeyPair keyPair = null;
- try {
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
- //设置密钥长度
- keyPairGenerator.initialize(keyLength);
- //产生密钥对
- keyPair = keyPairGenerator.generateKeyPair();
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- }
- return keyPair;
- }
- /*
- 加密或解密数据的通用方法
- @param srcData
- 待处理的数据
- @param key
- 公钥或者私钥
- @param mode
- 指定是加密还是解密,值为Cipher.ENCRYPT_MODE或者Cipher.DECRYPT_MODE
- */
- private static byte[] processData(byte[] srcData, Key key,int mode){
- //用来保存处理结果
- byte[] resultBytes = null;
- try {
- //获取Cipher实例
- Cipher cipher = Cipher.getInstance(sTransform);
- //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥
- cipher.init(mode,key);
- //处理数据
- resultBytes = cipher.doFinal(srcData);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (NoSuchPaddingException e) {
- e.printStackTrace();
- } catch (InvalidKeyException e) {
- e.printStackTrace();
- } catch (BadPaddingException e) {
- e.printStackTrace();
- } catch (IllegalBlockSizeException e) {
- e.printStackTrace();
- }
- return resultBytes;
- }
- /*
- 使用公钥加密数据,结果用Base64转码
- */
- public static String encryptDataByPublicKey(byte[] srcData, PublicKey publicKey){
- byte[] resultBytes = processData(srcData,publicKey,Cipher.ENCRYPT_MODE);
- return Base64.encodeToString(resultBytes,sBase64Mode);
- }
- /*
- 使用私钥解密,返回解码数据
- */
- public static byte[] decryptDataByPrivate(String encryptedData, PrivateKey privateKey){
- byte[] bytes = Base64.decode(encryptedData,sBase64Mode);
- return processData(bytes,privateKey,Cipher.DECRYPT_MODE);
- }
- /*
- 使用私钥进行解密,解密数据转换为字符串,使用utf-8编码格式
- */
- public static String decryptedToStrByPrivate(String encryptedData, PrivateKey privateKey){
- return new String(decryptDataByPrivate(encryptedData,privateKey));
- }
- /*
- 使用私钥解密,解密数据转换为字符串,并指定字符集
- */
- public static String decryptedToStrByPrivate(String encryptedData, PrivateKey privateKey,String charset){
- try {
- return new String(decryptDataByPrivate(encryptedData,privateKey),charset);
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- return null;
- }
- /*
- 使用私钥加密,结果用Base64转码
- */
- public static String encryptDataByPrivateKey(byte[] srcData,PrivateKey privateKey){
- byte[] resultBytes = processData(srcData,privateKey,Cipher.ENCRYPT_MODE);
- return Base64.encodeToString(resultBytes,sBase64Mode);
- }
- /*
- 使用公钥解密,返回解密数据
- */
- public static byte[] decryptDataByPublicKey(String encryptedData,PublicKey publicKey){
- byte[] bytes = Base64.decode(encryptedData,sBase64Mode);
- return processData(bytes,publicKey,Cipher.DECRYPT_MODE);
- }
- /*
- 使用公钥解密,结果转换为字符串,使用默认字符集utf-8
- */
- public static String decryptedToStrByPublicKey(String encryptedData,PublicKey publicKey){
- return new String(decryptDataByPublicKey(encryptedData,publicKey));
- }
- /*
- 使用公钥解密,结果转换为字符串,使用指定字符集
- */
- public static String decryptedToStrByPublicKey(String encryptedData,PublicKey publicKey,String charset){
- try {
- return new String(decryptDataByPublicKey(encryptedData,publicKey),charset);
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- return null;
- }
- /*
- 将字符串形式的公钥转换为公钥对象
- */
- public static PublicKey keyStrToPublicKey(String publicKeyStr){
- PublicKey publicKey = null;
- byte[] keyBytes = Base64.decode(publicKeyStr,sBase64Mode);
- X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
- try {
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- publicKey = keyFactory.generatePublic(keySpec);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (InvalidKeySpecException e) {
- e.printStackTrace();
- }
- return publicKey;
- }
- /*
- 将字符串形式的私钥,转换为私钥对象
- */
- public static PrivateKey keyStrToPrivate(String privateKeyStr){
- PrivateKey privateKey = null;
- byte[] keyBytes = Base64.decode(privateKeyStr,sBase64Mode);
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
- try {
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- privateKey = keyFactory.generatePrivate(keySpec);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (InvalidKeySpecException e) {
- e.printStackTrace();
- }
- return privateKey;
- }
- }
利用生成的密钥对,测试一下工具类:
- public class MainActivity extends AppCompatActivity {
- //字符串公钥,可以直接保存在客户端
- public static final String PUBLIC_KEY_STR = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArDq+LD0E5aRw9O6oElL2jvb7OGxOACxdcZZnvwN4L+Pv3aM4KSGl4Q7zDSAj/ViaQDC6Y0f3GXiAPIoGPcUnIcm/mpiNZ85NHgoYtgwpP4o0nAEarEUu/YPfdzYAVF7ku+azVJPxelbgxQV0tlamKk0H1COHi3nIdgbusaAvEarMZfFMk25MKB03LrWBjJ9ydDFOjvfokigdxvBDmFhyTsgU1QlEsDPKNFqRS+nrDx6z6j5Xpfeq3P59sQJLE3Hd6YGbUxJB4eVDua5KWS6Fw/5mFWfGBQmdMqm4dUEXlCAYr1U6GVtJJ+amSfzwP1U2D5KD7xCy8N3MJRlgsN2iFwIDAQAB";
- //字符串密钥,通常保存在服务器,这里为了方便演示,直接保存在客户端
- public static final String PRIVATE_KEY_STR = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsOr4sPQTlpHD07qgSUvaO9vs4bE4ALF1xlme/A3gv4+/dozgpIaXhDvMNICP9WJpAMLpjR/cZeIA8igY9xSchyb+amI1nzk0eChi2DCk/ijScARqsRS79g993NgBUXuS75rNUk/F6VuDFBXS2VqYqTQfUI4eLech2Bu6xoC8Rqsxl8UyTbkwoHTcutYGMn3J0MU6O9+iSKB3G8EOYWHJOyBTVCUSwM8o0WpFL6esPHrPqPlel96rc/n2xAksTcd3pgZtTEkHh5UO5rkpZLoXD/mYVZ8YFCZ0yqbh1QReUIBivVToZW0kn5qZJ/PA/VTYPkoPvELLw3cwlGWCw3aIXAgMBAAECggEABrWPHPgPjcaXI+N8JqKWukECzlLhwv33cepTBkzjTLJLcM3f7TJDXP4RF8zNuhvOfnundyChjpt0G2ehEJzyhk1uql4Q/B88P9RS3ByjKrd+jyk32cgkKXoOpX00DBVaQbud9siAmqxxuxsYTdYYSQORL4Fm0VcgKQDiIYdE7iIx0G+CTO8ClWKNwQsY82GdEd1DizGVz7p747k5doSiSi6Bu7YHXk9d5kiFeGhBRXO2KQt2ZfyVsRbDuKdyWvpMeRKFE8dsSvgEC1Cli8ThGjPM1PLJYmkWRGwFu+Rorua04u6ss6zqEam08pOm0qzfoKJ7ZvaiIhbecjadRC9qeQKBgQDYBeDxPQu1IwA92KtcazSCGXCk4cf3IqlDnlT/kVTdy5RsVa93mq2KAYSlTOq+6b58qPP5RlNx0kbWZUo4eyqy3s7GHcDI9kkSgljUKUboFNvtD4ROMgJ8f8xauEsKb1MOkS940JTJ4OfdzHfzOLj/DTfyxFl58AJGfUyi7hfJQwKBgQDMGiPvzJPFzvOL+jQPbF3B+ttlJLOAmHpgzlkqlWTD3EQC7EW9AZiuIlk0mgxXMWkULvpn2sem3/RwTbUp6omaz2/vWZE9UXUvLXAMWy44zNNaXUP/rROxvpFXuvD63N2BevHzL4t2GDCO54yrXq5vNkjqRBTee8sfqxpOLP68nQKBgQCqIh8h/6Eb3OAQ1XdIh0pIeH7F7OhPVGYY0jdBPJWpRO+1TtquCQ1KFp4Ajg6Ho5IZnfrgRSntB94wdn+48hAT5fTWBZLS811jjXMmTQgCOoNnNgROjYZ1xTUN8f1vz3OLkn7f2O6F/HLAtYt27CKPBTseINQTfBpep8pWu8vR/wKBgBWB46uPSTsc9bkYYogFiVO5lYjw9yFj7/FnjSnZmEazXU9ZinfCRU6EPBY47Xf6svH3iVeMTGGfU+jJp3+FQX7YwRjdvVpSzSBtj1MeAJ7nppXtIg89M8gVJsex4VbuE0FjrT9NEUsefW9xovckAQmjFMfq6LARJ3Rs2VbHkwhZAoGBANOj4V5tJzcZmoav14WfNTs5EPq+W8ZR73NDaffTq5oWytqYaQoG/haISANySHL5mF+PIZ5lKBRHnzO6u2tk+ir/LjmMqJT5WzhtQqAv8jkogkxzD8nyXtiIyRyf8s/oI2UQwdNWIxqQKLIrqGQ2HCuSC1QquZD1EmuIYjE6/w5j";
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Logger.init("朱志强");
- //获取公钥
- PublicKey publicKey = RSAUtil.keyStrToPublicKey(PUBLIC_KEY_STR);
- //获取私钥
- PrivateKey privateKey = RSAUtil.keyStrToPrivate(PRIVATE_KEY_STR);
- //需要加密的数据
- String clearText01 = "大家好,我是朱志强!";
- //公钥加密结果
- String publicEncryptedResult = RSAUtil.encryptDataByPublicKey(clearText01.getBytes(),publicKey);
- //私钥解密结果
- String privateDecryptedResult = RSAUtil.decryptedToStrByPrivate(publicEncryptedResult,privateKey);
- Logger.d("公钥加密,私钥解密测试:\n"
- + "原文:\n" + clearText01 + "\n"
- + "公钥加密结果:\n" + publicEncryptedResult + "\n"
- + "私钥解密结果:\n" + privateDecryptedResult
- );
- //需要加密的数据
- String clearText02 = "希望大家多多支持我的博客,不足之处还望斧正!";
- //私钥加密结果
- String privateEncryptedResult = RSAUtil.encryptDataByPrivateKey(clearText02.getBytes(),privateKey);
- //公钥解密结果
- String publicDecryptedResult = RSAUtil.decryptedToStrByPublicKey(privateEncryptedResult,publicKey);
- Logger.d("私钥加密,公钥解密测试:\n"
- + "原文:\n" + clearText02 + "\n"
- + "私钥加密结果:\n" + privateEncryptedResult + "\n"
- + "公钥解密结果:\n" + publicDecryptedResult
- );
- }
- }
运行结果如下:
5.RSA的局限性
RSA的原理和使用方式决定了它的安全性之高,不过它依然有自己的短板。首先,RSA加解密比较耗时,必然引起性能下降,特别是大并发的服务端来说,影响更加明显。第二,RSA对要加密的数据的长度也有限制,待加密的字节数不能超过密钥的长度值除以 8 再减去 11(即:KeySize / 8 - 11)。