lpilp / phpsm2sm3sm4

php版本,支持国密SM2的签名算法,非对称加解密,SM3的hash, SM4的对称加解密
329 stars 76 forks source link

phpSM2对接javaSM2 #93

Closed HelloYang666 closed 1 month ago

HelloYang666 commented 1 month ago

php使用sm2加密后,java无法解密。java加密的数据php也无法解密

### java代码 package com.aisino.risk.detection.business.util; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Base64;

import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.KeyGenerationParameters; import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.crypto.generators.ECKeyPairGenerator; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECKeyGenerationParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.encoders.Hex;

import com.aisino.risk.common.vo.Sm2KeyVo; import com.aisino.risk.common.web.ex.InterruptBusinessException;

public class SM2Util { private static String CHARSET_UTF8="UTF-8";

/**
 * SM2加密
 * @param data          待加密数据
 * @param publicKey     公钥
 * @return
 * @throws UnsupportedEncodingException
 * @throws InvalidCipherTextException
 */
public static String encryptByPublicKey(String data,String publicKey) {
    byte[] arrayOfBytes = null;
    try {
      X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
      ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
              sm2ECParameters.getG(),
              sm2ECParameters.getN());
      ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(hexString2byte(publicKey));
      // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
      ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
      SM2Engine sm2Engine = new SM2Engine();
      sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

      byte[] in = data.getBytes(CHARSET_UTF8);
      arrayOfBytes =  Base64.getEncoder().encode(sm2Engine.processBlock(in, 0, in.length));
    }catch (Exception e) {
        e.printStackTrace();
        throw new InterruptBusinessException("参数解密失败");
    }
    return Hex.toHexString(arrayOfBytes);
}

/**
 *  SM2解密
 * @param encrtpyData   待解密数据
 * @param privateKey    私钥
 * @return
 * @throws InvalidCipherTextException
 * @throws UnsupportedEncodingException
 */
public static String decryptByPrivateKey(String encrtpyData,String privateKey){
    try {
       X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
       ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
       byte[] cipherDataByte = Base64.getDecoder().decode(Hex.decode(encrtpyData)) ;
       BigInteger privateKeyD = new BigInteger(privateKey, 16);
       ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
       SM2Engine sm2Engine = new SM2Engine();
       sm2Engine.init(false, privateKeyParameters);
       byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
        return new String(arrayOfBytes,CHARSET_UTF8);
    }catch (Exception e) {
        e.printStackTrace();
        throw new InterruptBusinessException("参数解密失败");
    }
}

public static Sm2KeyVo getKeys() throws NoSuchAlgorithmException {
    X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
    ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
    ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
    keyPairGenerator.init((KeyGenerationParameters)new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
    AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
    BigInteger privatekey = ((ECPrivateKeyParameters)asymmetricCipherKeyPair.getPrivate()).getD();
    String privateKeyHex = privatekey.toString(16);
    ECPoint ecPoint = ((ECPublicKeyParameters)asymmetricCipherKeyPair.getPublic()).getQ();
    String publicKeyHex = Hex.toHexString(ecPoint.getEncoded(false));
    Sm2KeyVo sm2KeyVo = new Sm2KeyVo();
    sm2KeyVo.setPrivateKey(privateKeyHex);
    sm2KeyVo.setPublicKey(publicKeyHex);
    return sm2KeyVo;
}

/**
 * @param hexString 16进制字符串    如:"33d20046" 转换为 0x33 0xD2 0x00 0x46
 * @return
 * @Title: hexString2byte
 * @Description: 16进制字符串转字节数组
 * @since: 0.0.1
 */
public static byte[] hexString2byte(String hexString) {
    if (null == hexString || hexString.length() % 2 != 0 || hexString.contains("null")) {
        return null;
    }
    byte[] bytes = new byte[hexString.length() / 2];
    for (int i = 0; i < hexString.length(); i += 2) {
        bytes[i / 2] = (byte) (Integer.parseInt(hexString.substring(i, i + 2), 16) & 0xff);
    }
    return bytes;
}

public static void main(String[] args) throws InvalidCipherTextException, UnsupportedEncodingException, NoSuchAlgorithmException {

// //region 生成公私钥 Sm2KeyVo Sm2KeyVo = getKeys(); String qifuClientsm2pubkey = Sm2KeyVo.getPublicKey(); System.out.println("sm2pubkey:" + qifuClientsm2pubkey); String qifuServersm2prikey = Sm2KeyVo.getPrivateKey(); System.out.println("PrivateKey:" + qifuServersm2prikey); //endregion

  //region 用公钥加密

// String content = "{\"openId\":\"11\",\"uniqueId\":\"11\"}"; // String publicKey = "04e00a77ba5c1f50a2a3144b0fa2eb1bda2e00bb5c56bf10f2e3808429b30e1a2836c1c4e664e12f84749fd14eeb0b60d66dd15c56e7dc527c930778aaa60ae433"; // String s = encryptByPublicKey(content, publicKey); // System.out.println(s); //endregion

  //region 私钥解密

// String ciphertext = "42504c66646b4d6e4a55646d69566145644447616c4c524b6747784a4b516674564b744374774c69614235506d546f666336384c485359774e68393974746c585a534547524e6b6339464270304670312b4e41506233687755583179715a754c38724f49444e624663316268416f352f4c38442f506c525a595a68586c4d7771537356643337796f68773d3d"; // String responseEncry = decryptByPrivateKey(ciphertext, "f49ea2f2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5e3a04cb1"); // System.out.println(responseEncry); //endregion

//

} }

