Java基礎-——數據安全

數據安全

數據的安全傳輸要從以下幾方面入手

  • 防竊聽
  • 防篡改
  • 防僞造

現代計算機加密技術已經成爲一門學科,基於數學理論,很難!這部分可以做了解

URL編碼

  • 類似於utf8等字符集編碼規則
  • URL編碼規則:
    在這裏插入圖片描述
  • URL編碼是編碼算法,爲了便於瀏覽器和服務器處理!不是加密算法

Base64編碼

  • 把二進制數據用文本表示的編碼算法,適用於文本協議,例如電子郵件協議
  • 例如將“中”進行Base64編碼:
    在這裏插入圖片描述
import java.util.Base64;

public class SecBase64 {
	public static void main(String[] args) throws Exception {
		String original = "Hello\u00ff編碼測試";
		String b64 = Base64.getEncoder().encodeToString(original.getBytes("UTF-8"));
		System.out.println(b64);// 如何要轉成適合URL傳輸的文本,需要使用getUrlEncoder()
		String ori = new String(Base64.getDecoder().decode(b64), "UTF-8");
		System.out.println(ori);
	}
}
  • Base64是編碼算法,這種算法會降低效率;

MD5加密算法

  • 是一種摘要算法,摘要算法也稱爲哈希算法/Hash/Digest/數字指紋
  • 特點:任意長度的數據輸入,固定長度的數據輸出
  • 目的:驗證原始數據是否被篡改
  • 碰撞:不同的輸入得到相同的輸出,因爲將任意長度映射到固定長度,碰撞是不可避免的
  • 衡量Hash的安全性:
    在這裏插入圖片描述
  • 常見的Digest
    在這裏插入圖片描述
  • 使用md5加密算法要注意彩虹表攻擊
    在這裏插入圖片描述
import java.math.BigInteger;
import java.security.MessageDigest;

public class MD5Salt {
	public static byte[] toMD5(byte[] input) {
		MessageDigest md;
		try {
			md = MessageDigest.getInstance("MD5");// SHA-1	SHA-256
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		md.update(input);
		return md.digest();
	}

	public static void main(String[] args) throws Exception {
		String passwd = "helloworld";
		String salt = "Random salt";
		byte[] r = MD5Salt.toMD5((salt + passwd).getBytes("UTF-8"));// 只接受字節類型,需要轉化
		System.out.println(String.format("%032x", new BigInteger(1, r)));// 轉成16進制顯示
	}
}

Bouncy Castle

  • 適用於Java的輕量級加密API
  • Java加密擴展(JCE)和Java加密體系結構(JCA)的提供程序
  • Java安全套接字擴展(JSSE)的提供程序等
  • 可以進行動態和靜態配置
import java.security.MessageDigest;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

public class Digest {

	public static byte[] digest(String hashAlgorithm, byte[] input) {
		MessageDigest md;
		try {
			md = MessageDigest.getInstance(hashAlgorithm);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		md.update(input);
		return md.digest();
	}
	public static void main(String[] args) throws Exception {
		// 把BouncyCastle作爲Provider添加到java.security:
		Security.addProvider(new BouncyCastleProvider());// 動態導入jar包到工程,這裏註冊
		String s = "Java摘要算法測試";
		byte[] input = s.getBytes("UTF-8");
		byte[] r1 = digest("MD5", input);
		System.out.println(r1.length + ": " + ByteUtils.toHexString(r1));
		byte[] r2 = digest("SHA-1", input);
		System.out.println(r2.length + ": " + ByteUtils.toHexString(r2));
		byte[] r3 = digest("SHA-256", input);
		System.out.println(r3.length + ": " + ByteUtils.toHexString(r3));
		byte[] r4 = digest("RipeMD160", input);	// JDK沒有的算法,有BouncyCastle提供
		System.out.println(r4.length + ": " + ByteUtils.toHexString(r4));
	}
}

Hmac

