1. 程式人生 > 其它 >JAVA AES加密與解密

JAVA AES加密與解密

本博文出自https://blog.csdn.net/u011781521/article/details/77932321

一、AES加密簡介

 

AES加密演算法是密碼學中的高階加密標準(Advanced Encryption Standard,AES),又稱Rijndael加密法,是美國聯邦政府採用的一種區塊加密標準。這個標準用來替代原先的DES,已經被多方分析且廣為全世界所使用。


AES 是一個新的可以用於保護電子資料的加密演算法。明確地說,AES 是一個迭代的、對稱金鑰分組的密碼,它可以使用128、192 和 256 位金鑰,並且用 128 位(16位元組)分組加密和解密資料。與公共金鑰密碼使用金鑰對不同,對稱金鑰密碼使用相同的金鑰加密和解密資料。通過分組密碼返回的加密資料 的位數與輸入資料相同。迭代加密使用一個迴圈結構,在該迴圈中重複置換(permutations )和替換(substitutions)輸入資料。

 

AES加密有很多輪的重複和變換。大致步驟如下:


1、金鑰擴充套件(KeyExpansion)。
2、初始輪(Initial Round)。
3、重複輪(Rounds),每一輪又包括:SubBytes、ShiftRows、MixColumns、AddRoundKey。
4、最終輪(Final Round),最終輪沒有MixColumns。

 

二、AES在JAVA中的使用

 

JCE,Java Cryptography Extension,在早期JDK版本中,由於受美國的密碼出口條例約束,Java中涉及加解密功能的API被限制出口,所以Java中安全元件被分成了兩部分: 不含加密功能的JCA(Java Cryptography Architecture )和含加密功能的JCE(Java Cryptography Extension)。

JCA和JCE的API體系架構

JCE的API都在javax.crypto包下,核心功能包括:加解密、金鑰生成(對稱)、MAC生成、金鑰協商。


(1). 加解密

加解密功能由Cipher元件提供,其也是JCE中最核心的元件。

1. Cipher的幾個知識點:

a. Cipher在使用時需以引數方式指定transformation
b. transformation的格式為algorithm/mode/padding,其中algorithm為必輸項,如: AES/DES/CBC/PKCS5Padding
c. 預設的mode為ECB,預設的padding為PKCS5Padding
d. 在block演算法與流加密模式組合時, 需在mode後面指定每次處理的bit數, 如DES/CFB8/NoPadding, 如未指定則使用預設值, SunJCE預設值為64bits
e. Cipher有4種操作模式: ENCRYPT_MODE(加密), DECRYPT_MODE(解密), WRAP_MODE(匯出Key), UNWRAP_MODE(匯入Key),初始化時需指定某種操作模式

 


演算法/模式/填充 16位元組加密後資料長度 不滿16位元組加密後長度
AES/CBC/NoPadding 16 不支援
AES/CBC/PKCS5Padding 32 16
AES/CBC/ISO10126Padding 32 16
AES/CFB/NoPadding 16 原始資料長度
AES/CFB/PKCS5Padding 32 16
AES/CFB/ISO10126Padding 32 16
AES/ECB/NoPadding 16 不支援
AES/ECB/PKCS5Padding 32 16
AES/ECB/ISO10126Padding 32 16
AES/OFB/NoPadding 16 原始資料長度
AES/OFB/PKCS5Padding 32 16
AES/OFB/ISO10126Padding 32 16
AES/PCBC/NoPadding 16 不支援
AES/PCBC/PKCS5Padding 32 16
AES/PCBC/ISO10126Padding 32 16

可以看到,在原始資料長度為16的整數倍時,假如原始資料長度等於16*n,則使用NoPadding時加密後資料長度等於16*n,其它情況下加密資料長 度等於16*(n+1)。在不足16的整數倍的情況下,假如原始資料長度等於16*n+m[其中m小於16],除了NoPadding填充之外的任何方 式,加密資料長度都等於16*(n+1);NoPadding填充情況下,CBC、ECB和PCBC三種模式是不支援的,CFB、OFB兩種模式下則加密 資料長度等於原始資料長度。


2. 對稱加密的演算法與金鑰長度選擇

 

