JavaSE(九)加密与安全

加密与安全

数据安全:防窃听、防篡改、防伪造

编码算法

URL编码

URL编码的目的是把任意文本数据编码为%前缀表示文本
编码规则:

1)A-Z,a-z,0-9以及- _ . *保持不变
2)其他字符以%XX表示
	<:%3C
	中:%E4%B8%AD(UTF-8:0xe4b8ad)
String orginal = "URL 参数";
String encoded = URLEncoder.encode(orginal, "UTF-8");
System.out.println(encoded); // URL+%E5%8F%82%E6%95%B0
String ori = new String(URLDecoder.decode(encoded, "UTF-8"));
System.out.println(ori); // URL 参数

Base64编码

Base64编码的目的是把任意二进制数据编码为文本(长度增加1/3)

String base64Encode(byte[] data)
byte[]{0xe4, 0xb8, 0xad} -> "5Lit"

在这里插入图片描述
Base64编码是一种文本(a-z,A-Z,0-9,+/=)表示二进制内容的方式,适用于文本协议,但是效率会下降,Base64应用于:电子邮件协议
Base64编码长度如果不是3的整数倍,会末尾补0x00或0x00 0x00,编码后加一个等号(=)表示补充了1个字节,编码后加两个等号(==)表示补充了2个字节。

public class Main {
	public static void main(String[] args) throws Exception {
		String original = "Hello\u00ff编码测试";
		String b64 = Base64.getEncoder().encodeToString(original.getBytes());
		System.out.println(b64);
		String ori = new String(Base64.getDecoder().decode(b64), "UTF-8");
		System.out.println(ori);
	}
}
// SGVsbG/Dv+e8lueggea1i+ivlQ==
// Helloÿ编码测试

// 还可以利用withoutPadding()去掉末尾的等号
String b64 = Base64.getEncoder().withoutPadding().encodeToString(original.getBytes());
// SGVsbG/Dv+e8lueggea1i+ivlQ

// 使用URL的Base64编码是 加号(+)变减号(-),反斜线(/)变下划线(_)
String original = "Hello\u00ff编码测试";
		String b64 = Base64.getUrlEncoder().withoutPadding().encodeToString(original.getBytes());
		System.out.println(b64);
		String ori = new String(Base64.getUrlDecoder().decode(b64), "UTF-8");
		System.out.println(ori);
// SGVsbG_Dv-e8lueggea1i-ivlQ
// Helloÿ编码测试

其他类似Base64的编码有:Base32、Base48、Base58

摘要算法

摘要算法(哈希算法 / Hash / Digest / 数字指纹)
计算任意长度数据的摘要,输出固定长度,相同的输入始终得到相同的输出,不同的输入尽量得到不同的输出
Java的Object.hashCode()方法就是一个摘要算法

输入:任意数据
输出:固定长度数据(int,byte[4])
相同的输入得到相同的输出:equals、hashCode

碰撞:两个不同的输入得到了相同的输出
Hash算法的安全性:

1)碰撞率低
2)不能猜测输出
3)输入的任意一个bit的变化会造成输出完全不同
4)很难从输出反推输入(只能穷举)

常用的摘要算法

算法 输出长度
MD5 128bits 16bytes
SHA-1 160bits 20bytes
SHA-256 256bits 32bytes
RipeMD-160 160bits 20bytes

MD5

1)验证原始数据是否被篡改
2)存储用户口令
3)需要防止彩虹表攻击:对每个口令额外添加随机数salt
用途:验证文件的完整性、存储用户口令

md5(inputPassword)
md5(salt + inputPassword)

import java.math.BigInteger;
import java.security.MessageDigest;

