上文《Android加密算法之对称加密AES》提到与对称加密算法相对应的非对称加密RSA,本文正式进入该算法的简单讲解和使用。
非对称加密算法:加密和解密使用不同密钥的加密算法,也称为公私钥加密。不同的秘钥指作公开密钥(publickey)和私有秘钥(privatekey),两个天生一对,密不可分。如果用公钥对数据进行加密,只有用对应的私钥才能解密,如果用私钥对数据进行加密,那么只有用对应的公钥才能解密。
上述可能会给小伙伴们一个很友好的疑问,都可以互相加解密,为什么有公私之分,随便给两个密钥公私身份可以吗?
首先从长度上来比较,私钥长度明显比公钥高不少,这意味着对密钥的记忆难度有着很大差别,另外在逆向破解中,由于该算法特性,长度短的公钥想逆向推导出私钥的代价是相当昂贵的,相反私钥推导出公钥相对来说简单到爆炸,因此我们会本能的想到,长度短的密钥公开给众人使用解开自己的公布的密文,而长度长的密钥由自己私有保管。
其次从非对称加密的设计本意来说:公钥加密,私钥解密;私钥加签,公钥验签。设想A和B两位是好友,A持有公钥PK-A和私钥Sk-A,B持有公钥PK-B和私钥SK-B,PK-A和PK-B都是公开的,全世界人都知道,而SK-A和SK-B分别只有A和B自己知道。分析以下场景:
场景1:A给B发信息,在通信过程中,信息内容可能被非法之徒劫持,如果A没有作加密处理,那就.....后果可想而知,那么A会选择用什么密钥加密?我们逐一排除,选择PK-A?加密完后,只有SK-A解密,这倒好了,劫持者不知道SK-A,无法解密就算了,B也不知道啊,也解不了,PASS掉!选SK-A?OK,那么只能PK-A解密,而B和劫持者都知道PK-A,都能解密,这跟没加密有什么区别?狠狠的PASS掉!选SK-B?你想多了,只有B知道SK-B啊,没得选,只能选PK-B,那么只能用SK-B解密了,劫持者并不知道SK-B,只能干看着密文着急,而B能轻松解密并给查看A给自己发的内容了。
场景2:A对B突发好感,给其写了一封匿名电子情书,B看了后很感动但是以为是C写的,于是向作为好友的A分享自己的喜悦,这时候A着急了,说是自己写的,B就是不相信,以为在逗他。没办法,A只能出大招,高度自信地告诉B把这封信上的一处签名解开就是A的名字,那么问题来了,A用什么密钥来制作签名的?为啥这么自信?PK-B?,全世界人都可以制作这个签名,挂掉!SK-B?没人知道啊,怎么制作啊?毙掉!PK-A?别人也知道啊,也可以制作这个签名啊,那么只能SK-A了,只有A能做这样的签名,这时候B拿A的PK-A验证这个签名,验证的结果正是A的姓名。从此二人过上了愉快的基友生活.....咦。。。。。。。。
相信上面两个场景,会让小伙伴们感悟颇深,无法忘怀,理解为重,理解为重哈。。。
回到主题,之前文章《Android加密算法之对称加密AES》我们知道,对称加密算法具有算法公开、计算量小、加密速度快、加密效率高等优点,非对称加密算法则比之慢数千倍,这一点笔者在做RSA加解密的时候深有感触,这里提醒Android Developer在做非对称加密的时候最好能在子线程中执行,否则容易造成ANR。话说回来,非对称加密算法在保护通信安全方面,具有对称加密难以企及的优势,具体以后文章会更新拓展。
非对称加密主要使用的是RSA算法,Android RSA算法一样沿用java的API,公私钥长度建议为2048位(至少1024及以上,以下已被破解),推荐"RSA/ECB/OAEPWithSHA256AndMGF1Padding"填充方式,否则容易被重放攻击。下面为RSA实现工具类RSAUtils.java :
package cn.icheny.security;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
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.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
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;
import android.util.Base64;
/**
* RSA加密,解密工具
*
* @author Cheny
*/
public class RSAUtils {
// 推荐的RSA加密模式,否则容易被重放攻击
private static final String RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING = "RSA/ECB/OAEPWithSHA256AndMGF1Padding";
private static final String RSA = "RSA";
/**
* 生成秘钥,公钥和私钥
*
* @return Base64编码的公钥和私钥 secretKeys[]{rsaPublicKey,rsaPrivateKey}
*/
public static String[] generateSecretKey() {
String[] secretKeys = null;
try {
// 构建密钥对生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
// 初始化秘钥长度 2048位(512位已被破解,至少用1024及以上)
keyPairGenerator.initialize(2048);
// 生成密钥对,包含公钥私钥
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 公钥对象,RSAPublicKey为PublicKey子接口
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
// 公钥对象,RSAPrivateKey为PrivateKey子接口
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
// 公钥
byte[] publicKeyBytes = rsaPublicKey.getEncoded();
// 私钥
byte[] privateKeyBytes = rsaPrivateKey.getEncoded();
secretKeys = new String[2];
// 对公私钥Base64转码,加解密时需要把转码后的公私钥进行Base64还原
secretKeys[0] = Base64.encodeToString(publicKeyBytes, Base64.DEFAULT);
secretKeys[1] = Base64.encodeToString(privateKeyBytes, Base64.DEFAULT);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return secretKeys;
}
/**
* 公钥加密
*
* @param plaintext 明文
* @param rsaPublicKey 公钥
* @return Base64编码的密文
*/
public static String encryptByPublicKey(String plaintext, String rsaPublicKey) {
try {
// Base64还原公钥
byte[] publicKeyBytes = Base64.decode(rsaPublicKey, Base64.DEFAULT);
// X509编码秘钥规范
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
// 秘钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 还原公钥对象,PublicKey为RSAPublicKey父接口
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
// Cipher对象,指定算法模式
Cipher cipher = Cipher.getInstance(RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING);
// 初始化,指定为加密模式
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 执行加密
byte[] result = cipher.doFinal(plaintext.getBytes("UTF-8"));
return Base64.encodeToString(result, Base64.DEFAULT); // 对密文Base64转码,解密时需要把转码后的密文进行Base64还原
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 公钥解密
*
* @param ciphertext 密文
* @param rsaPublicKey 公钥
* @return Base64编码的密文
*/
public static String decryptByPublicKey(String ciphertext, String rsaPublicKey) {
try {
// Base64还原公钥
byte[] publicKeyBytes = Base64.decode(rsaPublicKey, Base64.DEFAULT);
// X509秘钥编码规范
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
// 秘钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 还原公钥对象,PublicKey为RSAPublicKey父接口
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
// Cipher对象,指定算法模式
Cipher cipher = Cipher.getInstance(RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING);
// 初始化,指定为解密模式
cipher.init(Cipher.DECRYPT_MODE, publicKey);
// Base64还原密文
byte[] cipherBytes = Base64.decode(ciphertext, Base64.DEFAULT);
// 执行解密
byte[] result = cipher.doFinal(cipherBytes);
return new String(result, "UTF-8");
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 私钥加密
*
* @param plaintext 明文
* @param rsaPrivateKey 私钥
* @return Base64编码的密文
*/
public static String encryptByPrivateKey(String plaintext, String rsaPrivateKey) {
try {
// Base64还原私钥
byte[] privateKeyBytes = Base64.decode(rsaPrivateKey, Base64.DEFAULT);
// PKCS8秘钥编码规范
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
// 秘钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 还原公钥对象,PrivateKey为RSAPrivateKey父接口
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
// Cipher对象,指定算法模式
Cipher cipher = Cipher.getInstance(RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING);
// 初始化,指定为加密模式
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
// 执行加密
byte[] result = cipher.doFinal(plaintext.getBytes("UTF-8"));
return Base64.encodeToString(result, Base64.DEFAULT);// 对密文Base64转码,解密时需要把转码后的密文进行Base64还原
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 私钥解密
*
* @param ciphertext 密文
* @param rsaPrivateKey 秘钥
* @return 明文
*/
public static String decryptByPrivateKey(String ciphertext, String rsaPrivateKey) {
try {
// Base64还原私钥
byte[] privateKeyBytes = Base64.decode(rsaPrivateKey, Base64.DEFAULT);
// PKCS8秘钥编码规范
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
// 秘钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 还原公钥对象,PrivateKey为RSAPrivateKey父接口
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
// Cipher对象,指定算法模式
Cipher cipher = Cipher.getInstance(RSA_ECB_OAEP_WITH_SHA256_AND_MGF1_PADDING);
// 初始化,指定为解密模式
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// Base64还原密文
byte[] cipherBytes = Base64.decode(ciphertext, Base64.DEFAULT);
// 执行解密
byte[] result = cipher.doFinal(cipherBytes);
return new String(result, "UTF-8");
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}
个人习惯,代码中注释得很详细,所以就不在此话似唐僧了,与其一边看代码,一边看代码下文的解释让人视觉疲劳,还不如把注释写好,笔者深有体会。文章先到这,日后会继续更新。。。
暂无评论内容