非对称加密需要两把密钥:公钥和私钥,他们是一对,如果用公钥对数据加密,那么只能用对应的私钥解密。如果用私钥对数据加密,只能用对应的公钥进行解密。因为加密和解密用的是不同的密钥,所以称为非对称加密。
(1) A 要向 B 发送信息,A 和 B 都要产生一对用于加密和解密的公钥和私钥。
(2) A 的私钥保密,A 的公钥告诉 B;B 的私钥保密,B 的公钥告诉 A。
(3) A 要给 B 发送信息时,A 用 B 的公钥加密信息,因为 A 知道 B 的公钥。
(4) A 将这个消息发给 B (已经用 B 的公钥加密消息)。
(5) B 收到这个消息后,B 用自己的私钥解密 A 的消息。其他所有收到这个报文的人都无法解密,因为只有 B 才有 B 的私钥。
目前最有影响力的公钥加密运算,将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有。
以 A 要把信息发给 B 为例,确定角色:A 为加密者,B 为解密者。首先由 B 随机确定一个 KEY,称之为私钥,将这个 KEY 始终保存在机器 B 中而不发出来;然后,由这个 KEY 计算出另一个 KEY,称之为公钥。这个公钥的特性是几乎不可能通过它自身计算出生成它的私钥。接下来通过网络把这个公钥传给 A,A 收到公钥后,利用公钥对信息加密,并把密文通过网络发送到 B,最后 B 利用已知的私钥,就能对密文进行解码。
RSA算法的本质是数学,公钥和私钥是数学上关联的,无须直接传递。
4.RSA算法的过程
注意:
理论上私钥是可以推导出公钥的,公钥无法推导出私钥。
私钥中获取公钥测试地址:http://tool.chacuo.net/cryptgetpubkey
下面将非对称加密的两种方式进行封装处理,达到一套代码实现两种加密方式,代码可以直进行使用:
public class AsymmetricTest {
private final static String RSA = "RSA";
public static void main(String[] args) {
// 加密密文
String input = "key=85CD019515D14B91AD942787532314FF&startTime=1629431243245&endTime=1660967243244";
// 生成密钥对文件,在实际开发中,根据实际需求生成文件位置
String pubPath = "C:\\Users\\Desktop\\publicKey.pub";
String priPath = "C:\\Users\\Desktop\\privateKey.pri";
// 生成公钥和私钥文件,并且打印公钥的字符串和私钥字符串
generateKeyPair(pubPath, priPath);
System.out.println("\n==============================================\n");
// 从文件中加载密钥
PublicKey publicKey = loadPublicKeyFromFile(pubPath);
PrivateKey privateKey = loadPrivateKeyFromFile(priPath);
// 公钥加密,私钥解密
String encrypted = encryptByAsymmetric(input, publicKey);
System.out.println("非对称RSA-公钥加密:" + encrypted);
System.out.println("非对称RSA-私钥解密:" + decryptByAsymmetric(encrypted, privateKey));
System.out.println("\n==============================================\n");
// 私钥加密,公钥解密
String encrypted2 = encryptByAsymmetric(input, privateKey);
System.out.println("非对称RSA-私钥加密:" + encrypted2);
System.out.println("非对称RSA-公钥解密:" + decryptByAsymmetric(encrypted2, publicKey));
}
/**
* 从文件中加载公钥
*
* @param filePath : 文件路径
* @return : 公钥
* @throws Exception
*/
public static PublicKey loadPublicKeyFromFile(String filePath) {
try {
// 将文件内容转为字符串
String keyString = FileUtils.readFileToString(new File(filePath), String.valueOf(StandardCharsets.UTF_8));
return loadPublicKeyFromString(keyString);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("获取公钥文件字符串失败!");
}
}
/**
* 从文件中加载私钥
*
* @param filePath : 文件路径
* @return : 私钥
* @throws Exception
*/
public static PrivateKey loadPrivateKeyFromFile(String filePath) {
try {
// 将文件内容转为字符串
String keyString = FileUtils.readFileToString(new File(filePath), String.valueOf(StandardCharsets.UTF_8));
return loadPrivateKeyFromString(keyString);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("获取私钥文件字符串失败!");
}
}
/**
* 从字符串中加载公钥
*
* @param keyString : 公钥
* @return : 公钥
* @throws Exception
*/
public static PublicKey loadPublicKeyFromString(String keyString) {
try {
// 进行Base64解码
byte[] decode = Base64.decode(keyString);
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 构建密钥规范
X509EncodedKeySpec key = new X509EncodedKeySpec(decode);
// 获取公钥
return keyFactory.generatePublic(key);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("获取公钥失败!");
}
}
/**
* 从字符串中加载私钥
*
* @param keyString : 私钥
* @return : 私钥
* @throws Exception
*/
public static PrivateKey loadPrivateKeyFromString(String keyString) {
try {
// 进行Base64解码
byte[] decode = Base64.decode(keyString);
// 获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 构建密钥规范
PKCS8EncodedKeySpec key = new PKCS8EncodedKeySpec(decode);
// 生成私钥
return keyFactory.generatePrivate(key);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("获取私钥失败!");
}
}
/**
* 打印密钥对并且保存到文件
*
* @return
*/
public static void generateKeyPair(String pubPath, String priPath) {
try {
// 创建密钥对生成器对象
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
// 生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
String privateKeyString = Base64.encode(privateKey.getEncoded());
String publicKeyString = Base64.encode(publicKey.getEncoded());
System.out.println("私钥:" + privateKeyString);
System.out.println("公钥:" + publicKeyString);
// 保存文件
if (pubPath != null) {
FileUtils.writeStringToFile(new File(pubPath), publicKeyString, String.valueOf(StandardCharsets.UTF_8));
}
if (priPath != null) {
FileUtils.writeStringToFile(new File(priPath), privateKeyString, String.valueOf(StandardCharsets.UTF_8));
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("生成密钥对失败!");
}
}
/**
* 非对称加密数据
*
* @param input : 原文
* @param key : 密钥
* @return : 密文
* @throws Exception
*/
public static String encryptByAsymmetric(String input, Key key) {
try {
// 获取Cipher对象
Cipher cipher = Cipher.getInstance(RSA);
// 初始化模式(加密)和密钥
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] resultBytes = getMaxResultEncrypt(input, cipher);
return Base64.encode(resultBytes);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("加密失败!");
}
}
/**
* 非对称解密数据
*
* @param encrypted : 密文
* @param key : 密钥
* @return : 原文
* @throws Exception
*/
public static String decryptByAsymmetric(String encrypted, Key key) {
try {
// 获取Cipher对象
Cipher cipher = Cipher.getInstance(RSA);
// 初始化模式(解密)和密钥
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(getMaxResultDecrypt(encrypted, cipher));
} catch (
Exception e) {
e.printStackTrace();
throw new RuntimeException("解密失败!");
}
}
/**
* 分段处理加密数据
*
* @param input : 加密文本
* @param cipher : Cipher对象
* @return
*/
private static byte[] getMaxResultEncrypt(String input, Cipher cipher) throws Exception {
byte[] inputArray = input.getBytes();
int inputLength = inputArray.length;
// 最大加密字节数,超出最大字节数需要分组加密
int MAX_ENCRYPT_BLOCK = 117;
// 标识
int offSet = 0;
byte[] resultBytes = {};
byte[] cache = {};
while (inputLength - offSet > 0) {
if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
offSet += MAX_ENCRYPT_BLOCK;
} else {
cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
offSet = inputLength;
}
resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
}
return resultBytes;
}
/**
* 分段处理解密数据
*
* @param decryptText : 加密文本
* @param cipher : Cipher对象
* @throws Exception
*/
private static byte[] getMaxResultDecrypt(String decryptText, Cipher cipher) throws Exception {
byte[] inputArray = Base64.decode(decryptText.getBytes(StandardCharsets.UTF_8));
int inputLength = inputArray.length;
// 最大解密字节数,超出最大字节数需要分组加密
int MAX_ENCRYPT_BLOCK = 128;
// 标识
int offSet = 0;
byte[] resultBytes = {};
byte[] cache = {};
while (inputLength - offSet > 0) {
if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
offSet += MAX_ENCRYPT_BLOCK;
} else {
cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
offSet = inputLength;
}
resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
}
return resultBytes;
}
}
效果:
是Schnorr和EIGamal签名算法的变种,基于整数有限域离散对数难题。不单单只有公钥,私钥,还有数字签字。
(1) 使用消息摘要算法将发送数据加密生成数字摘要。
(2) 发送方用自己的私钥对摘要再加密,形成数字签名。
(3) 将原文和加密的摘要同时传给对方。
(4) 接受方用发送方的公钥对摘要解密,同时对收到的数据用消息摘要算法产生同一摘要。
(5) 将解密后的摘要和收到的数据在接收方重新加密产生的摘要相互对比,如果两者一致,则说明在传送过程中信息没有破坏和篡改。否则,则说明信息已经失去安全性和保密性。
ECC、DH算法等其他非对称加密算法,感兴趣的小伙伴可以继续深入研究...