开源库
本文中用到的加密算法实现开源项目如下:
java:https://github.com/ZZMarquis/gmhelper
golang:https://github.com/Hyperledger-TWGC/ccs-gm
js:https://github.com/JuneAndGreen/sm-crypto
SM2
SM2的调试较为复杂,需要注意的点有两个:1、密钥对的生成;2、JS加解密时的特殊处理。
生成密钥对
经过测试,只有golang生成的密钥对才能在golang、java和js三端通用,生成方式如下:
iniSk, _ := sm2.GenerateKey(rand.Reader) iniPk := iniSk.PublicKey
pemSk, err := PrivateKeyToPEM(iniSk, nil) if err != nil { t.Errorf("private key to pem error %t", err) } fmt.Printf("pem私钥:%s", string(pemSk))
pemPk, err := PublicKeyToPEM(&iniPk, nil) if err != nil { t.Errorf("public key to pem error %t", err) } fmt.Printf("pem公钥:%s", string(pemPk))
|
生成的PEM密钥可以直接在golang和java中使用,但是js的密钥还需要做一次转换,这里使用Java做转换:
String privatePem = "这里填上文生成的pem私钥"; byte[] pkcs8PrivateKey = BCECUtil.convertECPrivateKeyPEMToPKCS8(privatePem); System.out.println(HexUtil.getHexString(pkcs8PrivateKey));
BCECPrivateKey ecpPrivateKey = BCECUtil.convertPKCS8ToECPrivateKey(pkcs8PrivateKey); ECPrivateKeyParameters privateKeyParameters = BCECUtil.convertPrivateKeyToParameters(ecpPrivateKey); System.out.println("JS私钥: " + HexUtil.byteToHex(privateKeyParameters.getD().toByteArray()));
String publicPem = "这里填上文生成的pem公钥"; byte[] x509PublicKey = BCECUtil.convertECPublicKeyPEMToX509(publicPem);
String publicKey = HexUtil.getHexString(x509PublicKey); System.out.println(publicKey);
BCECPublicKey bcecPublicKey = BCECUtil.convertX509ToECPublicKey(x509PublicKey); ECPublicKeyParameters publicKeyParameter = BCECUtil.convertPublicKeyToParameters(bcecPublicKey); System.out.println("JS公钥: " + HexUtil.byteToHex(publicKeyParameter.getQ().getEncoded(false)));
|
java的算法实现中没有给出 HexUtil
的实现,这里贴出我这里的实现:
HexUtil
golang加解密
func TestEncryptAndDecrypt(t *testing.T) { pempk := `这里填上文生成的pem公钥` normalPk, err := utils.PEMtoPublicKey([]byte(pempk), nil) if err != nil { fmt.Println(err) }
pemSk := `这里填上文生成的pem私钥` normalSk, err := utils.PEMtoPrivateKey([]byte(pemSk), nil) if err != nil { fmt.Println(err) }
msg := []byte("helloword123123!") cipher, err := sm2.Encrypt(rand.Reader, normalPk, msg) fmt.Println("===================================================") fmt.Println("加密后字符串:", hex.EncodeToString(cipher))
fmt.Println("===================================================") res, err := sm2.Decrypt(cipher, normalSk) if err != nil { fmt.Println("解密失败:", err) } fmt.Println("解密后字符串:", string(res)) }
|
java加解密
String publicPem = "这里填上文生成的pem公钥"; byte[] x509PublicKey = BCECUtil.convertECPublicKeyPEMToX509(publicPem); BCECPublicKey publicKey = BCECUtil.convertX509ToECPublicKey(x509PublicKey); byte[] cipherText = SM2Util.encrypt(publicKey, PLAIN_TEXT.getBytes(StandardCharsets.UTF_8)); System.out.println("加密后密文:"+HexUtil.getHexString(cipherText));
String privatePem = "这里填上文生成的pem私钥"; byte[] pkcs8PrivateKey = BCECUtil.convertECPrivateKeyPEMToPKCS8(privatePem); BCECPrivateKey privateKey = BCECUtil.convertPKCS8ToECPrivateKey(pkcs8PrivateKey); byte[] plainText = SM2Util.decrypt(privateKey, HexUtil.hexToByte("这里填加密后密文")); System.out.println("解密后明文:"+new String(plainText));
|
js加解密
注意:
JS生成的密文必须加前缀“04”才能被golang和java正确解密
golang和java生成的密文一定要去除前缀“04”,再将密文全部转为小写字母后方可正确解密
const sm2 = require('sm-crypto').sm2
encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode) console.log('加密后密文:', encryptData)
encryptData = '加密后密文' decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) console.log('解密后明文', decryptData)
|
SM3
SM3的调试比较简单,下面只列出各个语言的调用方法。
每种语言生成的密文会有大小写的差异,实际使用时可以忽略大小写进行对比。
golang加密
func TestSM3HashWithJava(t *testing.T) { hash := sm3.New() hash.Write([]byte("helloword!sm3")) hashed := hash.Sum(nil) fmt.Println("SM3加密密文: ", hex.EncodeToString(hashed)) }
|
java加密
@Test void testHash() { byte[] hashed = SM3Util.hash(PLAIN_TEXT.getBytes(StandardCharsets.UTF_8)); System.out.println(new String(hashed)); System.out.println(HexUtil.getHexString(hashed)); SM3Util.verify(PLAIN_TEXT.getBytes(StandardCharsets.UTF_8), hashed); }
|
js加密
msgString = 'helloword!sm3' const sm3 = require('sm-crypto').sm3 hashData = sm3(msgString) console.log('【hashData】', hashData)
|
SM4
SM4的调试也比较简单,只需要注意key的生成和js加密key的特殊处理
golang加解密
func TestSM4(t *testing.T) { key := []byte("0123456789abcdef") msg := []byte("0123456789abcdef012345678") encMsg, err := sm4.Sm4Ecb(key, msg, sm4.ENC) if err != nil { t.Errorf("sm4 enc error:%s", err) return } fmt.Println("加密后的密文:", hex.EncodeToString(encMsg))
encMsg, err = hex.DecodeString("13278C63B7305E3C131CDBBBE6F59B86A6B8937FE1E2B336779FE4DB0537E750") dec, err := sm4.Sm4Ecb(key, encMsg, sm4.DEC) if err != nil { t.Errorf("sm4 dec error:%s", err) return } fmt.Println("解密后的明文:", string(dec))
if !bytes.Equal(msg, dec) { t.Errorf("sm4 self enc and dec failed") } }
|
java加解密
@Test public void testECBPadding() throws Exception { byte[] key = "0123456789abcdef".getBytes(StandardCharsets.UTF_8); System.out.println("16进制加密key: " + HexUtil.byteToHex(key)); System.out.println("加密key: " + new String(key)); byte[] data = "0123456789abcdef012345678".getBytes(StandardCharsets.UTF_8);
byte[] cipherText = SM4Util.encrypt_ECB_Padding(key, data); System.out.println("SM4 ECB Padding encrypt result: " + HexUtil.byteToHex(cipherText));
byte[] decryptedData = SM4Util.decrypt_ECB_Padding(key, cipherText); System.out.println("SM4 ECB Padding decrypt result: " + new String(decryptedData)); System.out.println(new String(decryptedData)); if (!Arrays.equals(decryptedData, data)) { System.out.println("加解密失败"); } }
|
js加解密
const sm4 = require('sm-crypto').sm4
key = '30313233343536373839616263646566' msgString = '0123456789abcdef012345678'
encryptData = sm4.encrypt(msgString, key) console.log('【encryptData】', encryptData) decryptData = sm4.decrypt(encryptData, key) console.log('【decryptData】', decryptData)
|