加密与安全
一、编码算法
最常见的编码算法有 ASCLL 编码、Unicode编码、UTF-8编码
1、URL编码
URL 编码是浏览器发送数据给服务器时使用的编码
(1)URL 编码的规则
- 如果字符是 A-Z,a-z,0-9,以及 -、_、.、*,则保持不变
- 如果是其他字符,先转成为 UTF-8 编码,然后对每个字节以 ==%xx==表示
例如:字符"中"的UTF-8编码是0xe4b8ad,因此,它的URL编码是%E4%B8%AD。URL编码总是大写。
(2)代码示例
// URL编码只需要对中文进行解码
// 解码时,会从“%”开始识别,并进行解码
public class Main {
public static void main(String[] args) {
String decoded = URLDecoder.decode("%E4%B8%AD%E6%96%87%21", "utf-8");
System.out.println(decoded);
}
}
特别注意:
URL编码是编码算法,不是加密算法。URL编码的目的是把任意文本数据编码为%前缀表示的文本,编码后的文本仅包含AZ,az,0~9,-,_,.,*和%,便于浏览器和服务器处理。
2、Base64编码
(1)Base64编码规则
- Base64编码设计对二进制数据进行编码,表示成文本格式
- 可以把任意长度的二进制数据变为纯文本
- 纯文本内容中只包含指定字符A-Z、a-z、0~9、+、/、=
- 它的原理是把3字节的二进制数据按6bit一组,用4个int整数表示,然后查表,把int整数当做索引,在表中进行查找,得到编码后的字符串
(2)基于Base64进行编码或解码
// 原字节数组
byte[] buff = { 76, 88, 98, 12, 34, -45 };
// 基于Base64进行编码
String encodeToString = Base64.getEncoder().encodeToString(buff);
System.out.println(encodeToString);
// 基于Base64进行解码
byte[] decode = Base64.getDecoder().decode(encodeToString);
System.out.println(Arrays.toString(decode));
方法说明
- getEncoder() :从Base64类中获取编码器
- encodeToString(str):将 str 编码并将结果转变成字符串
- getDecoder():从Base64类中获取解码器
- decode(str):将指定内容进行解码并返回一个字节数组
(3)案例
将图片进行编码并解码
// 将图片进行编码并写入文本文件
byte[] readAllBytes = Files.readAllBytes(Paths.get("E:\\APESOURCE\\image\\3.jpg"));
String encode = Base64.getEncoder().encodeToString(readAllBytes);
Files.write(Paths.get("E:\\APESOURCE\\image\\3copy.txt"),encode.getBytes());
// 将文本文件进行解码并还原成图片
byte[] readAllBytes2 = Files.readAllBytes(Paths.get("E:\\APESOURCE\\image\\3copy.txt"));
String base64Img=new String(readAllBytes2);
byte[] decode = Base64.getDecoder().decode(readAllBytes2);
Files.write(Paths.get("E:\\APESOURCE\\image\\3copy.jpg"),decode);
(4)基于URL方式进行Base64继续编码处理
若使用Base64对URL进行编码,URL编码中没有“/”,二Base64编码中存在"/",所以需要使用getUrlEncoder()解码器
String encodeToString = Base64.getUrlEncoder().encodeToString(buff);
System.out.println("编码后:"+encodeToString);
byte[] decode1 = Base64.getUrlDecoder().decode(encodeToString);
System.out.println("解码后:"+Arrays.toString(decode1));
(5)小结
- URL编码和Base64编码都是编码算法,它们不是加密算法;
- URL编码的目的是把任意文本数据编码为%前缀表示的文本,便于浏览器和服务器处理;
- Base64编码的目的是把任意二进制数据编码为文本,但编码后数据量会增加1/3。
二、哈希算法
1、作用
哈希算法(Hash)又称为摘要算法(Digest),他的作用就是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要(输出结果)
哈希算法的目的就是:为了验证原始数据是否被篡改
2、特点
- 相同的输入一定得到相同的输出
- 不同的输出大概率得到不同的输出
3、哈希碰撞
哈希碰撞是指:两个不同的输入得到了相同的输出
"AaAaAa".hashCode(); // 0x7460e8c0
"BBAaBB".hashCode(); // 0x7460e8c0
"通话".hashCode(); // 0x11ff03
"重地".hashCode(); // 0x11ff03
==说明:==哈希碰撞永远不可能避免,哈希算法是把一个无限的输入集合映射到一个有限的输出集合中,所以必然会产生碰撞
所以一个安全的哈希算法必须满足:
- 碰撞概率低
- 不能猜测输出,即输出不能有规律
4、常用的哈希算法
算法 | 输出长度(位) | 输出长度(字节) |
---|---|---|
MD5 | 128 bits | 16 bytes |
SHA-1 | 160 bits | 20 bytes |
RipeMD-160 | 160 bits | 20 bytes |
SHA-256 | 256 bits | 32 bytes |
SHA-512 | 512 bits | 64 bytes |
- 这些算法均由Java标准库提供,所以可以直接通过统一的接口调用即可
以 MD5 算法为例
// 原始密码
String password="coco";
// 创建基于MD5算法的信息摘要对象
MessageDigest md5 = MessageDigest.getInstance("MD5");
// 只要原始数据的顺序内容一样,则加密后的结果相同
md5.update(password.getBytes());
// 计算加密结果
String ret=HashTool.bytesToHex(md5.digest());
System.out.println(ret);
- 若要使用其他编码方式,则只需要改**MessageDigest.getInstance(“MD5”);**参数即可
- md5.update():更新原始数据(即向对象中添加需要加密的内容)
- md5.digest():调用此方法得到加密后的结果,返回值为byte[]
由于加密后的返回值为byte[] ,为了更方便的观察,可以将其转换为16进制的字符串
public static String bytesToHex(byte[] bytes) {
StringBuilder ret = new StringBuilder();
for (byte b : bytes) {
// 将字节值转换为2位十六进制字符串
ret.append(String.format("%02x", b));
}
return ret.toString();
}
- “%02x”:表示2位十六进制,如果不够 则补0
创建一个工具类,方便后期调用
public class HashTool {
private static MessageDigest digest;
private HashTool() {
}
// 通过MD5进行信息摘要计算(哈希计算)
public static String digestByMD5(String source) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("MD5");
return handler(source);
}
// 通过SHA-1进行信息摘要计算(哈希计算)
public static String digestBySHA1(String source) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("SHA-1");
return handler(source);
}
// 通过消息摘要对象,处理加密内容
private static String handler(String source) {
digest.update(source.getBytes());
byte[] bytes = digest.digest();
String hash = HashTool.bytesToHex(bytes);
return hash;
}
// 将字节数组转换为16进制字符串
public static String bytesToHex(byte[] bytes) {
StringBuilder ret = new StringBuilder();
for (byte b : bytes) {
ret.append(String.format("%02x", b));
}
return ret.toString();
}
}
5、案例
(1)按照MD5算法对图片进行“加密”
// 图片原始字节内容
byte[] imageBuf = Files.readAllBytes(Paths.get("E:\\APESOURCE\\image\\3.jpg"));
// 创建基于MD5算法的消息摘要对象
MessageDigest md5 = MessageDigest.getInstance("Md5");
// 将字节内容放入对象中
md5.update(imageBuf);
// 获取加密摘要
byte[] imageDigest = md5.digest();
System.out.println("加密后:"+Arrays.toString(imageDigest));
System.out.println("加密后(十六进制):"+HashTool.bytesToHex(imageDigest));
System.out.println("加密后的长度“"+imageDigest.length);
6、小结
- 哈希算法可用于验证数据完整性,具有防篡改检测的功能;
- 常用的哈希算法有MD5、SHA-1等;
- 用哈希存储口令时要考虑彩虹表攻击。
三、Hmac算法
1、概述
Hmac算法就是一种基于密钥的消息认证码算法,它的全称是 Hash-base Message Authentication,是一种更安全的消息摘要算法,总是配合着某种哈希算法一起使用
2、优点
- Hmac 使用的是key 长度是 64 字节。
- Hmac是标准算法,同样适用于其他哈希算法
- Hmac 输出和原有的哈希算法长度一致
3、基于 HmacMD5 加密算法
public class HmacMD5 {
public static void main(String[] args)
// 获取HmacMD5密钥生成器
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
// 产生密钥
SecretKey secreKey = keyGen.generateKey();
// 打印随机生成的密钥:
byte[] keyArray = secreKey.getEncoded();
StringBuilder key = new StringBuilder();
for(byte bite:keyArray) {
key.append(String.format("%02x", bite));
}
System.out.println(key);
// 使用HmacMD5加密
Mac mac = Mac.getInstance("HmacMD5");
mac.init(secreKey); // 初始化密钥
mac.update("HelloWorld".getBytes("UTF-8"));// 更新原始密码内容
byte[] resultArray = mac.doFinal(); // 进行加密处理,并获取加密结果
StringBuilder result = new StringBuilder();
for(byte bite:resultArray) {
result.append(String.format("%02x", bite));
}
System.out.println(result);
}
}
主要步骤:
- 通过名称 HmacMD5 获取 KeyGenerator 实例;
- 通过 KeyGenerator 创建一个 SecretKey 实例;
- 通过名称 HmacMD5 获取Mac实例;
- 用 SecretKey 初始化Mac实例;
- 对 Mac 实例反复调用 update(byte[]) 输入数据;
- 调用 Mac 实例的 doFinal() 获取最终的哈希值
4、恢复密钥
通过HmacMD5 加密后的数据,如果想跟原来的数据做对比,则需要将 SecretKey 生成的密钥进行恢复,
// 原始密码
String password = "nhmyzgq";
// 通过"秘钥的字节数组",恢复秘钥
byte[] keyByteArray = {126, 49, 110, 126, -79, -5, 66, 34, -122, 123, 107, -63, 106, 100, -28, 67, 19, 23, 1, 23, 47, 63, 47, 109, 123, -111, -27, -121, 103, -11, 106, -26, 110, -27, 107, 40, 19, -8, 57, 20, -46, -98, -82, 102, -104, 96, 87, -16, 93, -107, 25, -56, -113, 12, -49, 96, 6, -78, -31, -17, 100, 19, -61, -58};
// 恢复秘钥
SecretKey key = new SecretKeySpec(keyByteArray,"HmacMD5");
// 加密
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);
mac.update(password.getBytes());
byte[] resultByteArray = mac.doFinal();
StringBuilder resultStr = new StringBuilder();
for(byte b : resultByteArray) {
resultStr.append(String.format("%02x", b));
}
System.out.println("加密结果:" + resultStr);
关于使用 Hmac 算法的验证问题
- 使用 Hmac 算法时需要生产一个密钥,产生的密钥是一个字节数组
- 用这个密钥与原始数据结合,进行加密
- 恢复密钥,就是将一个字节数组封装成密钥对象
- 然后将这个对象与原始密码进行加密
- 产生的结果如果与第一次加密的结果一样,则验证通过,说明Hmac算法产生的密钥与字节数组封装的密钥是同一个
- 反之验证失败
四、BouncyCastle(第三方开源库)
BouncyCastle就是一个提供了很多哈希算法和加密算法的第三方开源库。它提供了Java标准库没有的一些算法,例如,RipeMD160哈希算法。
我们要使用BouncyCastle提供的RipeMD160算法,需要先把BouncyCastle注册一下:
public class Main {
public static void main(String[] args) throws Exception {
// 注册BouncyCastle提供的通知类对象BouncyCastleProvider
Security.addProvider(new BouncyCastleProvider());
// 获取RipeMD160算法的"消息摘要对象"(加密对象)
MessageDigest md = MessageDigest.getInstance("RipeMD160");
// 更新原始数据
md.update("HelloWorld".getBytes());
// 获取消息摘要(加密)
byte[] result = md.digest();
// 消息摘要的字节长度和内容
System.out.println(result.length); // 160位=20字节
System.out.println(Arrays.toString(result));
// 16进制内容字符串
String hex = new BigInteger(1,result).toString(16);
System.out.println(hex.length()); // 20字节=40个字符
System.out.println(hex);
}
}
以上为单向加密算法,不可解密
五、对称加密算法
1、概述
对称加算法就是传统的==用一个密钥进行加密和解密==,例如,常用的 WinZip 和WinRAR 对压缩包的加密和解密,就是使用对称加密算法
2、常用的对称加密算法
算法 | 密钥长度 | 工作模式 | t填充模式 |
---|---|---|---|
DES | 56/54 | ECB/CBC/PCBC/CTR | NoPadding/PKCS5Padding |
AES | 128/192/256 | ECB/CBC/PCBC/CTR | NoPadding/PKCS5Padding/PKCS7Padding |
IDEA | 128 | ECB | PKCS5Padding/PKCS7Padding |
说明:
- 密钥长度直接决定加密强度
- 工作模式和填充模式可以看成是对称加密算法的参数和格式选择
- DES算法由于密钥过短,可以在短时间内被暴力破解,所以现在已经不安全了。
3、使用 AES 加密
AES算法是目前应用最广泛的加密算法。比较常见的工作模式是ECB和CBC。
1、ECB 模式
ECB模式是最简单的AES加密模式,它需要一个固定长度的密钥,固定的明文会生成固定的密文
解密与加密均使用同一个密钥,所以安全性较低
2、AES 加密 + ECB 模式
// AES + ECB
public class Demo01 {
public static void main(String[] args) throws GeneralSecurityException {
// 原文:
String message = "天生我材必有用飞流直下三千尺";
System.out.println("Message(原始信息): " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "1234567890abcdef".getBytes();
// 加密:
byte[] data = message.getBytes();
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted(加密内容): " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
System.out.println("Decrypted(解密内容): " + new String(decrypted));
}
// 加密:
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 创建密码对象,需要传入算法/工作模式/填充模式
Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
// 根据key的字节内容,"恢复"秘钥对象
SecretKey keySpec=new SecretKeySpec(key, "AES");
// 初始化秘钥:设置加密模式ENCRYPT_
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// 根据原始内容(字节),进行加密
return cipher.doFinal(input);
}
// 解密:
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 创建密码对象,需要传入算法/工作模式/填充模式
Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
// 根据key的字节内容,"恢复"秘钥对象
SecretKey keySpec=new SecretKeySpec(key, "AES");
// 初始化秘钥:设置解密模式DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec);
// 根据原始内容(字节),进行解密
return cipher.doFinal(input);
}
}
步骤:
- 根据算法名称/工作模式/填充模式获取Cipher实例;
- 根据算法名称初始化一个SecretKey实例,密钥必须是指定长度
- 使用SerectKey初始化Cipher实例,并设置加密或解密模式
- 传入明文或密文,获得密文或明文
3、CBC 模式
CBC模式,需要一个随机数作为 IV 参数,这样对于同一份明文,每次生成的密文都不同
4、AES 加密 + CBC 模式
//AES + CBC
public class Demo02 {
public static void main(String[] args) throws Exception {
// 原文:
String message = "Hello";
System.out.println("Message(原始信息): " + message);
// 256位密钥 = 32 bytes Key:
byte[] key = "1234567890abcdef1234567890abcdef".getBytes();
// 加密:
byte[] data = message.getBytes();
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted(加密内容): " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
System.out.println("Decrypted(解密内容): " + new String(decrypted));
}
// 加密:
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 设置算法/工作模式CBC/填充
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 恢复秘钥对象
SecretKey keySpec = new SecretKeySpec(key, "AES");
// CBC模式需要生成一个16 bytes的initialization vector:
SecureRandom sr = SecureRandom.getInstanceStrong();// 生成器
byte[] iv = sr.generateSeed(16);
System.out.println("iv字节数组内容:" + Arrays.toString(iv));
System.out.println("iv字节数组长度:" + iv.length);
// 随机数封装成 IvParameterSpec 参数对象
IvParameterSpec ivps = new IvParameterSpec(iv);
// 初始化秘钥:操作模式、秘钥、IV参数
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
// 加密
byte[] data = cipher.doFinal(input);
// IV不需要保密,把IV和密文一起返回:
return join(iv,data);
}
// 解密:
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 把input分割成IV和密文:
byte[] iv=new byte[16];
byte[] data=new byte[input.length-16];
System.arraycopy(input, 0, iv, 0, 16);
System.arraycopy(input, 16, data, 0, data.length);
// 解密:
Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec=new SecretKeySpec(key, "AES");
IvParameterSpec ivps = new IvParameterSpec(iv);
// 初始化秘钥:操作模式、秘钥、IV参数
cipher.init(Cipher.DECRYPT_MODE, keySpec,ivps);
// 解密操作
return cipher.doFinal(data);
}
// 合并数组
public static byte[] join(byte[] bs1, byte[] bs2) {
byte[] r=new byte[bs1.length+bs2.length];
System.arraycopy(bs1, 0, r, 0, bs1.length);
System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
return r;
}
}
步骤与 ECB 模式步骤相似,只需要额外添加一个随机生成的 IV 参数,然后将这个参数加密后与原文合并,解密时也需要将合并的数据拆分,分别获取 IV 参数和加密后的原文
5、小结
- 对称加密算法使用同一个密钥进行加密和解密,常用算法有DES、AES和IDEA等;
- 密钥长度由算法设计决定,AES的密钥长度是128/192/256位;
- 使用对称加密算法需要指定算法名称、工作模式和填充模式。
六、密钥交换算法
1、概述
AES 加密算法,需要将密钥进行传输,在这个过程中存在安全隐患,导致密钥泄露因此,需要使用密钥交换算法:DH
DH算法解决了密钥在双方不直接传递密钥的情况下完成密钥交换,这个神奇的交换原理完全由数学理论支持。
2、步骤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DeTteQGX-1689078676998)(E:\桌面\870edf07cee6af069cffdd61c74ac66.png)]
- Alice 和 Bob 均有自己的私钥
- 将各自的私钥进行计算,会产生一个公钥
- 将各自产生的公钥传给对方
- 然后结合自己的私钥和对方的公钥进行计算,双方可以得到一个相同的共享密钥
- 通过对比最后产生的共享密钥
由于在这个过程中,只有公钥进行传输,私钥并没有参与传输,因此保证了数据在传输过程的安全性
3、代码示例
public class Demo03 {
public static void main(String[] args) {
// Bob和Alice:
Person bob = new Person("Bob");
Person alice = new Person("Alice");
// 各自生成KeyPair: 公钥+私钥
bob.generateKeyPair();
alice.generateKeyPair();
// 双方交换各自的PublicKey(公钥):
// Bob根据Alice的PublicKey生成自己的本地密钥(共享公钥):
bob.generateSecretKey(alice.publicKey.getEncoded());
// Alice根据Bob的PublicKey生成自己的本地密钥(共享公钥):
alice.generateSecretKey(bob.publicKey.getEncoded());
// 检查双方的本地密钥是否相同:
bob.printKeys();
alice.printKeys();
//使用共享密钥,继续dosth
}
}
//用户类
class Person {
public final String name; // 姓名
// 密钥
public PublicKey publicKey; // 公钥
private PrivateKey privateKey; // 私钥
private byte[] secretKey; // 本地秘钥(共享密钥)
// 构造方法
public Person(String name) {
this.name = name;
}
// 生成本地KeyPair:(公钥+私钥)
public void generateKeyPair() {
KeyPairGenerator kpGen;
try {
// 创建DH算法的“密钥对”生成器
kpGen = KeyPairGenerator.getInstance("DH");
kpGen.initialize(512);
KeyPair kp=kpGen.generateKeyPair();
this.privateKey=kp.getPrivate();
this.publicKey=kp.getPublic();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
// 按照 "对方的公钥" => 生成"共享密钥"
public void generateSecretKey(byte[] receivedPubKeyBytes) {
try {
// 从byte[] 恢复PublicKey
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(receivedPubKeyBytes);
// 根据DH算法获取KeyFactory
KeyFactory kf = KeyFactory.getInstance("DH");
// 通过KeyFactory创建公钥
PublicKey recePublicKey = kf.generatePublic(keySpec);
// 创建密钥协议对象(用于密钥协商)
KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
keyAgreement.init(this.privateKey); // 初始化“自己的PrivateKey”
keyAgreement.doPhase(recePublicKey, true); // 根据"对方的PublicKey"
// 生成SecretKey本地密钥(共享公钥)
this.secretKey = keyAgreement.generateSecret();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void printKeys() {
System.out.printf("Name: %s\n", this.name);
System.out.printf("Private key: %x\n", new BigInteger(1, this.privateKey.getEncoded()));
System.out.printf("Public key: %x\n", new BigInteger(1, this.publicKey.getEncoded()));
System.out.printf("Secret key: %x\n", new BigInteger(1, this.secretKey));
}
}
七、非对称加密算法(RSA)
1、概述
非对称加密:加密和解密使用的不是相同的密钥,用户A密钥加密后所得的信息,只能用用户A的解密密钥才能解密。如果知道了其中一个,并不能计算出另外一个。因此如果公开了一对密钥中的一个,并不会危害到另外一个的秘密性质。称公开的密钥为公钥;不公开的密钥为私钥。只有同一个公钥-私钥对才能正常加解密。
非对称加密的缺点:运算速度非常慢,比对称加密要慢很多。
2、案例
小明要加密一个消息发送给小红,他应该首先向小红索取她的公钥,然后,他用小红的公钥加密,把加密结果发送给小红,此文件只能由小红的私钥解开,因为小红的私钥在她自己手里,所以,除了小红,没有任何人能解开此加密结果。
// RSA
public class Demo05 {
public static void main(String[] args) throws Exception {
// 明文:
byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
// 创建公钥/私钥对
Human hong = new Human("小红");
Human ming = new Human("小明");
// 小明使用小红的公钥进行加密
// 1.获取小红的公钥
PublicKey hongPublicKey = hong.getPublicKey();
System.out.println(String.format("小红的public key(公钥): %x", new BigInteger(1, hongPublicKey.getEncoded())));
// 2.使用公钥加密
byte[] encrypted = ming.encrypt(plain, hongPublicKey);
System.out.println(String.format("encrypted(加密): %x", new BigInteger(1, encrypted)));
// 小红使用自己的私钥解密:
// 1.获取小红的私钥,并输出
PrivateKey hongPrivateKey = hong.getPrivateKey();
System.out.println(String.format("小红的private key(私钥): %x", new BigInteger(1, hongPrivateKey.getEncoded())));
// 2.使用私钥解密
byte[] decrypted = hong.decrypt(encrypted);
System.out.println("decrypted(解密): " + new String(decrypted, "UTF-8"));
}
}
// 用户类
class Human {
// 姓名
String name;
// 私钥:
PrivateKey privatekey;
// 公钥:
PublicKey publickey;
// 构造方法
public Human(String name) throws GeneralSecurityException {
// 初始化姓名
this.name = name;
// 生成公钥/私钥对:
KeyPairGenerator kpGen=KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp=kpGen.generateKeyPair();
this.privatekey=kp.getPrivate();
this.publickey=kp.getPublic();
}
// 把私钥导出为字节
public PrivateKey getPrivateKey() {
return this.privatekey;
}
// 把公钥导出为字节
public PublicKey getPublicKey() {
return this.publickey;
}
// 用公钥加密
public byte[] encrypt(byte[] message,PublicKey publickey) throws GeneralSecurityException {
// 使用公钥进行初始化
Cipher cipher=Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publickey); // 使用公钥进行初始化
return cipher.doFinal(message);
}
// 用私钥解密:
public byte[] decrypt(byte[] input) throws GeneralSecurityException {
// 使用私钥进行初始化
Cipher cipher=Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privatekey);
return cipher.doFinal(input);
}
}
RSA的公钥和私钥都可以通过getEncoded()方法获得以byte[]表示的二进制数据,并根据需要保存到文件中。要从byte[]数组恢复公钥或私钥,可以这么写:
byte[] pkData = ...
byte[] skData = ...
KeyFactory kf = KeyFactory.getInstance("RSA");
// 恢复公钥:
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pkData);
PublicKey pk = kf.generatePublic(pkSpec);
// 恢复私钥:
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(skData);
PrivateKey sk = kf.generatePrivate(skSpec);
3、小结
- 非对称加密就是加密和解密使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密;