public class Main {
	public static byte[] toMD5(byte[] input) {
		MessageDigest md;
		try {
			md = MessageDigest.getInstance("MD5");
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		md.update(input);
		return md.digest();
	}
	public static void main(String[] args) throws Exception {
		String s = "Md5摘要算法测试";
		byte[] r = toMD5(s.getBytes("UTF-8"));
		System.out.println(String.format("%032x", new BigInteger(1, r)));
	}
}
String password = "Hello, world";
String salt = "Random salt";
byte[] r = toMD5((salt + password).getBytes("UTF-8"));
System.out.println(String.format("%032x", new BigInteger(1, r)));

SHA1

是由美国国家安全局开发的一种哈希算法,输出160bits或20bytes,主要有SHA-0 / SHA-1 / SHA-256 / SHA-512,比MD5更加安全

算法 输出长度
SHA-1 160bits 20bytes
SHA-256 256bits 32bytes
SHA-512 512bits 64bytes
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(input); 
...
byte[] result = md.digest(); // 20bytes

BouncyCastle

是第三方算法提供的一组加密/哈希算法,官网:http://www.bouncycastle.org/
如何使用第三方提供的算法

添加第三方jar至classpath
注册第三方算法提供方 Security.addProvider(new BouncyCastleProvider());//注册
正常使用JDK提供的接口
Security.addProvider(new BouncyCastleProvider());//注册
MessageDigest md = MessageDigest.getInstance("RipeMD160");
md.update(input);
...
byte[] result = md.digest(); // 20bytes

Hmac

Hash-based Message Authentication Code,是基于秘钥的消息认证码算法,是一种更安全的消息摘要算法
HmacMd5 ≈ md5(secure_ksy, data);
Hmac是把Key混入摘要的算法

byte data = ...
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
SecretKey skey = keyGen.generateKey();
Mac mac = Mac.getInstance("HmacMD5");
mac.init(skey);
mac.update(data);
byte[] result = mac.doFinal();

可以配合MD5、SHA-1等摘要算法
摘要长度和原摘要算法长度相同

加密算法

对称加密算法

使用同一个密钥进行加密和解密
在这里插入图片描述
常用算法:DES、AES、IDEA等

算法 密钥长度 工作模式 填充模式
DES 56/64 ECB/CBC/PCBC/CTR/… NoPadding/PKCS5Padding…
AES 128/192/256 ECB/CBC/PCBC/CTR/… NoPadding/PKCS5Padding/PKCS7Padding…
IDEA 128 ECB PKCS5Padding/PKCS5Padding…

密钥长度由算法设计决定,AES的密钥长度是128 / 192 / 256
使用对称加密算法需要指定:算法名称 / 工作模式(ECB、CBC、PCBC…)/填充模式(NoPadding、PKCS5Padding、PKCS7Padding…)
ECB模式

public class AES_ECB_Cipher {
	static final String CIPHER_NAME = "AES/ECB/PKCS5Padding";
	/**
	 * 加密
	 */
	public static byte[] encrypt(byte[] key, byte[] input) throws Exception{
		Cipher cipher = Cipher.getInstance(CIPHER_NAME);
		SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
		cipher.init(Cipher.ENCRYPT_MODE, keySpec);
		return cipher.doFinal(input);
	}
	/**
	 * 解密
	 */
	public static byte[] decrpty(byte[] key, byte[] input) throws Exception{
		Cipher cipher = Cipher.getInstance(CIPHER_NAME);
		SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
		cipher.init(Cipher.DECRYPT_MODE, keySpec);
		return cipher.doFinal(input);
	}
	public static void main(String[] args) throws Exception{
		// 原文:
		String message = "Hello, world! encrypted using AES!";
		System.out.println("Message: " + message);
		// 128位密钥 = 16 bytes Key:
		byte[] key = "1234567890abcdef".getBytes("UTF-8");
		// 加密:
		byte[] data = message.getBytes(StandardCharsets.UTF_8);
		byte[] encrypted = encrypt(key, data);
		System.out.println("Enctypted data: " + Base64.getEncoder().encodeToString(encrypted));
		// 解密:
		byte[] decrypted = decrpty(key, encrypted);
		System.out.println("Decrypted data: " + new String(decrypted, "UTF-8"));
	}
}
// Message: Hello, world! encrypted using AES!
// Enctypted data: 6ofAje3dbEseeIBkwKEonQIUi09dPO9fVx4OgZ7ozsE7BWtJJdcJs1+N58l1mWqh
// Decrypted data: Hello, world! encrypted using AES!

CBC模式

public class AES_CBC_Cipher {
	static final String CIPHER_NAME = "AES/CBC/PKCS5Padding";
	/**
	 * 加密
	 */
	public static byte[] encrypt(byte[] key, byte[] input) throws Exception {
		Cipher cipher = Cipher.getInstance(CIPHER_NAME);
		SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
		// CBC模式需要生成一个16位的initialization vector
		SecureRandom sr = SecureRandom.getInstanceStrong();
		byte[] iv = sr.generateSeed(16);
		IvParameterSpec ipvs = new IvParameterSpec(iv);
		cipher.init(Cipher.ENCRYPT_MODE, keySpec, ipvs);
		byte[] data = cipher.doFinal(input);
		// IV不需要保密,把IV和密文一起返回
		return join(iv, data);
	}
	/**
	 * 解密
	 */
	public static byte[] decrpty(byte[] key, byte[] input) throws Exception {
		// 把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(CIPHER_NAME);
		SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
		IvParameterSpec ivps = new IvParameterSpec(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;
	}
	public static void main(String[] args) throws Exception {
		// 原文:
		String message = "Hello, world! encrypted using AES!";
		System.out.println("Message: " + message);
		// 128位密钥 = 16 bytes Key:
		byte[] key = "1234567890abcdef".getBytes("UTF-8");
		// 加密:
		byte[] data = message.getBytes(StandardCharsets.UTF_8);
		byte[] encrypted = encrypt(key, data);
		System.out.println("Enctypted data: "
				+ Base64.getEncoder().encodeToString(encrypted));
		// 解密:
		byte[] decrypted = decrpty(key, encrypted);
		System.out.println("Decrypted data: " + new String(decrypted, "UTF-8"));
	}
}
// Message: Hello, world! encrypted using AES!
// Enctypted data: 0snryGJc4joIzXM2gq/qIY2Vzh/unufhvXfO4k7sk79whZ7nTf30YvO64SihyO2LaA0Ab7KIO9pDzLW8Z/c9Xg==
// Decrypted data: Hello, world! encrypted using AES!

AES256
使用256位加密需要修改JDK的policy文件
https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html下载,切换到目录D:\Program Files\Java\jdk1.8.0_131\jre\lib\security下替换调 local_policy.jar 和 US_export_policy.jar
在这里插入图片描述

public class AES256_ECB_Cipher {
	static final String CIPHER_NAME = "AES/ECB/PKCS5Padding";
	/**
	 * 加密
	 */
	public static byte[] encrypt(byte[] key, byte[] input) throws Exception{
		Cipher cipher = Cipher.getInstance(CIPHER_NAME);
		SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
		cipher.init(Cipher.ENCRYPT_MODE, keySpec);
		return cipher.doFinal(input);
	}
	/**
	 * 解密
	 */
	public static byte[] decrpty(byte[] key, byte[] input) throws Exception{
		Cipher cipher = Cipher.getInstance(CIPHER_NAME);
		SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
		cipher.init(Cipher.DECRYPT_MODE, keySpec);
		return cipher.doFinal(input);
	}
	public static void main(String[] args) throws Exception{
		// 原文:
		String message = "Hello, world! encrypted using AES!";
		System.out.println("Message: " + message);
		// 256位密钥 = 32 bytes Key:
		byte[] key = "1234567890abcdef1234567890abcdef".getBytes("UTF-8");
		// 加密:
		byte[] data = message.getBytes(StandardCharsets.UTF_8);
		byte[] encrypted = encrypt(key, data);
		System.out.println("Enctypted data: " + Base64.getEncoder().encodeToString(encrypted));
		// 解密:
		byte[] decrypted = decrpty(key, encrypted);
		System.out.println("Decrypted data: " + new String(decrypted, "UTF-8"));
	}
}
// Message: Hello, world! encrypted using AES!
// Enctypted data: zXgDofZMm8UHUqJJdyt4SBQToTtvTWjGUeh/XFlTzJr515ZUmGEgTWOL08g++rfL
// Decrypted data: Hello, world! encrypted using AES!

口令加密算法

PBE(Password Based Encryption)算法:

1)由用户输入口令,采用随机数杂凑计算出密钥进行加密
2)Password:用户口令,例如“hello 123”
3)Salt:随机生成byte[]
4)Key:generate(byte[] salt, String password)

