1. 程式人生 > >JavaSE(九)加密與安全

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