1. 程式人生 > >java中的加密演算法

java中的加密演算法

資料的加密,在資料傳輸過程中是常用到的,前面幾天在公眾號上看到一篇文章講的加密演算法,感覺寫得很不錯,於是乎記錄寫自己感覺重要的再加上寫自己的理解並粘上程式碼。

一.什麼是加密演算法
資料加密的基本過程就是對原來為明文的檔案或資料按某種演算法進行處理,使其成為不可讀的一段程式碼,通常稱為“密文”,使其只能在輸入相應的金鑰之後才能顯示出本來內容,通過這樣的途徑來達到保護資料不被非法人竊取,閱讀的目的。該過程的逆過程為解密,即將該編碼資訊轉化為其原始資料的過程.Java的加密知識也是Java的常見的領域之一,加密技術的底層確實很複雜,運用了大量的數學知識,要弄明白非常複雜。但是Java的語言中運用密碼加密工具卻是非常簡單。我們在Java的裡面運用這些加密技術,只需要把原理和使用場景等搞明白就可以了,具體底層實現不用研究。

二,分類

常用的加密演算法有對稱加密演算法,非對稱加密演算法,雜湊演算法,數字簽名等幾類。    

對稱加密顧名思義就是加密和解密是對稱的,加密時用一個祕鑰去加密,解密時用同一個祕鑰去解密,由資訊傳送方和接收方共同約定一個祕鑰。缺點是風險都在這個祕鑰上面,一旦被竊取,資訊會暴露。所以安全級別不夠高。常用對稱加密演算法有DES,3DES,AES等。在JDK中也都有封裝。

非對稱加密,顧名思義就是加密與解密的過程不是對稱的,不是用的同一個祕鑰。非對稱加密有個公私鑰對的概念,也就是有兩把祕鑰,一把是公鑰,一把是私鑰,一對公私鑰有固定的生成方法,在加密的時候,用公鑰去加密,接收方再用對應的私鑰去解密。使用時可以由接收方生成公私鑰對,然後將公鑰傳給加密方,這樣私鑰不會在網路中傳輸,沒有被竊取的風險。比如GitHub的底層的SSH協議就是公私鑰非對稱加密。並且公鑰是可以由私鑰推匯出來的,反過來卻不行,由通過公鑰無法推匯出私鑰。常用演算法有RSA,ECC等.ECC也是比特幣底層用的比較多的演算法。通過和對稱加密的對比,可以看到,非對稱加密解決了祕鑰傳輸中的安全問題。

雜湊演算法,簡單說就是將任意資料都轉換成一個固定長度的字串。通過雜湊後的值幾乎無法推匯出原文。而且兩個不同的原文雜湊後結果一定不同。常用演算法有MD5, SHA256等等。常用場景,MD5常用場景是資料庫的密碼儲存.sha256在挖礦中可以用到。

非對稱加密也有一個問題,就是內容在傳送前可能被篡改,因為公鑰是有可能被竊取的,所以竊取者完全可以改為傳送別的內容。

解決的辦法就是數字簽名。數字簽名和非對稱加密是反過來的,也是有公私鑰對,但是是用私鑰簽名,用公鑰去驗證簽名。比如傳送方除了傳送用公鑰加密後的密文,還要傳送簽名,簽名內容通常是密文雜湊後的字串,接收方首先驗證簽名是否正確,如果正確那麼密文解密後就是真正需要並且沒有被篡改過的內容。注意,簽名和非對稱用的是兩對不同的公私鑰。

上面是對幾個加密演算法的一個簡單講解,除了上面的還有base58等,比特幣底層安全也是依賴於加密。後面會一個一個介紹要用到的加密演算法的介紹和使用,但是僅僅是使用,底層不會講。

三,演算法詳解
 

MD5

 

MD5即訊息摘要演算法5(資訊 - 摘要演算法5),用於確保資訊傳輸完整一致。是計算機廣泛使用的雜湊演算法之一(又譯摘要演算法,雜湊演算法),主流程式語言普遍已有MD5實現。將資料(如漢字)運算為另一固定長度值,是雜湊演算法的基礎原理,MD5的前身有MD2,MD3和MD4。

