目录
1 为什么需要对数据加密
军事战争、支付交易等传输的数据都是非常隐私的,一旦这些信息被截获会,会造成巨大的损失,但是如果截获的信息是加密的,而又没有解密的秘钥,对于截获者来说就是一堆乱码,这就是加密最重要的意义。
2 RSA用到的数学原理
欧拉函数
设n为正整数,以 φ(n)表示不超过n且与n互质的正整数个数,称为n的欧拉函数。
欧拉函数是积性函数:如果p,q互质,则有φ(pq) = φ(p)φ(q)
如果p为质数,那么φ(p)=p-1,所以如果p,q都为大于1的质数的话,就有φ(pq)=φ(p)φ(q)=(p-1)(q-1),比如φ(10)=φ(2)φ(5)=(2-1)(5-1)=4。
欧拉定理
如果n,a为正整数,且n,a互质,则有
a^φ(n) % n = 1
例如3和10互质,则有3^φ(10) %10 = 1,即3^4%10=81%10=1。
费小马定理
如果a是不能被质数p整除的正整数,则有 a^(p-1) % p = 1 ,因为根据欧拉函数有φ(p) = p - 1。
比如4^(5-1)%5=256%5=1
模反元素
如果两个正整数a和n互质,那么一定可以找到整数b,使得 ab-1 被n整除,或者说ab被n除的余数是1,即ab % n = 1。这时,b就叫做a的“模反元素”。
比如a=4,n=5,ab%n=4*b%5=1,计算得b=4。
3 秘钥生成原理
1)选取两个不相等的质数P和Q。
P = 3
Q = 11
2)计算P和Q的乘积
N = P*Q = 3*11=33
3)计算N的欧拉函数
φ(N) = φ(PQ) = φ(P)φ(Q) = (P-1)(N-1) = (3-1)(11-1) = 20
4)选取公钥
公钥E需要满足两个条件
a)1 < E < φ(N)
b)E与φ(N)互质
为了计算方便我们取E=3
公钥就是(33,3)
5)计算私钥
套用公式
E * D % φ(N) = 1
3 * D % 20 = 1
计算得到D = 7
私钥就是(33,7)
到此,公钥和私钥就都生成了,那么如果利用这一对秘钥进行加解密呢。
4 加解密原理
加密
加密需要使用公钥(N,E),如果对M进行加密,M需要满足两个条件
1)M是整数,如果不是整数可以使用对应的ASCII值或UNICODE值
2)M必须小于N
加密就是算出下式中的C
C = M^E % N
例如取M=4
C = 4^3 % 33 =31
即可得到4加密后的值为31。
解密
解密就是计算下式中的M
M = C^D % N
M = 31^7 % 33 = 4
即得到M加密之前的值4,这就是使用RSA加解密的原理。加解密流程图如下所示:
5 安全性保证
秘钥的生成一共使用了6个数字,分别是:P、Q、N、φ(N)、E、D。这6个数字中,公钥使用了两个(N,E),这两个值是公开的,其他的都是保密的。如果D被破解,私钥也就被破解了,那么如果知道了N和E如何破解D呢。
1)E * D % φ(N) = 1,所以需要知道φ(N),才能算出D;
2)φ(N) = (P-1)(Q-1),所以需要知道P和Q才能算出φ(N) ;
3)N = P*Q,所以需要对N进行因数分解才能算出P和Q。
但是大整数的因数分解,用现在的计算机水平是非常困难的,秘钥越长,越难破解,目前被破解的最长的RSA秘钥是768个二进制位,所以支付行业使用1024位的RSA秘钥基本安全,2048位的密钥及其安全。
我们可以尝试一下对一个整数做因数分解,代码如下所示:
public class Factorization {
public static void factor(int num) {
double sqr = Math.sqrt(num);
System.out.println("因数分解结果:");
for (int i = 2; i <= sqr; i++) {
if (num % i == 0) {
System.out.print(i + " ");
num /= i;
i--;
}
}
}
}
要对1024位的整数做因数分解,需要运算2^512次,一台2.3GHz的计算机每秒钟计算次数是23亿次,和2^35是同一个数量级,一年有60*60*24*365=3千万秒,和2^29次方是同一个数量级, 那么
2^512/23亿/3千万 ≈ 2^448年
也就是说要对1024位的整数做因数分解,以现在的计算水平需要2^448年才可以。所以使用RSA加密,只要秘钥足够长,是非常安全的。
6 加解密示例
jdk已经帮我们把加解密的方法封装好了,要对数据加密,需要先生成一对秘钥,生成秘钥的方法如下所示:
public class KeyPairGenUtil {
public static int KEY_LENGTH = 1024;//密钥大小
public static String ALGORITHM_TYPE = "RSA";//算法类型
public static Map genKeyPair() throws NoSuchAlgorithmException {
Map<String, String> keyMap = new HashMap<>();//存储公钥和私钥
//为RSA算法创建KeyPairGenerator对象
KeyPairGenerator keyPairGenerator =KeyPairGenerator.getInstance(ALGORITHM_TYPE);
//创建RSA算法可信任的随机数源
SecureRandom secureRandom = new SecureRandom();
//使用随机数源初始化keyPairGenerator对象
keyPairGenerator.initialize(KEY_LENGTH, secureRandom);
//生成密钥对
KeyPair keyPair = keyPairGenerator.genKeyPair();
//获取私钥
PrivateKey privateKey = keyPair.getPrivate();
//获取公钥
PublicKey publicKey = keyPair.getPublic();
//使用base64将私钥和公钥转化为字符串
String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
keyMap.put("privateKey", privateKeyStr);
keyMap.put("publicKey", publicKeyStr);
return keyMap;
}
}
公钥和私钥生成之后就可以对数据进行加解密了,加解密的示例如下所示:
public class RSAUtils {
public static String encrypt(String data, String publicKey) throws Exception {
//base64编码的公钥解析为二进制
byte[] publicKeyByte = Base64.getDecoder().decode(publicKey);
//得到公钥
PublicKey pubKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyByte));
//加密数据
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
//得到加密后的数据
String encryptData = Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes()));
return encryptData;
}
public static String decrypt(String data, String privateKey) throws Exception {
//base64编码的私钥解析为二进制
byte[] privateKeyByte = Base64.getDecoder().decode(privateKey);
//base64解析后的加密数据
byte[] dataByte = Base64.getDecoder().decode(data.getBytes());
//获取私钥
PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyByte));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
//得到解密后的数据
String decryptData = new String(cipher.doFinal(dataByte));
return decryptData;
}
}