Key通过口令和随机salt计算得出,提高安全性
PBE算法内部使用的仍然是标准对称加密算法(例如AES)

public class PBECipher {
	static final String CIPHER_NAME = "PBEwithSHA1and128bitAES-CBC-BC";
	/**
	 * 加密
	 */
	public static byte[] encrypt(String password, byte[] salt, byte[] input) throws Exception {
		PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
		SecretKeyFactory sKeyFactory = SecretKeyFactory
				.getInstance(CIPHER_NAME);
		SecretKey skey = sKeyFactory.generateSecret(keySpec);
		PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
		Cipher cipher = Cipher.getInstance(CIPHER_NAME);
		cipher.init(Cipher.ENCRYPT_MODE, skey, pbeps);
		return cipher.doFinal(input);
	}
	/**
	 * 解密
	 */
	public static byte[] decrypt(String password, byte[] salt, byte[] input) throws Exception {
		PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
		SecretKeyFactory sKeyFactory = SecretKeyFactory
				.getInstance(CIPHER_NAME);
		SecretKey skey = sKeyFactory.generateSecret(keySpec);
		PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
		Cipher cipher = Cipher.getInstance(CIPHER_NAME);
		cipher.init(Cipher.DECRYPT_MODE, skey, pbeps);
		return cipher.doFinal(input);
	}
	public static void main(String[] args) throws Exception {
		// 把BouncyCastle作为Provider添加到java.security:
		Security.addProvider(new BouncyCastleProvider());
		// 原文:
		String message = "Hello, world! encrypted using PBE!";
		// 加密口令
		String password = "hello12345";
		// 16 bytes随机Salt:
		byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16);
		System.out.printf("salt: %032x\n", new BigInteger(1, salt));
		// 加密:
		byte[] data = message.getBytes(StandardCharsets.UTF_8);
		byte[] encrypted = encrypt(password, salt, data);
		System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
		// 解密:
		byte[] decrypted = decrypt(password, salt, encrypted);
		System.out.println(new String(decrypted, "UTF-8"));
	}
}