演算法名稱 金鑰長 塊長 速度 說明
DES 56 64 64 慢 不安全, 不要使用
3DES 112/168 64 很慢 中等安全, 適合加密較小的資料
AES 128, 192, 256 128 快 安全
Blowfish (4至56)*8 64 快 快 應該安全, 在安全界尚未被充分分析、論證
RC4 40-1024 64 很快 安全性不明確


推薦使用AES演算法。一般認為128bits的金鑰已足夠安全,如果可以請選擇256bits的金鑰。注意:


a. 金鑰長度是在生成金鑰時指定的,如:


KeyGenerator generator = KeyGenerator.getInstance("AES/CBC/PKCS5PADDING");
generator.init(256);
SecretKey key = generator.generateKey();

 

3. 加密示例程式碼

/**
* 根據金鑰對指定的明文plainText進行加密.
*
* @param plainText 明文
* @return 加密後的密文.
*/
public static final String encrypt(String plainText) {
Key secretKey = getKey("fendo888");
try {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] p = plainText.getBytes("UTF-8");
byte[] result = cipher.doFinal(p);
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(result);
return encoded;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

4. 解密示例程式碼


/**
* 根據金鑰對指定的密文cipherText進行解密.
*
* @param cipherText 密文
* @return 解密後的明文.
*/
public static final String decrypt(String cipherText) {
Key secretKey = getKey("fendo888");
try {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
BASE64Decoder decoder = new BASE64Decoder();
byte[] c = decoder.decodeBuffer(cipherText);
byte[] result = cipher.doFinal(c);
String plainText = new String(result, "UTF-8");
return plainText;
} catch (Exception e) {
throw new RuntimeException(e);
}

}

其中的getKey()

public static Key getKey(String keySeed) {
if (keySeed == null) {
keySeed = System.getenv("AES_SYS_KEY");
}
if (keySeed == null) {
keySeed = System.getProperty("AES_SYS_KEY");
}
if (keySeed == null || keySeed.trim().length() == 0) {
keySeed = "abcd1234!@#$";// 預設種子
}
try {
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(keySeed.getBytes());
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(secureRandom);
return generator.generateKey();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

 

三.AESUtils幫助類

package com.fendo.MD5;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.lang3.StringUtils;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
* @Title: AESUtils.java
* @Package com.fendo.MD5
* @Description: TODO
* @author fendo
* @date 2017年9月11日 下午5:48:17
* @version V1.0
*/
public class AESUtils {


private static final String encodeRules = "fendo";

/**
* 加密
* 1.構造金鑰生成器
* 2.根據ecnodeRules規則初始化金鑰生成器
* 3.產生金鑰
* 4.建立和初始化密碼器
* 5.內容加密
* 6.返回字串
*/
public static String AESEncode(String content) {
try {
//1.構造金鑰生成器,指定為AES演算法,不區分大小寫
KeyGenerator keygen = KeyGenerator.getInstance("AES");
//2.根據ecnodeRules規則初始化金鑰生成器
//生成一個128位的隨機源,根據傳入的位元組陣列
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(encodeRules.getBytes());
keygen.init(128, random);
//3.產生原始對稱金鑰
SecretKey original_key = keygen.generateKey();
//4.獲得原始對稱金鑰的位元組陣列
byte[] raw = original_key.getEncoded();
//5.根據位元組陣列生成AES金鑰
SecretKey key = new SecretKeySpec(raw, "AES");
//6.根據指定演算法AES自成密碼器
Cipher cipher = Cipher.getInstance("AES");
//7.初始化密碼器,第一個引數為加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二個引數為使用的KEY
cipher.init(Cipher.ENCRYPT_MODE, key);
//8.獲取加密內容的位元組陣列(這裡要設定為utf-8)不然內容中如果有中文和英文混合中文就會解密為亂碼
byte[] byte_encode = content.getBytes("utf-8");
//9.根據密碼器的初始化方式--加密:將資料加密
byte[] byte_AES = cipher.doFinal(byte_encode);
//10.將加密後的資料轉換為字串
//這裡用Base64Encoder中會找不到包
//解決辦法:
//在專案的Build path中先移除JRE System Library,再新增庫JRE System Library,重新編譯後就一切正常了。
String AES_encode = new String(new BASE64Encoder().encode(byte_AES));
//11.將字串返回
return AES_encode;
} 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 (UnsupportedEncodingException e) {
e.printStackTrace();
}
//如果有錯就返加nulll
return null;
}



/**
* AES加密
* @param content 待加密的內容
* @param encryptKey 加密金鑰
* @return 加密後的byte[]
* @throws Exception
*/
public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new SecureRandom(encryptKey.getBytes()));

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(kgen.generateKey().getEncoded(), "AES"));

return cipher.doFinal(content.getBytes("utf-8"));
}

/**
* 解密
* 解密過程:
* 1.同加密1-4步
* 2.將加密後的字串反紡成byte[]陣列
* 3.將加密內容解密
*/
public static String AESDecode(String content) {
try {
//1.構造金鑰生成器,指定為AES演算法,不區分大小寫
KeyGenerator keygen = KeyGenerator.getInstance("AES");
//2.根據ecnodeRules規則初始化金鑰生成器
//生成一個128位的隨機源,根據傳入的位元組陣列
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(encodeRules.getBytes());
keygen.init(128, random);
//3.產生原始對稱金鑰
SecretKey original_key = keygen.generateKey();
//4.獲得原始對稱金鑰的位元組陣列
byte[] raw = original_key.getEncoded();
//5.根據位元組陣列生成AES金鑰
SecretKey key = new SecretKeySpec(raw, "AES");
//6.根據指定演算法AES自成密碼器
Cipher cipher = Cipher.getInstance("AES");
//7.初始化密碼器,第一個引數為加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二個引數為使用的KEY
cipher.init(Cipher.DECRYPT_MODE, key);
//8.將加密並編碼後的內容解碼成位元組陣列
byte[] byte_content = new BASE64Decoder().decodeBuffer(content);
/*
* 解密
*/
byte[] byte_decode = cipher.doFinal(byte_content);
String AES_decode = new String(byte_decode, "utf-8");
return AES_decode;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
//如果有錯就返加nulll
return null;
}


/**
* AES解密
* @param encryptBytes 待解密的byte[]
* @param decryptKey 解密金鑰
* @return 解密後的String
* @throws Exception
*/
public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new SecureRandom(decryptKey.getBytes()));

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(kgen.generateKey().getEncoded(), "AES"));
byte[] decryptBytes = cipher.doFinal(encryptBytes);