MD5演算法具有以下特點:

1,壓縮性:任意長度的資料,算出的MD5值長度都是固定的。

2,容易計算:從原資料計算出MD5值很容易。

3,抗修改性:對原資料進行任何改動,哪怕只修改1個位元組,所得到的MD5值都有很大區別。

4,強抗碰撞:已知原資料和其MD5值,想找到一個具有相同MD5值的資料(即偽造資料)是非常困難的。

MD5的作用是讓大容量資訊在用數字簽名軟體簽署私人金鑰前被"壓縮"成一種保密的格式(就是把一個任意長度的位元組串變換成一定長的十六進位制數字串)。除了MD5以外,其中比較有名的還有sha-1、RIPEMD以及Haval等。

JDK就自帶了md5加密演算法,直接呼叫很方便。需要引入一個類:
import java.security.MessageDigest;
具體程式碼如下(直接copy就可以使用):

import java.security.MessageDigest;

/**
 * @version :2018年3月11日 上午10:35:12 類說明
 */
public class MD5Util {

	public static String bytesToHexString(byte[] src) {
		StringBuilder stringBuilder = new StringBuilder("");
		if (src == null || src.length <= 0) {
			return null;
		}
		for (int i = 0; i < src.length; i++) {
			int v = src[i] & 0xFF;
			String hv = Integer.toHexString(v);
			if (hv.length() < 2) {
				stringBuilder.append(0);
			}
			stringBuilder.append(hv);
		}
		return stringBuilder.toString();
	}

	/**
	 * 解析
	 * 
	 * @param hexString
	 * @return
	 */
	public static byte[] hexStringToBytes(String hexString) {
		if (hexString == null || hexString.equals("")) {
			return null;
		}
		hexString = hexString.toUpperCase();
		int length = hexString.length() / 2;
		char[] hexChars = hexString.toCharArray();
		byte[] d = new byte[length];
		for (int i = 0; i < length; i++) {
			int pos = i * 2;
			d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
		}
		return d;
	}

	/**
	 * 將指定byte陣列以16進位制的形式列印到控制檯
	 * 
	 * @param b
	 */
	public static void printHexString(byte[] b) {
		for (int i = 0; i < b.length; i++) {
			String hex = Integer.toHexString(b[i] & 0xFF);
			if (hex.length() == 1) {
				hex = '0' + hex;
			}
			System.out.print(hex.toUpperCase());
		}

	}

	/**
	 * Convert char to byte
	 * 
	 * @param c
	 *            char
	 * @return byte
	 */
	private static byte charToByte(char c) {
		return (byte) "0123456789abcdef".indexOf(c);
	}

	/**
	 * 加密
	 * 
	 * @param str
	 * @return
	 */
	public static String encodeMD5(String str) {
		String strDigest = "";
		try {
			// 此 MessageDigest 類為應用程式提供資訊摘要演算法的功能,必須用try,catch捕獲
			MessageDigest md5 = MessageDigest.getInstance("MD5");
			byte[] data = md5.digest(str.getBytes("utf-8"));// 轉換為MD5碼
			strDigest = bytesToHexString(data);
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
		return strDigest;
	}
}

SHA25

學Java的對雜湊演算法都不陌生,畢竟每個類都有hashCode方法。

雜湊演算法(Hash Algorithm),又稱雜湊演算法,雜湊演算法,是一種從任意檔案中創造小的數字「指紋」的方法。與指紋一樣,雜湊演算法就是一種以較短的資訊來保證檔案唯一性的標誌,這種標誌與檔案的每一個位元組都相關,而且難以找到逆向規律。因此,當原有檔案發生改變時,其標誌值也會發生改變,從而告訴檔案使用者當前的檔案已經不是你所需求的檔案。

一個優秀的 hash 演算法,將能實現:

正向快速:給定明文和 hash 演算法,在有限時間和有限資源內能計算出 hash 值。

逆向困難:給定(若干) hash 值,在有限時間內很難(基本不可能)逆推出明文。

輸入敏感:原始輸入資訊修改一點資訊,產生的 hash 值看起來應該都有很大不同。

衝突避免:很難找到兩段內容不同的明文,使得它們的 hash 值一致(發生衝突)。即對於任意兩個不同的資料塊,其hash值相同的可能性極小;對於一個給定的資料塊,找到和它hash值相同的資料塊極為困難。

但在不同的使用場景中,如資料結構和安全領域裡,其中對某一些特點會有所側重。

安全雜湊演算法(英語:Secure Hash Algorithm,縮寫為SHA)是一個密碼雜湊函式家族,是FIPS所認證的安全雜湊演算法。能計算出一個數字訊息所對應到的,長度固定的字串(又稱訊息摘要)的演算法。且若輸入的訊息不同,它們對應到不同字串的機率很高。

SHA家族的五個演算法,分別是SHA-1,SHA-224,SHA-256,SHA-384,和SHA-512。主要適用於數字簽名標準(DigitalSignature Standard DSS)裡面定義的數字簽名演算法(Digital Signature)演算法DSA)。比特幣裡面的就是SHA-256演算法。