密钥交换算法

Diffe-Hellman(DH)算法:是一种密钥交换协议,通信双方通过不安全的信道协商密钥,然后进行对称加密传输,DH算法没有解决中间人攻击
在这里插入图片描述

public class DH {
	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();
		// 双方的SecretKey相同,后续通信将使用SecretKey作为密钥进行AES加密
		String msgBobToAlice = bob.sendMessage("Hello, Alice!");
		System.out.println("Bob -> Alice: " + msgBobToAlice);
		String aliceDecrypted = alice.receiveMessage(msgBobToAlice);
		System.out.println("Alice decrypted: " + aliceDecrypted);
	}
}
class Person {
	public final String name;
	public PublicKey publicKey;
	private PrivateKey privateKey;
	private SecretKey secretKey;
	public Person(String name) {
		this.name = name;
	}
	/**
	 * 生成本地KeyPair
	 */
	public void generateKeyPair() {
		try {
			KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH");
			kpGen.initialize(512);
			KeyPair kp = kpGen.generateKeyPair();
			this.privateKey = kp.getPrivate();
			this.publicKey = kp.getPublic();
		} catch (GeneralSecurityException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 生成密钥
	 * 
	 * @param receivedPubKeyBytes
	 */
	public void generateSecretKey(byte[] receivedPubKeyBytes) {
		try {
			// 从byte[]恢复PublicKey
			X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
					receivedPubKeyBytes);
			KeyFactory kf = KeyFactory.getInstance("DH");
			PublicKey receivedPublicKey = kf.generatePublic(keySpec);
			// 生成本地密钥:
			KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
			keyAgreement.init(this.privateKey); // 自己的PrivateKey
			keyAgreement.doPhase(receivedPublicKey, true);// 对方的PublicKey
			// 生成AES密钥:
			this.secretKey = keyAgreement.generateSecret("AES");
		} catch (GeneralSecurityException 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.getEncoded()));
	}
	/**
	 * 发送加密消息
	 * 
	 * @param message
	 * @return
	 */
	public String sendMessage(String message) {
		try {
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
			cipher.init(Cipher.ENCRYPT_MODE, this.secretKey);
			byte[] data = cipher.doFinal(message.getBytes("UTF-8"));
			return Base64.getEncoder().encodeToString(data);
		} catch (GeneralSecurityException | IOException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 接收加密消息并解密
	 * 
	 * @param message
	 * @return
	 */
	public String receiveMessage(String message) {
		try {
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
			cipher.init(Cipher.DECRYPT_MODE, this.secretKey);
			byte[] data = cipher.doFinal(Base64.getDecoder().decode(message));
			return new String(data, "UTF-8");
		} catch (GeneralSecurityException | IOException e) {
			throw new RuntimeException(e);
		}
	}
}

非对称加密算法

RSA(Ron Rivest,Adi Shamir,Leonard Adleman)
非对称加密就是加密和解密使用的不是相同的密钥
只有同一个公钥 / 私钥对才能正常加密 / 解密

加密:用自己的私钥加密,然后发送给对方
encrypt(privateKeyA, message) ==> encrypted
解密:对方用自己的公钥解密
decrypted(publicKeyA, encrypted) ==> message

公钥公开,私钥保密
只是用非对称加密算法不能防止中间人攻击

public class RSAKeyPair {
	/**
	 * 私钥
	 */
	PrivateKey sk;
	/**
	 * 公钥
	 */
	PublicKey pk;
	
	/**
	 * 生成公钥/私钥对
	 * @throws GeneralSecurityException
	 */
	public RSAKeyPair() throws GeneralSecurityException{
		KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
		kpGen.initialize(1024);
		KeyPair kp = kpGen.generateKeyPair();
		this.sk = kp.getPrivate();
		this.pk = kp.getPublic();
	}
	/**
	 * 从以保存的字节中(例如:读取文件)恢复公钥/私钥
	 * @param pk
	 * @param sk
	 * @throws GeneralSecurityException
	 */
	public RSAKeyPair(byte[] pk, byte[] sk) throws GeneralSecurityException{
		KeyFactory kf = KeyFactory.getInstance("RSA");
		X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pk);
		this.pk = kf.generatePublic(pkSpec);
		PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(sk);
		this.sk = kf.generatePrivate(skSpec);
	}
	/**
	 * 把私钥导出为字节
	 * @return
	 */
	public byte[] getProvateKey(){
		return this.sk.getEncoded();
	}
	/**
	 * 把公钥导出为字节
	 * @return
	 */
	public byte[] getPublicKey(){
		return this.pk.getEncoded();
	}
	/**
	 * 用公钥加密
	 * @param message
	 * @return
	 * @throws GeneralSecurityException
	 */
	public byte[] encrypt(byte[] message) throws GeneralSecurityException{
		Cipher cipher = Cipher.getInstance("RSA");
		cipher.init(Cipher.ENCRYPT_MODE, this.pk);
		return cipher.doFinal(message);
	}
	/**
	 * 用私钥解密 
	 * @param input
	 * @return
	 * @throws GeneralSecurityException
	 */
	public byte[] decrypt(byte[] input) throws GeneralSecurityException{
		Cipher cipher = Cipher.getInstance("RSA");
		cipher.init(Cipher.DECRYPT_MODE, this.sk);
		return cipher.doFinal(input);
	}
	public static void main(String[] args) throws Exception{
		// 明文:
		byte[] plain = "Hello,使用RSA非对称加密算法对数据进行加密!".getBytes();
		// 创建公钥/私钥对:
		RSAKeyPair rsa = new RSAKeyPair();
		// 加密:
		byte[] encrypted = rsa.encrypt(plain);
		System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
		// 解密:
		byte[] decrypted = rsa.decrypt(encrypted);
		System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
		// 保存公钥/私钥:
		byte[] pk = rsa.getPublicKey();
		byte[] sk = rsa.getProvateKey();
		System.out.println("pk: " + Base64.getEncoder().encodeToString(pk));
		System.out.println("sk: " + Base64.getEncoder().encodeToString(sk));
		// 重新恢复公钥/私钥:
		RSAKeyPair rsa2 = new RSAKeyPair(pk, sk);
		// 加密:
		byte[] encrypted2 = rsa2.encrypt(plain);
		System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted2));
		// 解密:
		byte[] decrypted2 = rsa2.decrypt(encrypted2);
		System.out.println("decrypted: " + new String(decrypted2, "UTF-8"));
	}
}
// encrypted: dfED6ShwTYfQnYTG+1xqqZtn4jzhtciwdqe2hd0/oZqdH4JosyhfekZvGQlehpDAeAYKrPJWzCBbD8k/lt66HSlZutPddR/9L+DF1/JCghgyoLHtWf2L4XU2n0DNw5LSdp2gvoh/xK6ryauxyx9cr/fAnYPvFT4P0PYJwRzKJYQ=
// decrypted: Hello,使用RSA非对称加密算法对数据进行加密!
// pk: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6boCz2BXq/6ZhrVC2WUf6IY64gSBghsi9d4iDlvAzA+M5lYuswv77ppmO+1tJhiQq6rb+SAcjiYBMyOfB5YIPwvnD0zHfKJysDtbgmxGJGfnVMEcQOZrom8BlJ6HbVpPhMJrBNeTnl83VmPN6YSXCyRn5d7fBj1o4OYjKQVhyBQIDAQAB
// sk: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALpugLPYFer/pmGtULZZR/ohjriBIGCGyL13iIOW8DMD4zmVi6zC/vummY77W0mGJCrqtv5IByOJgEzI58Hlgg/C+cPTMd8onKwO1uCbEYkZ+dUwRxA5muibwGUnodtWk+EwmsE15OeXzdWY83phJcLJGfl3t8GPWjg5iMpBWHIFAgMBAAECgYB5DuBL5/jzi+ZCbwSGJBt09O10KYUYqO0Y+tGOgSigN9oIvm7gVA624ku8aMOyToSVyaviGmtoTBUfxyWW5q046Qw9b/eLGmUjhIjhFsCmUhmjwZFGWBIJjcgwhFNk216ElHWGxmhBHzsYUcsMi8vM5EnKy6IxXKE4qOc5GqX70QJBAPbyBK5RhOdPIZo6iWhLbTSbhLGr0V571wnbSP1wvQDeEjDl54bHwVBIzGWck9it4XRBArlqvIP6XKa1j68Eh68CQQDBRHcZldATuivO16UK3jBhbzgZnDuNIf6V83YhjdLoFeoG78J8/2lFsIsh2ZHEpXNE+WfN1tfcwADbhLN5VRqLAkApPbf8aM7aVoPVHwuNqHMfgw2BIqG/tszt73pcITTfbTb6hZrKDphBUTcZjTG/0SJC6QyMbL/5BpnsmYvafdYvAkArvsV+gwxwCqmzzdfLl/M3eqcmFNjd3x3y7wKEIcflRGCOZWKOTHmVjppPdSGrcw+eQx2d181QaB1JksXU5tB1AkEAnvjnG96xhZfkzUOZeS205FwwDcawjnyTTGAIIwwMLnBAmVzKfurGHN1vPjKyBevrO5hoVf0O0IG54tOgUH6r/g==
// encrypted: WqweDrqJ0nSjm/HOg1tJpm8JNnjDppvWmCw0NmBpIxQR3mbP/7wU0yff3tv01iWXOYh9pRC4vVSATUV+AS2IgsgGYH9RCAVpwbgF1gig5bcNYfi3RzrlBfP9vap3lpE46CWm8w8PusVgIw7/1K4ISyokCcfnxItuHiRKBLqAs0Q=
// decrypted: Hello,使用RSA非对称加密算法对数据进行加密!

