在上一遍Android中常用的加密算法——AES加密中我们介绍了对称加密和非对称加密,对称加密由于加密和解密使用同一个秘钥因此安全性与非对称加密相比要低得多。这一篇我们就来介绍一种被广泛应用的非对称加密——RSA加密。
RSA加密算法
RSA是一种应用十分广泛的非对称加密算法,在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。1973年,在英国政府通讯总部工作的数学家克利福德·柯克斯(Clifford Cocks)在一个内部文件中提出了一个相同的算法,但他的发现被列入机密,一直到1997年才被发表。——维基百科
其算法原理基于很简单的数学知识:既对两个大素数相乘得到其乘积很简单,但对乘积进行因数分解很难,两个大素数组合即为公钥,乘积未秘钥。只要保证两个不想等的素数足够大就可以保证加密足够安全。
算法实现
生成秘钥
2009年12月12日,编号为RSA-768(768 bits, 232 digits)数也被成功分解[10]。这一事件威胁了现通行的1024-bit密钥的安全性,普遍认为用户应尽快升级到2048-bit或以上。——维基百科
Android同样提供了系统类KeyPairGenerator用于辅助生成秘钥。
/**
* 生产秘钥 秘钥长度建议不要小于1024
* @param keyLength 秘钥长度,范围512 —— 2048
* @return the secretKey
* @throws Exception
*/
public static KeyPair generateKey(int keyLength) throws NoSuchAlgorithmException {
// 获取秘钥生成器
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(keyLength);
// 生成秘钥并返回
return keyGenerator.genKeyPair();
}
/**
* 生产秘钥, 默认秘钥长度1024
*
* @return the secretKey
* @throws Exception
*/
public static KeyPair generateKey() throws NoSuchAlgorithmException {
return generateKey(1024);
}
上面的方法可以很便捷的在Android上生成秘钥,但实际开发中因为安全性问题很少在Android端生成秘钥(不排除特殊需求),通常是服务器端将公钥发送给Android端,Android端用公钥进行加密后发送给服务器端,服务器用私钥进行解密。秘钥的保存和传输通常会转换成一串ASCII码如下:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxlSpJwBEM/ia2P5jLTAGSxMexRxSKlmF
gIrPX7g0DsPIrqhMZMleTSLXOMT8D+1+T1UlHfwsJOybpVoliLoXIPIpk62TrJwDMPoXAZXP8AHS
vdcrDawQsypT3ZomUWup9yUoXUhcECqkyOeumfSKSCd1465UzWYJU3rKop/NTPWyELjBPZPRM+7V
X8ymmNLnTu01sCirM8KVVAkYmtZ1S15MnQaMCEyNOJGYHc0ctLcMKGfcesG/o+eapddJ0lvIwxWq
s6GsBTOui1e172SqdLqib8Zw/41WeVCXXIlj/O+b78k4gLM3tZBoAx9jOChRPZXW8cy/5Ug2HCS2
S+wNMwIDAQAB
-----END PUBLIC KEY-----
第一行和最后一行是标注秘钥的开始和结束,在转换为私钥和公钥对象是需要去掉。
//秘钥字符串生成Key需要通过Base64转换为byte[]
publicKey = RSAUtil.getPublicKey(Base64.decode(publicKeyStr, Base64.DEFAULT));
privateKey = RSAUtil.getPrivateKey(Base64.decode(privateKeyStr, Base64.DEFAULT));
转换为秘钥对象是需要先使用Base64类对秘钥字符串进行解码decode,同理如果要将Android端生成的秘钥进行保存或者传输通常也需要用Base64类进行编码encode转换为字符串。
/**
* 将公钥的byte[]数据还原为PublicKey
* @param publicKeyBytes
* @return PublicKey
* @throws Exception
*/
public static PublicKey getPublicKey(byte[] publicKeyBytes) throws Exception{
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
/**
* 将私钥的byte[]数据还原为PrivateKey
* @param privateKey
* @return PrivateKey
* @throws Exception
*/
public static PrivateKey getPrivateKey(byte[] privateKey) throws Exception{
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
注意私钥转换和公钥转换用的类是不同的。
加密
/**
* 用公钥加密,默认填充模式 RSA/ECB/PKCS1Padding
* <br>每次加密的字节数,不能超过密钥的长度值减去11
*
* @param data 需加密数据的byte数据
* @param publicKey 公钥
* @return 加密后的byte型数据
*/
public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
return encrypt(data, publicKey, "RSA/ECB/PKCS1Padding");
}
/**
* 用公钥加密
* <br>每次加密的字节数,不能超过密钥的长度值减去11
*
* @param data 需加密数据的byte数据
* @param publicKey 公钥
* @param transformation 加密模式和填充方式
* @return 加密后的byte型数据
*/
public static byte[] encrypt(byte[] data, PublicKey publicKey, String transformation) throws Exception {
Cipher cipher = Cipher.getInstance(transformation);
// 编码前设定编码方式及密钥
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 传入编码数据并返回编码结果
return cipher.doFinal(data);
}
加密模式和填充方式同样不过多探讨了参考上一篇提到的几篇文章,只要保存加密和解密端保持模式一致就好。
需要注意的是:加密时只能一次性加密秘钥长度减11字节的数据,假设秘钥长度为1024(下同),那么每次只能加密117字节(1024 / 8 - 11),若数据过长请分段加密。
有分段加密同样的也需要分段解密,前面我们说了每次只能加密117字节的数据,而这些数据加密之后的长度正好是秘钥长度1024bit,所以我们在解密时需要对每1024bit数据进行解密。
解密
/**
* 用私钥解密,默认加密模式和填充方式为 RSA/ECB/PKCS1Padding"
*
* @param encryptedData 经过encrypt()加密返回的byte数据
* @param privateKey私钥
* @return
*/
public static byte[] decrypt(byte[] encryptedData, PrivateKey privateKey) throws Exception {
return decrypt(encryptedData, privateKey,"RSA/ECB/PKCS1Padding");
}
/**
* 用私钥解密
*
* @param encryptedData 经过encrypt()加密返回的byte数据
* @param privateKey私钥
* @param transformation 加密模式和填充方式
* @return
*/
public static byte[] decrypt(byte[] encryptedData, PrivateKey privateKey, String transformation) throws Exception {
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encryptedData);
}
注意加密模式和填充方式必须与加密时保持一致。