這篇文章給大家介紹怎么在java項目中實現一個sm4加密算法,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
SMS4算法是在國內廣泛使用的WAPI無線網絡標準中使用的加密算法,是一種32輪的迭代非平衡Feistel結構的分組加密算法,其密鑰長度和分組長度均為128。SMS4算法的加解密過程中使用的算法是完全相同的,唯一不同點在于該算法的解密密鑰是由它的加密密鑰進行逆序變換后得到的。
SMS4分組加密算法是中國無線標準中使用的分組加密算法,在2012年已經被國家商用密碼管理局確定為國家密碼行業標準,標準編號GM/T 0002-2012并且改名為SM4算法,與SM2橢圓曲線公鑰密碼算法,SM3密碼雜湊算法共同作為國家密碼的行業標準,在我國密碼行業中有著極其重要的位置。
SMS4算法的分組長度為128bit,密鑰長度也是128bit。加解密算法均采用32輪非平衡Feistel迭代結構,該結構最先出現在分組密碼LOKI的密鑰擴展算法中。SMS4通過32輪非線性迭代后加上一個反序變換,這樣只需要解密密鑰是加密密鑰的逆序,就能使得解密算法與加密算法保持一致。SMS4加解密算法的結構完全相同,只是在使用輪密鑰時解密密鑰是加密密鑰的逆序。
S盒是一種利用非線性變換構造的分組密碼的一個組件,主要是為了實現分組密碼過程中的混淆的特性和設計的。SMS4算法中的S盒在設計之初完全按照歐美分組密碼的設計標準進行,它采用的方法是能夠很好抵抗差值攻擊的仿射函數逆映射復合法。
SM4常用于政府系統的數據傳輸加密,比如當我們前端向后臺傳參數的時候,可以使用此算法。對參數的數據進行加密,然后后臺對加密的數據進行解密再存儲到數據庫中,保證數據傳輸過程中,不受泄露。
本次提供的方案不僅提供sm4的加密解密,還提供了md5算法的完整性防篡改校驗。
對于java端,我們使用的基于spring的aop切面和自定義注解來實現。整體思路為,當后臺開啟加密解密的時候,針對于打上注解的方法,尋找實體類中打上注解的字段進行加密和解密。再從前端傳遞請求的request中取出md5的header,進行md5的完整性,防篡改校驗。
首先我們必須說的是兩個工具類,一個是SM4Utils工具類,另一個則是md5工具類。
下面先來說一下SM4Utils。這個工具類用于SM4算法的加密和解密及密碼校驗。我們先直接看代碼,然后后面對此進行解釋。
public class Sm4Utils {
private static final String ENCODING = "UTF-8";
public static final String ALGORIGTHM_NAME = "SM4";
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS7Padding";
public static final int DEFAULT_KEY_SIZE = 128;
public Sm4Utils() {
}
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* @Description:生成ecb暗號
*/
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName,BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORIGTHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
/**
* @Description:自動生成密鑰
*/
public static byte[] generateKey() throws Exception {
return generateKey(DEFAULT_KEY_SIZE);
}
public static byte[] generateKey(int keySize) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORIGTHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(keySize, new SecureRandom());
return kg.generateKey().getEncoded();
}
/**
* @Description:加密
*/
public static String encryptEcb(String hexKey, String paramStr, String charset) throws Exception {
String cipherText = "";
if (null != paramStr && !"".equals(paramStr)) {
byte[] keyData = ByteUtils.fromHexString(hexKey);
charset = charset.trim();
if (charset.length() <= 0) {
charset = ENCODING;
}
byte[] srcData = paramStr.getBytes(charset);
byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
cipherText = ByteUtils.toHexString(cipherArray);
}
return cipherText;
}
/**
* @Description:加密模式之ecb
*/
public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
byte[] bs = cipher.doFinal(data);
return bs;
}
/**
* @Description:sm4解密
*/
public static String decryptEcb(String hexKey, String cipherText, String charset) throws Exception {
String decryptStr = "";
byte[] keyData = ByteUtils.fromHexString(hexKey);
byte[] cipherData = ByteUtils.fromHexString(cipherText);
byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
charset = charset.trim();
if (charset.length() <= 0) {
charset = ENCODING;
}
decryptStr = new String(srcData, charset);
return decryptStr;
}
/**
* @Description:解密
*/
public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
/**
* @Description:密碼校驗
*/
public static boolean verifyEcb(String hexKey,String cipherText,String paramStr) throws Exception {
boolean flag = false;
byte[] keyData = ByteUtils.fromHexString(hexKey);
byte[] cipherData = ByteUtils.fromHexString(cipherText);
byte[] decryptData = decrypt_Ecb_Padding(keyData,cipherData);
byte[] srcData = paramStr.getBytes(ENCODING);
flag = Arrays.equals(decryptData,srcData);
return flag;
}
/**
* @Description:測試類
*/
public static void main(String[] args) {
try {
String json = "{\"name\":\"color\",\"sex\":\"man\"}";
// 自定義的32位16進制密鑰
String key = "cc9368581322479ebf3e79348a2757d9";
String cipher = Sm4Utils.encryptEcb(key, json,ENCODING);
System.out.println(cipher);
System.out.println(Sm4Utils.verifyEcb(key, cipher, json));
json = Sm4Utils.decryptEcb(key, cipher,ENCODING);
System.out.println(json);
} catch (Exception e) {
e.printStackTrace();
}
}
}我們來仔細看一下上面的代碼,首先定義了個4個靜態不可修改都變量,用于下方方法的使用。包括編碼utf-8,密碼名稱sm4,密碼的分組方式SM4/ECB/PKCS7Padding和默認的key值長度128。
整體的方法我們分為生成ecb暗號,自動生成密鑰,加密,解密,密碼校驗的算法。
以上為sm4utils的核心代碼。
/**
* @Description:md5加密工具
*/
public class Md5Utils {
/**
* @Description:獲得md5加密串
*/
public static String getMD5String(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
return new BigInteger(1, md.digest()).toString(16);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}MD5Utils主要用于對參數的完整性校驗,防止篡改。
此時我們已經實現了主要的加密解密工具,接下來實現spring的aop自定義注解,自定義注解我們要實現三個。
第一個是加密注解,用于方法上的,表示該方法的參數需要被加密。
第二個是解密注解,用于方法上的,表示該方法的參數需要被解密。
第三個是字段加密解密注解,用于標識實體類的字段是否需要被加密和解密。
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Order(-2147483648)
public @interface EncryptMethod {
}可以看到此時打了一個order注解,-2147483648用于標識優先級最高。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Order(-2147483648)
@Inherited
public @interface DecryptMethod {
}@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Order(-2147483648)
public @interface EncryptField {
}注解的準備工作已經做完,接下來就是實現sm4的切面方法。不了解spring的aop的實現方法,可以去先補一下spring的相關知識,這里不做贅述。我們這里采用的切面都是環繞通知,切面的切點是加密解密的注解。
說一下切面的答題實現思路。我們可以看到@ConditionalOnProperty(prefix = "sm4", value = "enable", matchIfMissing = false),這個我們做成了根據配置文件的配置進行動態的開關。
我們在appication.yml文件中進行如下的配置。這樣切面是否生效就取決于配置。然后我們捕捉加密的注解和解密的注解,然后對加了注解的方法中的邏輯進行加密和解密。
下方代碼的切點方法為EncryptAOPCut和DecryptAOPCut。隨后通過around對切點進行捕捉。分別調用的核心的加密算法encryptMethodAop和解密算法decryptMethodAop。
sm4: enable: true
核心的加密解密算法都是使用環繞通知的ProceedingJoinPoint類,從他的對象中我們可以取到spring的各種參數,包括request請求,請求的參數和response對象。
/**
* @Description:sm4加密解密切面
*/
@Order(-2147483648)
@Aspect
@Component
@ConditionalOnProperty(prefix = "sm4", value = "enable", matchIfMissing = false)
public class Sm4Aspect {
private Logger log = LoggerFactory.getLogger(Sm4Aspect.class);
private static final String DEFAULT_KEY = "cc9368581322479ebf3e79348a2757d9";
public Sm4Aspect() {
}
@Pointcut("@annotation(com.jichi.aop.sm4.EncryptMethod)")
public void EncryptAOPCut() {
}
@Pointcut("@annotation(com.jichi.aop.sm4.DecryptMethod)")
public void DecryptAOPCut() {
}
@Around("EncryptAOPCut()")
public Object encryptMethodAop(ProceedingJoinPoint joinPoint) {
Object responseObj = null;
try {
responseObj = joinPoint.proceed();
this.handleEncrypt(responseObj);
//md5加密
String md5Data = Md5Utils.getMD5String(new Gson().toJson(responseObj));
SpringContextUtil.getHttpServletResponse().setHeader("md5",md5Data);
} catch (Throwable throwable) {
throwable.printStackTrace();
this.log.error("encryptMethodAop處理出現異常{}", throwable);
}
return responseObj;
}
@Around("DecryptAOPCut()")
public Object decryptMethodAop(ProceedingJoinPoint joinPoint) {
Object responseObj = null;
try {
responseObj = joinPoint.getArgs()[0];
//throw new RuntimeException("md5校驗失敗");
this.handleDecrypt(responseObj);
String md5 = "";
md5 = Md5Utils.getMD5String(new Gson().toJson(responseObj));
System.out.println(md5);
String origianlMd5 = "";
origianlMd5 = SpringContextUtil.getHttpServletRequest().getHeader("md5");
if(origianlMd5.equals(md5)){
responseObj = joinPoint.proceed();
}else{
this.log.error("參數的md5校驗不同,可能存在篡改行為,請檢查!");
throw new Exception("參數的md5校驗不同,可能存在篡改行為,請檢查!");
}
} catch (Throwable throwable) {
throwable.printStackTrace();
this.log.error("decryptMethodAop處理出現異常{}", throwable);
}
return responseObj;
}
private void handleEncrypt(Object requestObj) throws Exception {
if (!Objects.isNull(requestObj)) {
Field[] fields = requestObj.getClass().getDeclaredFields();
Field[] fieldsCopy = fields;
int fieldLength = fields.length;
for(int i = 0; i < fieldLength; ++i) {
Field field = fieldsCopy[i];
boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
if (hasSecureField) {
field.setAccessible(true);
String plaintextValue = (String)field.get(requestObj);
String encryptValue = Sm4Utils.encryptEcb(DEFAULT_KEY, plaintextValue, "");
field.set(requestObj, encryptValue);
}
}
}
}
private Object handleDecrypt(Object responseObj) throws Exception {
if (Objects.isNull(responseObj)) {
return null;
} else {
Field[] fields = responseObj.getClass().getDeclaredFields();
Field[] fieldsCopy = fields;
int fieldLength = fields.length;
for(int i = 0; i < fieldLength; ++i) {
Field field = fieldsCopy[i];
boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
if (hasSecureField) {
field.setAccessible(true);
String encryptValue = (String)field.get(responseObj);
String plaintextValue = Sm4Utils.decryptEcb(DEFAULT_KEY, encryptValue, "");
field.set(responseObj, plaintextValue);
}
}
return responseObj;
}
}
}首先我們可以定義一個實體類,對實體類的字段進行加密或解密的標識。我們這里建立了一個Info實體類,對于其中的name屬性,我們加了注解加密解密字段,對于sex屬性我們不做任何處理。
@Data
public class Info {
@EncryptField
private String name;
private String sex;
}然后我們對于controller方法打上加密的方法或解密的方法。
@RestController
@RequestMapping("/demo/test")
public class TestController {
@PostMapping("/saveInfo")
@DecryptMethod
public HashMap<String,String> saveInfo(@RequestBody Info info) {
HashMap<String,String> result = new HashMap<String,String>();
String name = info.getName();
System.out.println(name);
String sex= info.getSex();
System.out.println(sex);
result.put("flag","1");
result.put("msg","操作成功");
return result;
}
}注意到方法上的注解@DecryptMethod,以為這著我們的這個方法將會進行解密。如果是@EncryptMethod,則代表對方法進行加密。
關于怎么在java項目中實現一個sm4加密算法就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。