加密与安全(哈希算法)

加密与安全

一、编码算法

最常见的编码算法有 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、常用的哈希算法
算法输出长度(位)输出长度(字节)
MD5128 bits16 bytes
SHA-1160 bits20 bytes
RipeMD-160160 bits20 bytes
SHA-256256 bits32 bytes
SHA-512512 bits64 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);
	}
}

主要步骤:

  1. 通过名称 HmacMD5 获取 KeyGenerator 实例;
  2. 通过 KeyGenerator 创建一个 SecretKey 实例;
  3. 通过名称 HmacMD5 获取Mac实例;
  4. 用 SecretKey 初始化Mac实例;
  5. 对 Mac 实例反复调用 update(byte[]) 输入数据;
  6. 调用 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填充模式
DES56/54ECB/CBC/PCBC/CTRNoPadding/PKCS5Padding
AES128/192/256ECB/CBC/PCBC/CTRNoPadding/PKCS5Padding/PKCS7Padding
IDEA128ECBPKCS5Padding/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);
	}
}

步骤:

  1. 根据算法名称/工作模式/填充模式获取Cipher实例;
  2. 根据算法名称初始化一个SecretKey实例,密钥必须是指定长度
  3. 使用SerectKey初始化Cipher实例,并设置加密或解密模式
  4. 传入明文或密文,获得密文或明文
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)]

  1. Alice 和 Bob 均有自己的私钥
  2. 将各自的私钥进行计算,会产生一个公钥
  3. 将各自产生的公钥传给对方
  4. 然后结合自己的私钥和对方的公钥进行计算,双方可以得到一个相同的共享密钥
  5. 通过对比最后产生的共享密钥

由于在这个过程中,只有公钥进行传输,私钥并没有参与传输,因此保证了数据在传输过程的安全性

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、小结
  • 非对称加密就是加密和解密使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kⅈꫛᧁ269

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值