非对称加密的优点:

1)对称加密需要协商密钥,而非对称加密可以安全地公开各自的公钥
2)N个人之间通信:
	使用非对称加密只需要N个密钥对,每个人只管理自己的密钥对
	使用对称加密需要N*(N-1)/2个密钥,每个人需要管理N-1个密钥

非对称加密的缺点:

运算速度慢

签名算法

RSA签名算法

用发送方的私钥对原始数据进行签名:sig = signature(privateKey, “message”);
只有用发送方公钥才能通过签名验证:boolean valid = verify(publicKey, sig, “message”)
数字签名 ≈ 混入了私钥/公钥的摘要
数字签名的目的:确认信息是某个发送方发送的,发送方不能抵赖发送过的信息,数据在传输过程中没有被修改
常用算法:MD5withRSA / SHA1withRSA / SHA256withRSA
在这里插入图片描述

public class SecRSASignature {
	PrivateKey sk;
	PublicKey pk;
	/**
	 * 生成公钥/私钥对
	 * @throws GeneralSecurityException
	 */
	public SecRSASignature() throws GeneralSecurityException{
		KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
		kpGen.initialize(1024);
		KeyPair kp = kpGen.generateKeyPair();
		this.sk = kp.getPrivate();
		this.pk = kp.getPublic();
	}
	/**
	 * 从以保存的字节中(例如:读取文件)恢复公钥/私钥
	 * @param pk
	 * @param sk
	 * @throws GeneralSecurityException
	 */
	public SecRSASignature(byte[] pk, byte[] sk) throws GeneralSecurityException{
		KeyFactory kf = KeyFactory.getInstance("RSA");
		X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pk);
		this.pk = kf.generatePublic(pkSpec);
		PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(sk);
		this.sk = kf.generatePrivate(skSpec);
	}
	/**
	 * 把私钥导出为字节
	 * @return
	 */
	public byte[] getProvateKey(){
		return this.sk.getEncoded();
	}
	/**
	 * 把公钥导出为字节
	 * @return
	 */
	public byte[] getPublicKey(){
		return this.pk.getEncoded();
	}
	/**
	 * sign by sk
	 * @param message
	 * @return
	 * @throws GeneralSecurityException
	 */
	public byte[] sign(byte[] message) throws GeneralSecurityException{
		Signature signature = Signature.getInstance("SHA1withRSA");
		signature.initSign(this.sk);
		signature.update(message);
		return signature.sign();
	}
	/**
	 * sign by pk
	 * @param message
	 * @param sign
	 * @return
	 * @throws GeneralSecurityException
	 */
	public boolean verify(byte[] message, byte[] sign) throws GeneralSecurityException{
		Signature signature = Signature.getInstance("SHA1withRSA");
		signature.initVerify(this.pk);
		signature.update(message);
		return signature.verify(sign);
	}
	public static void main(String[] args) throws Exception{
		byte[] message = "Hello, 使用SHA1withRSA算法进行数字签名!".getBytes();
		SecRSASignature rsas = new SecRSASignature();
		byte[] sign = rsas.sign(message);
		System.out.println("sign: " + Base64.getEncoder().encodeToString(sign));
		boolean verified = rsas.verify(message, sign);
		System.out.println("verify: " + verified);
		// 用另一个公钥验证:
		boolean verified2 = new SecRSASignature().verify(message, sign);
		System.out.println("verify with another public key: " + verified2);
		// 修改原始信息:
		message[0] = 100;
		boolean verified3 = rsas.verify(message, sign);
		System.out.println("verify changed message: " + verified3);
	}
}
// sign: HlqRdx162ZG1p52VfXF9TVwPLSsaZup6VlXp2xi5VkwM5v8KIAunKcFNE7WhOuzdZjySxNz2NMJGd4LHdRq0D280nTOgBvGvWXX8m3R6hVMIk5pdDLDfoIsZ+cALz1Q8nTgV83gEl0ugeuJvBLPLd3arNKQ4ERgzYpTgh6SNGaM=
// verify: true
// verify with another public key: false
// verify changed message: false