lpilp commented 1 month ago

这个java是对方的还是你封装的, 有两个问题: 1 SM2Engine sm2Engine = new SM2Engine(mode = Mode.C1C2C3 ); 这里有个参数 mode , 不填的话,缺省是 c1c2c3样式的, 新国密标准是c1c3c2, 这个参数可调,JAVA中没填就是 c1c2c3模式,请注意 2 这个BC库加密出来是 byte[] 返回直接, JAVA代码中 是 arrayOfBytes = Base64.getEncoder().encode(sm2Engine.processBlock(in, 0, in.length)); 函数中base64 了,这个也没问题,但后面最后返回又转成hex, 正常是返回base64或是hex, 这个加了两重了,用PHP解密的时候也得多解一次hex

// php中这么解就行了,
$m2EncryptData = '42504c66646b4d6e4a55646d69566145644447616c4c524b6747784a4b516674564b744374774c69614235506d546f666336384c485359774e68393974746c585a534547524e6b6339464270304670312b4e41506233687755583179715a754c38724f49444e624663316268416f352f4c38442f506c525a595a68586c4d7771537356643337796f68773d3d';
$m2EncryptData = bin2hex(base64_decode(hex2bin($m2EncryptData)));
$privateKey = "f49ea2f274c5a982..........12c2d4c6c4c532f5e3a04cb1";
$m2DecryptData = $sm2->doDecrypt($m2EncryptData,$privateKey,1,C1C2C3);
echo ("\n解密后: ".$m2DecryptData);

如果你用PHP加密也注意下 比如我的代码生成的是hex ==》bin=>base64==>hex 注意下,java 缺省用的 c1c2c3模式,php缺省是c1c3c2模式,加密的时候加上模式参数

lpilp commented 1 month ago

例子中解开后是“成功”二字

HelloYang666 commented 1 month ago

这个java是对方的还是你封装的,有两个问题: 1 SM2Engine sm2Engine = new SM2Engine(mode = Mode.C1C2C3 ); 这里有个参数模式,不填的话,是c1c2c3风格的,新国密标准是c1c3c2,这个参数可调,JAVA中没填就是c1c2c3模式,请注意 2这个BC库加密出来的是byte[]返回直接,JAVA代码中 是 arrayOfBytes = Base64.getEncoder().encode(sm2Engine.processBlock(in, 0, in.length)); 函数中base64了,这个也没有问题,但是后面最后返回又转成hex,正常是返回base64或者hex,这个加了两次重了,用PHP解密的时候也解解了一次hex

// php中这么解就行了,
$m2EncryptData = '42504c66646b4d6e4a55646d69566145644447616c4c524b6747784a4b516674564b744374774c69614235506d546f666336384c485359774e68393974746c585a534547524e6b6339464270304670312b4e41506233687755583179715a754c38724f49444e624663316268416f352f4c38442f506c525a595a68586c4d7771537356643337796f68773d3d';
$m2EncryptData = bin2hex(base64_decode(hex2bin($m2EncryptData)));
$privateKey = "f49ea2f274c5a982..........12c2d4c6c4c532f5e3a04cb1";
$m2DecryptData = $sm2->doDecrypt($m2EncryptData,$privateKey,1,C1C2C3);
echo ("\n解密后: ".$m2DecryptData);

如果你用PHP加密的是也注意下比如我的代码生成hex ==》bin=>base64==>hex注意下,java 操作用的 c1c2c3 模式,php 调用是 c1c3c2 模式,加密的加上时候模式参数

java是对方封装的,我这边使用的php。使用你写的php解密可以正常解密了,但是加密还是不行,麻烦再帮忙给看看

/**
 * phpSm2加密
 */
public static function doEncrypt($arguments)
{
    $m2EncryptData = self::$sm2->doEncrypt($arguments, self::$publicKey, C1C2C3);
    $result = bin2hex(base64_encode(hex2bin($m2EncryptData)));
    return $result;
}