  • 把key混入摘要的算法
  • 可以配合MD5、SHA-1等摘要算法,長度和原摘要算法相同
  • 可以理解爲加入salt的MD5
import java.math.BigInteger;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;

public class Hmac {

	public static byte[] hmac(String hmacAlgorithm, SecretKey skey, byte[] input) throws Exception {
		Mac mac = Mac.getInstance(hmacAlgorithm);
		mac.init(skey);
		mac.update(input);
		return mac.doFinal();
	}
	public static void main(String[] args) throws Exception {
		// http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html#Mac
		String algorithm = "HmacSHA1";
		// 原始數據:
		String data = "helloworld";
		// 隨機生成一個Key:
		KeyGenerator keyGen = KeyGenerator.getInstance(algorithm);
		SecretKey skey = keyGen.generateKey();
		// 打印Key:
		byte[] key = skey.getEncoded();
		System.out.println(String.format("Key: %0" + (key.length * 2) + "x", new BigInteger(1, key)));
		// 用這個Key計算HmacSHA1:
		byte[] result = hmac(algorithm, skey, data.getBytes("UTF-8"));
		System.out.println(String.format("Hash: %0" + (result.length * 2) + "x", new BigInteger(1, result)));
	}
}

對稱加密算法

  • 加密密鑰和解密密鑰相同,大部分算法加密解密過程互逆
  • AES_CBC算法:
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES_CBC_Cipher {
	// 指明算法名稱/工作模式/填充模式
	static final String CIPHER_NAME = "AES/CBC/PKCS5Padding";

	// 加密:
	public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
		Cipher cipher = Cipher.getInstance(CIPHER_NAME);
		SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
		// CBC模式需要生成一個16 bytes的initialization vector:
		SecureRandom sr = SecureRandom.getInstanceStrong();
		byte[] iv = sr.generateSeed(16);
		IvParameterSpec ivps = new IvParameterSpec(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(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("Encrypted data: " + Base64.getEncoder().encodeToString(encrypted));
		// 解密:
		byte[] decrypted = decrypt(key, encrypted);
		System.out.println("Decrypted data: " + new String(decrypted, "UTF-8"));
	}
}
  • 使用AES256_ECB算法,由於Oracle收到相關法律限制,需要從官網下載jar包替換JDK的policy文件,否則不能進行256及以上長度密鑰的加密!

密鑰交換算法

  • 如何在不安全的信道上安全傳送密鑰?
  • 原理是各自生成公鑰私鑰,只傳遞公鑰,互相組合得到相同的密鑰!
    在這裏插入圖片描述
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;

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");// DH算法的KeyPair
			kpGen.initialize(512);
			KeyPair kp = kpGen.generateKeyPair();
			this.privateKey = kp.getPrivate();
			this.publicKey = kp.getPublic();
		} catch (GeneralSecurityException e) {
			throw new RuntimeException(e);
		}
	}

	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()));
	}

	// 發送加密消息:
	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);
		}
	}

	// 接收加密消息並解密:
	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);
		}
	}

}

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);
	}
}

在這裏插入圖片描述

  • 當然,有加密就有破解,如上圖所示,如果有中間人假冒,還是會被竊取;

非對稱加密算法

  • 加密和解密使用不同的密鑰
  • 只有同一個公鑰私鑰對才能正常加密/解密
  • 優點:
    在這裏插入圖片描述