DSA签名算法

DSA:Digital Signature Algorithm
使用EIGamal数字签名算法,和RSA数字签名相比,速度更快
算法包括:SHA1withDSA / SHA256withDSA / SHA512withDSA
其他数字签名算法:ECDSA(Elliptic Curve Digital Signature Algorithm(Bouncy Castle))

public class SecDSASignature {
	PrivateKey sk;
	PublicKey pk;
	/**
	 * 生成公钥/私钥对
	 * @throws GeneralSecurityException
	 */
	public SecDSASignature() throws GeneralSecurityException{
		KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DSA");
		kpGen.initialize(1024);
		KeyPair kp = kpGen.generateKeyPair();
		this.sk = kp.getPrivate();
		this.pk = kp.getPublic();
	}
	/**
	 * 从以保存的字节中(例如:读取文件)恢复公钥/私钥
	 * @param pk
	 * @param sk
	 * @throws GeneralSecurityException
	 */
	public SecDSASignature(byte[] pk, byte[] sk) throws GeneralSecurityException{
		KeyFactory kf = KeyFactory.getInstance("DSA");
		X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pk);
		this.pk = kf.generatePublic(pkSpec);
		PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(sk);
		this.sk = kf.generatePrivate(skSpec);
	}
	/**
	 * 把私钥导出为字节
	 * @return
	 */
	public byte[] getProvateKey(){
		return this.sk.getEncoded();
	}
	/**
	 * 把公钥导出为字节
	 * @return
	 */
	public byte[] getPublicKey(){
		return this.pk.getEncoded();
	}
	/**
	 * sign by sk
	 * @param message
	 * @return
	 * @throws GeneralSecurityException
	 */
	public byte[] sign(byte[] message) throws GeneralSecurityException{
		Signature signature = Signature.getInstance("SHA1withDSA");
		signature.initSign(this.sk);
		signature.update(message);
		return signature.sign();
	}
	/**
	 * sign by pk
	 * @param message
	 * @param sign
	 * @return
	 * @throws GeneralSecurityException
	 */
	public boolean verify(byte[] message, byte[] sign) throws GeneralSecurityException{
		Signature signature = Signature.getInstance("SHA1withDSA");
		signature.initVerify(this.pk);
		signature.update(message);
		return signature.verify(sign);
	}
	public static void main(String[] args) throws Exception{
		byte[] message = "Hello, 使用SHA1withDSA算法进行数字签名!".getBytes();
		SecDSASignature rsas = new SecDSASignature();
		byte[] sign = rsas.sign(message);
		System.out.println("sign: " + Base64.getEncoder().encodeToString(sign));
		boolean verified = rsas.verify(message, sign);
		System.out.println("verify: " + verified);
		// 用另一个公钥验证:
		boolean verified2 = new SecDSASignature().verify(message, sign);
		System.out.println("verify with another public key: " + verified2);
		// 修改原始信息:
		message[0] = 100;
		boolean verified3 = rsas.verify(message, sign);
		System.out.println("verify changed message: " + verified3);
	}
}
// sign: MCwCFFHhAUZW49W+m+YX7eHc1geBOMymAhQk3j/hi1SdbxzcjO4DQGRjUlMfvw==
// verify: true
// verify with another public key: false
// verify changed message: false