說簡單一些,就是對一個物件的多個關鍵不重複資訊組合起來,通過演算法生成一個加密字串。

引入的加密類和MD5一樣:

import java.security.MessageDigest;

下面是演算法的具體內容:

/**
     *  利用java原生的摘要實現SHA256加密
     * @param str 加密後的報文
     * @return
     */
    public static String getSHA256StrJava(String str){
        MessageDigest messageDigest;
        String encodeStr = "";
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(str.getBytes("UTF-8"));
            encodeStr = byte2Hex(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return encodeStr;
    }

    /**
     * 將byte轉為16進位制
     * @param bytes
     * @return
     */
    private static String byte2Hex(byte[] bytes){
        StringBuffer stringBuffer = new StringBuffer();
        String temp = null;
        for (int i=0;i<bytes.length;i++){
            temp = Integer.toHexString(bytes[i] & 0xFF);
            if (temp.length()==1){
                //1得到一位的進行補0操作
                stringBuffer.append("0");
            }
            stringBuffer.append(temp);
        }
        return stringBuffer.toString();
    }

AES

AES是一個對稱密碼,旨在取代DES成為廣泛使用的標準。
具體程式碼如下:

/**
	 * aes加密
	 * @param content
	 * @param password
	 * @return
	 */
	public static String encrypt(String content, String password) {
		try {
			KeyGenerator kgen = KeyGenerator.getInstance("AES");
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
			random.setSeed(password.getBytes());
			kgen.init(128, random);
			SecretKey secretKey = kgen.generateKey();
			byte[] enCodeFormat = secretKey.getEncoded();
			SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
			Cipher cipher = Cipher.getInstance("AES");
			byte[] byteContent = content.getBytes("utf-8");
			cipher.init(Cipher.ENCRYPT_MODE, key);
			byte[] result = cipher.doFinal(byteContent);
			String str = Base64.getEncoder().encodeToString(result);
			return str; 
		} catch (NoSuchAlgorithmException e) {
			// e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// e.printStackTrace();
		} catch (InvalidKeyException e) {
			// e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// e.printStackTrace();
		} catch (BadPaddingException e) {
			// e.printStackTrace();
		}
		return null;
	}
    /**
	 * aes解密
	 * @param str
	 * @param password
	 * @return
	 */
	public static String decrypt(String str, String password) {
		try {
			byte[] content = Base64.getDecoder().decode(str);
			KeyGenerator kgen = KeyGenerator.getInstance("AES");
			SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
			secureRandom.setSeed(password.getBytes());
			kgen.init(128, secureRandom);
			SecretKey secretKey = kgen.generateKey();
			byte[] enCodeFormat = secretKey.getEncoded();
			SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
			Cipher cipher = Cipher.getInstance("AES");// 建立密碼器
			cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
			byte[] result = cipher.doFinal(content);
			return new String(result,"UTF-8");
		} catch (NoSuchAlgorithmException e) {
			// e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// e.printStackTrace();
		} catch (InvalidKeyException e) {
			// e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// e.printStackTrace();
		} catch (BadPaddingException e) {
			// e.printStackTrace();
		} catch (Exception e) {
			// e.printStackTrace();
		}
		return "";
	}