return new String(decryptBytes);
}

/**
* base 64 加密
* @param bytes 待編碼的byte[]
* @return 編碼後的base 64 code
*/
public static String base64Encode(byte[] bytes){
return new BASE64Encoder().encode(bytes);
}

/**
* base 64 解密
* @param base64Code 待解碼的base 64 code
* @return 解碼後的byte[]
* @throws Exception
*/
public static byte[] base64Decode(String base64Code) throws Exception{
return StringUtils.isEmpty(base64Code) ? null : new BASE64Decoder().decodeBuffer(base64Code);
}

/**
* 將base 64 code AES解密
* @param encryptStr 待解密的base 64 code
* @param decryptKey 解密金鑰
* @return 解密後的string
* @throws Exception
*/
public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
}

/**
* AES加密為base 64 code
* @param content 待加密的內容
* @param encryptKey 加密金鑰
* @return 加密後的base 64 code
* @throws Exception
*/
public static String aesEncrypt(String content, String encryptKey) throws Exception {
return base64Encode(aesEncryptToBytes(content, encryptKey));
}

public static void main(String[] args) {
String[] keys = {
"", "root"
};
System.out.println("key | AESEncode | AESDecode");
for (String key : keys) {
System.out.print("key:"+key + " | ");
String encryptString = AESEncode(key);
System.out.print(encryptString + " | ");
String decryptString = AESDecode(encryptString);
System.out.println(decryptString);
}
}
}

 

————————————————
版權宣告:本文為CSDN博主「碼農致富」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/u011781521/article/details/77932321

————————————————
版權宣告:本文為CSDN博主「碼農致富」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/u011781521/article/details/77932321