数字证书

摘要算法:确保数据没有被篡改
非对称加密算法:对数据进行加密、解密
签名算法:确保数据完整性和抗否认性

数字证书就是集合了多种密码学算法,用于实现数据加解密、身份认证、签名等多种功能的一种网络安全标准。
数字证书采用链式签名管理,顶级CA证书已内置在操作系统中。

Root CA证书(根证书)
	下一级证书
		用户证书

常用算法:MD5、SHA1、SHA256、RSA、DSA、…
应用:https等
先用jdk自带的keytool命令生成keystore
keytool -genkeypair -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 36500 -alias mycert -keystore my.keystore -dname “CN=test07, OU=sample, O=sample, L=BJ, ST=BJ, C=CN” -keypass 123456 -storepass 456789
查看keystore 信息
keytool -list -keystore my.keystore -storepass 456789

public class X509 {
	private final PrivateKey privateKey;
	private final X509Certificate certificate;
	public X509(KeyStore ks, String certName, String password) throws GeneralSecurityException{
		this.privateKey = (PrivateKey) ks.getKey(certName, password.toCharArray());
		this.certificate = (X509Certificate) ks.getCertificate(certName);
	}
	public byte[] encrypt(byte[] message) throws GeneralSecurityException{
		Cipher cipher = Cipher.getInstance(this.privateKey.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, this.privateKey);
		return cipher.doFinal(message);
	}
	public byte[] decrypt(byte[] data) throws GeneralSecurityException{
		PublicKey publicKey = this.certificate.getPublicKey();
		Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, publicKey);
		return cipher.doFinal(data);
	}
	public byte[] sign(byte[] message) throws GeneralSecurityException{
		Signature signature = Signature.getInstance(this.certificate.getSigAlgName());
		signature.initSign(this.privateKey);
		signature.update(message);
		return signature.sign();
	}
	public boolean verify(byte[] message, byte[] sign) throws GeneralSecurityException{
		Signature signature = Signature.getInstance(this.certificate.getSigAlgName());
		signature.initVerify(this.certificate);
		signature.update(message);
		return signature.verify(sign);
	}
	static KeyStore loadKeyStore(String keyStoreFile, String password) throws GeneralSecurityException, IOException{
		try(InputStream input = new BufferedInputStream(new FileInputStream(keyStoreFile))){
			KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
			ks.load(input, password.toCharArray());
			return ks;
		}
	}
	public static void main(String[] args) throws GeneralSecurityException, IOException {
		byte[] message = "Hello, 使用X.509证书进行加密和签名!".getBytes();
		// 读取KeyStore:
		KeyStore ks = loadKeyStore("my.keystore", "456789");
		// 读取证书:
		X509 x509 = new X509(ks, "mycert", "123456");
		// 加密:
		byte[] encrypted = x509.encrypt(message);
		System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
		// 解密:
		byte[] decrypted = x509.decrypt(encrypted);
		System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
		// 签名:
		byte[] sign = x509.sign(message);
		System.out.println("sign: " + Base64.getEncoder().encodeToString(sign));
		// 验证签名:
		boolean verified = x509.verify(message, sign);
		System.out.println("verify: " + verified);
	}
}
// encrypted: FWIANXZEVSvRCv6HlMPUEDxyYb6iAAJs+hAugy1CJuFMT3d7SxlhANZAizZhMuL40aF/iANOh2M6XZaPU7Lx+1qI87fScPaQHN5uOaGgsIFaLk7XO1NXqvT6JvHx0lXouU+hz3Sg612QcRqZYqsvnVh5JfXuMAWnZnN47B1WzCY=
// decrypted: Hello, 使用X.509证书进行加密和签名!
// sign: T8lgW6ZLtl/oWgV/M4m/6flU34Jq2VNWdvC7vFFYXnu3Q/JgjHkaV4SUTt2cbNzAenuFsjWrZn5girapOn2t4Nn0Y9brbLQDTG6SavrupqJYbTLt2Q4XUzG/hg54F9vlq/JkDhY1jwctJf4p578RVSostMmv6Cf/aksc/TQWv8E=
// verify: true

猜你喜欢

转载自blog.csdn.net/yangwei234/article/details/85006685