RSA簽名算法

  • 發送方用自己的私鑰對消息進行簽名
  • 接收方用發送方的公鑰檢驗簽名是否有效(只有用私鑰的簽名纔能有效)
  • 保證了數據在傳輸過程中沒有被修改,防止僞造的發送方
    在這裏插入圖片描述
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class SecRSASignature {

	PrivateKey sk;
	PublicKey pk;

	public SecRSASignature() throws GeneralSecurityException {// 還是要獲得密鑰對
		// generate key pair:
		KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
		kpGen.initialize(1024);
		KeyPair kp = kpGen.generateKeyPair();
		this.sk = kp.getPrivate();
		this.pk = kp.getPublic();
	}

	public SecRSASignature(byte[] pk, byte[] sk) throws GeneralSecurityException {
		// create from bytes:
		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);
	}

	public byte[] getPrivateKey() {
		return this.sk.getEncoded();
	}

	public byte[] getPublicKey() {
		return this.pk.getEncoded();
	}

	public byte[] sign(byte[] message) throws GeneralSecurityException {
		// sign by sk:
		Signature signature = Signature.getInstance("SHA1withRSA");// MD5withRSA SHA256withRSA
		signature.initSign(this.sk);
		signature.update(message);
		return signature.sign();
	}

	public boolean verify(byte[] message, byte[] sign) throws GeneralSecurityException {
		// verify by pk:
		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("UTF-8");
		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);
	}
}
  • 類似的,還有DSA簽名算法

數字證書

  • 摘要算法保證了數據沒被篡改(平時也可加鹽作爲數據加密)
  • 非對稱加密算法實現了對數據的加密解密
  • 簽名算法確保了數據安全傳輸(完整性、抗否認性)
  • 數字證書就是集合了多種密碼學算法,用於實現數據加解密、身份認證、簽名等多種功能的網絡安全標準
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Base64;

import javax.crypto.Cipher;

public class X509 {
	private final PrivateKey privateKey;
	public final X509Certificate certificate;

	public X509(KeyStore ks, String certName, String password) {
		try {
			this.privateKey = (PrivateKey) ks.getKey(certName, password.toCharArray());
			this.certificate = (X509Certificate) ks.getCertificate(certName);
		} catch (GeneralSecurityException e) {
			throw new RuntimeException(e);
		}
	}
	public byte[] encrypt(byte[] message) {
		try {
			Cipher cipher = Cipher.getInstance(this.privateKey.getAlgorithm());
			cipher.init(Cipher.ENCRYPT_MODE, this.privateKey);
			return cipher.doFinal(message);
		} catch (GeneralSecurityException e) {
			throw new RuntimeException(e);
		}
	}
	public byte[] decrypt(byte[] data) {
		try {
			PublicKey publicKey = this.certificate.getPublicKey();
			Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			return cipher.doFinal(data);
		} catch (GeneralSecurityException e) {
			throw new RuntimeException(e);
		}
	}
	public byte[] sign(byte[] message) {
		try {
			Signature signature = Signature.getInstance(this.certificate.getSigAlgName());
			signature.initSign(this.privateKey);
			signature.update(message);
			return signature.sign();
		} catch (GeneralSecurityException e) {
			throw new RuntimeException(e);
		}
	}
	public boolean verify(byte[] message, byte[] sig) {
		try {
			Signature signature = Signature.getInstance(this.certificate.getSigAlgName());
			signature.initVerify(this.certificate);
			signature.update(message);
			return signature.verify(sig);
		} catch (GeneralSecurityException e) {
			throw new RuntimeException(e);
		}
	}
	static KeyStore loadKeyStore(String keyStoreFile, String password) {
		try (InputStream input = new BufferedInputStream(new FileInputStream(keyStoreFile))) {
			KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
			ks.load(input, password.toCharArray());
			return ks;
		} catch (GeneralSecurityException | IOException e) {
			throw new RuntimeException(e);
		}
	}
	public static void main(String[] args) throws Exception {
		byte[] message = "Hello,使用X.509證書進行加密和簽名!".getBytes("UTF-8");
		// 讀取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);
	}
}
  • 生成證書命令:(命令行下項目目錄執行)
// 如果要應用於網站,域名需保證一致
keytool -genkeypair -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 36500 -alias mycert -keystore my.keystore -dname "CN=www.sample.com, OU=sample, O=sample, L=BJ, ST=BJ, C=CN" -keypass 123456 -storepass 456789
// 查看證書命令
keytool -list -keystore my.keystore -storepass 456789

小結

安全問題是網絡通信的重難點,如果能到研究這一步,算是學有所成了吧!
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章