有做網(wǎng)站的公司嗎長(zhǎng)沙seo推廣外包
文章目錄
- 前言
- 一. 代碼
前言
最近還要深度研究hutools底層實(shí)現(xiàn),一定要搞透澈,本章將會(huì)是持續(xù)更新
參考資料:
Java代碼實(shí)現(xiàn)SM2算法以及注意點(diǎn)總結(jié)(踩坑記錄)
國(guó)密算法工具Smutil
一. 代碼
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.ECKeyUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.symmetric.SM4;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;import java.nio.charset.StandardCharsets;
import java.security.KeyPair;/*** @author YuanJie* @date 2024/8/6 上午8:44* 加密方式* 加密方式里邊,有兩種 一種是 C1C3C2另一種是C1C2C3,* 這兩種加密方式不同,當(dāng)時(shí)找的資料說(shuō)是,舊版本的標(biāo)準(zhǔn)上,* 使用的是C1C2C3,但是后續(xù)應(yīng)該是更新過(guò),使用的是C1C3C2,* 其他語(yǔ)言,比如Python或者Golang或者C的實(shí)現(xiàn)大多直接就是C1C3C2的,* 但是如果java中使用的bouncycastle的包,默認(rèn)使用的是C1C2C3,* 就會(huì)發(fā)生與其他語(yǔ)言的加密結(jié)果不能相互解密的情況,但是可能你跟其他人的Java系統(tǒng)加解密又沒(méi)有問(wèn)題。* <p>* 導(dǎo)出* Java生成的密鑰,在導(dǎo)出的時(shí)候,最好使用 PrivateKey.getD().toByteArray() 和 PublicKey.getQ().getEncoded() 導(dǎo)出密鑰,然后再轉(zhuǎn)成Base64或者Hex給其他系統(tǒng);* <p>* 導(dǎo)入* 導(dǎo)入的時(shí)候,請(qǐng)使用我上邊的代碼轉(zhuǎn)換成對(duì)應(yīng)的公鑰或者私鑰的對(duì)象,* 這里有另一個(gè)容易出錯(cuò)的地方,在私鑰byte[]轉(zhuǎn)私鑰對(duì)象的時(shí)候,* 有些人會(huì)使用new BigInteger(byte[])這個(gè)方法將byte[]轉(zhuǎn)為BigInteger,* 然后調(diào)用 keyFactory.generatePrivate(new ECPrivateKeySpec(BigInteger, ECParameterSpec)),* 但是實(shí)測(cè)這樣在一些情況下會(huì)報(bào)錯(cuò),這個(gè)時(shí)間長(zhǎng)了記不清具體原因了,好像是因?yàn)榈谝晃粸樨?fù)數(shù)的情況下會(huì)報(bào)錯(cuò),* 我的方法是把byte[]轉(zhuǎn)為Hex,然后再使用new BigInteger(hexStr, int)這種方式轉(zhuǎn)為BigInteger,這個(gè)需要注意。* <p>* 公鑰壓縮* publicKey.getQ().getEncoded()這個(gè)方法,* 有一個(gè)boolean參數(shù),控制輸出的密鑰是否為壓縮后的密鑰,* 輸出內(nèi)容轉(zhuǎn)為Hex之后,02和03開(kāi)頭的為壓縮后的密鑰,04表示未經(jīng)壓縮的密鑰。* Java里邊壓縮為壓縮的調(diào)用同一個(gè)方法就能轉(zhuǎn)為公鑰對(duì)象,但是其他語(yǔ)言的目前不清楚,所以導(dǎo)出的時(shí)候,最好標(biāo)注一下壓縮或者未壓縮。* <p>* Sm2簽名時(shí),有一個(gè)userId的概念,這個(gè)東西一般直接用CipherParameters對(duì)象是帶不進(jìn)去的,* 如果沒(méi)有,默認(rèn)是 Hex.decodeStrict(“31323334353637383132333435363738”) ,* 也就是1234567812345678的Ascii值,如果要使用自定義的userId,則需要使用ParametersWithID這個(gè)對(duì)象調(diào)用Signer的init,* 這個(gè)對(duì)象可以傳入一個(gè)CipherParameters,然后再傳入一個(gè)userId,可以把制定的userId帶進(jìn)去。* <p>* 簽名驗(yàn)簽RS* 我們平常接觸的算法,一般我們調(diào)用加解密算法只返回一個(gè)值,* 但是Sm2算法,簽名其實(shí)是有兩個(gè)值,一個(gè)R,一個(gè)S,* 兩個(gè)值構(gòu)成一個(gè)簽名結(jié)果,Java中bouncycastle的返回雖然也是一個(gè)值,* 但是大概看了一下算法的實(shí)現(xiàn)代碼,其實(shí)得出的結(jié)果也是兩個(gè)值,* 一個(gè)R,一個(gè)S,然后通過(guò)一個(gè)方法拼接成一個(gè)值(不確定這個(gè)方法的轉(zhuǎn)換方式是不是有標(biāo)準(zhǔn)的)。* 在我們與C程序一塊測(cè)試的時(shí)候,他們反饋了這個(gè)問(wèn)題,* 然后我對(duì)著bouncycastle包內(nèi)的org.bouncycastle.crypto.signers.SM2Signer類(lèi),* 摘出來(lái)了R、S和單一返回值的轉(zhuǎn)換代碼,已經(jīng)提取到上邊的代碼里,可以直接使用。*/
@Configuration
@Slf4j
public class NationalSecretsUtils {public static String privateKey2;public static String publicKey2;public static String secretKey4;public NationalSecretsUtils(@Value("${sm2.privateKey}") String privateKey2,@Value("${sm2.publicKey}") String publicKey2,@Value("${sm4.secretKey}") String secretKey4) {NationalSecretsUtils.privateKey2 = privateKey2;NationalSecretsUtils.publicKey2 = publicKey2;NationalSecretsUtils.secretKey4 = secretKey4;}public static void main(String[] args) {KeyPair keyPair = SecureUtil.generateKeyPair("SM2");// 2. 獲取公私鑰//這里公鑰不壓縮 公鑰的第一個(gè)字節(jié)用于表示是否壓縮 可以不要 65字節(jié)變64字節(jié)byte[] publicKey = ((BCECPublicKey) keyPair.getPublic()).getQ().getEncoded(false);byte[] privateKey = ((BCECPrivateKey) keyPair.getPrivate()).getD().toByteArray();String sm2pub = HexUtil.encodeHexStr(publicKey);String sm2pri = HexUtil.encodeHexStr(privateKey);NationalSecretsUtils.publicKey2 = sm2pub;NationalSecretsUtils.privateKey2 = sm2pri;NationalSecretsUtils.secretKey4 = HexUtil.encodeHexStr(SecureUtil.generateKey("SM4").getEncoded());log.info("sm2公鑰:{}", sm2pub);log.info("sm2私鑰:{}", sm2pri);log.info("sm4密鑰:{}", NationalSecretsUtils.secretKey4);// 測(cè)試sm2加解密log.info("sm2簽名:{}", HexUtil.encodeHexStr(generateSM2Sign("我是sm2數(shù)據(jù)")));log.info("sm2驗(yàn)證:{}", verifySM2Sign("我是sm2數(shù)據(jù)", generateSM2Sign("我是sm2數(shù)據(jù)")));// 測(cè)試sm4加解密log.info("sm4加密:{}", HexUtil.encodeHexStr(generateSM4Sign("我是sm4數(shù)據(jù)")));log.info("sm4解密:{}", decryptSM4Sign(generateSM4Sign("我是sm4數(shù)據(jù)")));// 測(cè)試sm3哈希算法log.info("sm3哈希算法:{}", generateSM3Sign("我是sm3數(shù)據(jù)"));log.info("sm3驗(yàn)證:{}", verifySM3Sign("我是sm3數(shù)據(jù)", generateSM3Sign("我是sm3數(shù)據(jù)")));}/*** SM2簽名 使用私鑰D值簽名* 默認(rèn)C1C3C2** @param text* @return*/public static byte[] generateSM2Sign(String text) {SM2 sm2 = new SM2(privateKey2, null, null);sm2.usePlainEncoding();return sm2.sign(text.getBytes(StandardCharsets.UTF_8), null);}/*** SM2驗(yàn)證 使用公鑰Q值驗(yàn)證簽名** @param text* @param sign* @return*/public static boolean verifySM2Sign(String text, byte[] sign) {SM2 sm2 = new SM2(null, ECKeyUtil.toSm2PublicParams(publicKey2));sm2.usePlainEncoding();return sm2.verify(text.getBytes(StandardCharsets.UTF_8), sign);}/*** SM3 哈希算法 3是能夠計(jì)算出256比特的散列值的單向散列函數(shù),主要用于數(shù)字簽名和消息認(rèn)證碼。*/public static String generateSM3Sign(String text) {return SmUtil.sm3(text);}/*** SM3驗(yàn)證** @param text* @param sign* @return*/public static boolean verifySM3Sign(String text, String sign) {return SmUtil.sm3(text).equals(sign);}/*** SM4 是一種對(duì)稱加密算法,它使用128位密鑰,使用分組密碼模式,使用CBC模式加密。* SM4加密*/public static String generateSM4Sign(String text) {SM4 sm4 = SmUtil.sm4(HexUtil.decodeHex(secretKey4));return sm4.encryptHex(text);}/*** SM4解密*/public static String decryptSM4Sign(String text) {SM4 sm4 = SmUtil.sm4(HexUtil.decodeHex(secretKey4));return sm4.decryptStr(text, StandardCharsets.UTF_8);}
}