java實(shí)現(xiàn)國產(chǎn)sm4加密算法
前言
今天給大家?guī)硪粋€國產(chǎn)SM4加密解密算法的java后端解決方案,代碼完整,可以直接使用,希望給大家?guī)韼椭?,尤其是做政府系統(tǒng)的開發(fā)人員,可以直接應(yīng)用到項(xiàng)目中進(jìn)行加密解密。
畫重點(diǎn)!是SM4哦,不是SM。哈哈,各位要在知識里遨游,不要想歪。正文開始~
國產(chǎn)SM4加密解密算法概念介紹
SMS4算法是在國內(nèi)廣泛使用的WAPI無線網(wǎng)絡(luò)標(biāo)準(zhǔn)中使用的加密算法,是一種32輪的迭代非平衡Feistel結(jié)構(gòu)的分組加密算法,其密鑰長度和分組長度均為128。SMS4算法的加解密過程中使用的算法是完全相同的,唯一不同點(diǎn)在于該算法的解密密鑰是由它的加密密鑰進(jìn)行逆序變換后得到的。
SMS4分組加密算法是中國無線標(biāo)準(zhǔn)中使用的分組加密算法,在2012年已經(jīng)被國家商用密碼管理局確定為國家密碼行業(yè)標(biāo)準(zhǔn),標(biāo)準(zhǔn)編號GM/T 0002-2012并且改名為SM4算法,與SM2橢圓曲線公鑰密碼算法,SM3密碼雜湊算法共同作為國家密碼的行業(yè)標(biāo)準(zhǔn),在我國密碼行業(yè)中有著極其重要的位置。
SMS4算法的分組長度為128bit,密鑰長度也是128bit。加解密算法均采用32輪非平衡Feistel迭代結(jié)構(gòu),該結(jié)構(gòu)最先出現(xiàn)在分組密碼LOKI的密鑰擴(kuò)展算法中。SMS4通過32輪非線性迭代后加上一個反序變換,這樣只需要解密密鑰是加密密鑰的逆序,就能使得解密算法與加密算法保持一致。SMS4加解密算法的結(jié)構(gòu)完全相同,只是在使用輪密鑰時解密密鑰是加密密鑰的逆序。
S盒是一種利用非線性變換構(gòu)造的分組密碼的一個組件,主要是為了實(shí)現(xiàn)分組密碼過程中的混淆的特性和設(shè)計的。SMS4算法中的S盒在設(shè)計之初完全按照歐美分組密碼的設(shè)計標(biāo)準(zhǔn)進(jìn)行,它采用的方法是能夠很好抵抗差值攻擊的仿射函數(shù)逆映射復(fù)合法。
SM4加密算法應(yīng)用場景
SM4常用于政府系統(tǒng)的數(shù)據(jù)傳輸加密,比如當(dāng)我們前端向后臺傳參數(shù)的時候,可以使用此算法。對參數(shù)的數(shù)據(jù)進(jìn)行加密,然后后臺對加密的數(shù)據(jù)進(jìn)行解密再存儲到數(shù)據(jù)庫中,保證數(shù)據(jù)傳輸過程中,不受泄露。
本次提供的方案不僅提供sm4的加密解密,還提供了md5算法的完整性防篡改校驗(yàn)。
Java端解決方案
對于java端,我們使用的基于spring的aop切面和自定義注解來實(shí)現(xiàn)。整體思路為,當(dāng)后臺開啟加密解密的時候,針對于打上注解的方法,尋找實(shí)體類中打上注解的字段進(jìn)行加密和解密。再從前端傳遞請求的request中取出md5的header,進(jìn)行md5的完整性,防篡改校驗(yàn)。
首先我們必須說的是兩個工具類,一個是SM4Utils工具類,另一個則是md5工具類。
下面先來說一下SM4Utils。這個工具類用于SM4算法的加密和解密及密碼校驗(yàn)。我們先直接看代碼,然后后面對此進(jìn)行解釋。
SM4Utils
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:密碼校驗(yàn) */ 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進(jìn)制密鑰 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(); } } }
我們來仔細(xì)看一下上面的代碼,首先定義了個4個靜態(tài)不可修改都變量,用于下方方法的使用。包括編碼utf-8,密碼名稱sm4,密碼的分組方式SM4/ECB/PKCS7Padding和默認(rèn)的key值長度128。
整體的方法我們分為生成ecb暗號,自動生成密鑰,加密,解密,密碼校驗(yàn)的算法。
以上為sm4utils的核心代碼。
MD5Utils
/** * @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主要用于對參數(shù)的完整性校驗(yàn),防止篡改。
此時我們已經(jīng)實(shí)現(xiàn)了主要的加密解密工具,接下來實(shí)現(xiàn)spring的aop自定義注解,自定義注解我們要實(shí)現(xiàn)三個。
第一個是加密注解,用于方法上的,表示該方法的參數(shù)需要被加密。
第二個是解密注解,用于方法上的,表示該方法的參數(shù)需要被解密。
第三個是字段加密解密注解,用于標(biāo)識實(shí)體類的字段是否需要被加密和解密。
加密的自定義注解
@Documented @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Order(-2147483648) public @interface EncryptMethod { }
可以看到此時打了一個order注解,-2147483648用于標(biāo)識優(yōu)先級最高。
解密的自定義注解
@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 { }
注解的準(zhǔn)備工作已經(jīng)做完,接下來就是實(shí)現(xiàn)sm4的切面方法。不了解spring的aop的實(shí)現(xiàn)方法,可以去先補(bǔ)一下spring的相關(guān)知識,這里不做贅述。我們這里采用的切面都是環(huán)繞通知,切面的切點(diǎn)是加密解密的注解。
加密解密切面的實(shí)現(xiàn)
說一下切面的答題實(shí)現(xiàn)思路。我們可以看到@ConditionalOnProperty(prefix = "sm4", value = "enable", matchIfMissing = false),這個我們做成了根據(jù)配置文件的配置進(jìn)行動態(tài)的開關(guān)。
我們在appication.yml文件中進(jìn)行如下的配置。這樣切面是否生效就取決于配置。然后我們捕捉加密的注解和解密的注解,然后對加了注解的方法中的邏輯進(jìn)行加密和解密。
下方代碼的切點(diǎn)方法為EncryptAOPCut和DecryptAOPCut。隨后通過around對切點(diǎn)進(jìn)行捕捉。分別調(diào)用的核心的加密算法encryptMethodAop和解密算法decryptMethodAop。
sm4: enable: true
核心的加密解密算法都是使用環(huán)繞通知的ProceedingJoinPoint類,從他的對象中我們可以取到spring的各種參數(shù),包括request請求,請求的參數(shù)和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處理出現(xiàn)異常{}", throwable); } return responseObj; } @Around("DecryptAOPCut()") public Object decryptMethodAop(ProceedingJoinPoint joinPoint) { Object responseObj = null; try { responseObj = joinPoint.getArgs()[0]; //throw new RuntimeException("md5校驗(yàn)失敗"); 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("參數(shù)的md5校驗(yàn)不同,可能存在篡改行為,請檢查!"); throw new Exception("參數(shù)的md5校驗(yàn)不同,可能存在篡改行為,請檢查!"); } } catch (Throwable throwable) { throwable.printStackTrace(); this.log.error("decryptMethodAop處理出現(xiàn)異常{}", 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; } } }
代碼實(shí)際應(yīng)用
首先我們可以定義一個實(shí)體類,對實(shí)體類的字段進(jìn)行加密或解密的標(biāo)識。我們這里建立了一個Info實(shí)體類,對于其中的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,以為這著我們的這個方法將會進(jìn)行解密。如果是@EncryptMethod,則代表對方法進(jìn)行加密。
總結(jié)
到此為止,涉及到j(luò)ava后端的代碼解決方案已經(jīng)完畢。示例代碼已經(jīng)給出,大家可以直接使用,本人親測有效。文中難免有不足,歡迎大家批評指正。
以上就是java實(shí)現(xiàn)國產(chǎn)sm4加密算法的詳細(xì)內(nèi)容,更多關(guān)于Java 國產(chǎn)sm4加密算法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
單點(diǎn)登錄的概念及SpringBoot實(shí)現(xiàn)單點(diǎn)登錄的操作方法
在本文中,我們將使用Spring Boot構(gòu)建一個基本的單點(diǎn)登錄系統(tǒng),我們將介紹如何使用Spring Security和JSON Web Tokens(JWTs)來實(shí)現(xiàn)單點(diǎn)登錄功能,本文假設(shè)您已經(jīng)熟悉Spring Boot和Spring Security,感興趣的朋友一起看看吧2024-10-10SpringBoot實(shí)現(xiàn)登錄校驗(yàn)(JWT令牌)
JWT全稱為JSON Web Token,是一種用于身份驗(yàn)證的開放標(biāo)準(zhǔn),本文主要介紹了SpringBoot實(shí)現(xiàn)登錄校驗(yàn)(JWT令牌),具有一定的參考價值,感興趣的可以了解一下2023-12-12log4j.properties 配置(實(shí)例講解)
下面小編就為大家?guī)硪黄猯og4j.properties 配置(實(shí)例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08jpa?onetomany?使用級連表刪除被維護(hù)表數(shù)據(jù)時的坑
這篇文章主要介紹了jpa?onetomany?使用級連表刪除被維護(hù)表數(shù)據(jù)時的坑,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12java對象與json對象之間互相轉(zhuǎn)換實(shí)現(xiàn)方法示例
這篇文章主要介紹了java對象與json對象之間互相轉(zhuǎn)換實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了java對象與json對象相互轉(zhuǎn)換實(shí)現(xiàn)步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-10-10Java中JSONObject和Map<String,?Object>的轉(zhuǎn)換方法
平時對接口時,經(jīng)常遇到j(luò)son字符串和map對象之間的交互,這篇文章主要給大家介紹了關(guān)于Java中JSONObject和Map<String,?Object>的轉(zhuǎn)換方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07