diff --git a/.gitignore b/.gitignore index 19b29e8..3191068 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .git/ out/ min-dev-java.iml -target \ No newline at end of file +target +log/ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 18dcd49..3d48976 100644 --- a/pom.xml +++ b/pom.xml @@ -8,14 +8,44 @@ min-dev-java 1.0 - + + + + src/main/resources + + **/* + + + + + + + junit junit 4.12 test + + + org.bouncycastle + bcprov-jdk15on + 1.65 + + + org.bouncycastle + bcpkix-jdk15on + 1.65 + + + + + ch.qos.logback + logback-classic + 1.2.3 + \ No newline at end of file diff --git a/src/main/java/minsecurity/PrivateKeyInterface.java b/src/main/java/minsecurity/PrivateKeyInterface.java new file mode 100644 index 0000000..6d99120 --- /dev/null +++ b/src/main/java/minsecurity/PrivateKeyInterface.java @@ -0,0 +1,18 @@ +package minsecurity; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.InvalidCipherTextException; + +/* + * @Author: hongyu guo + * @Description: 所有对称加密方法必须实现的基本方法 + * @Version: 1.0.0 + * @Date: 17:28 2021/03/03 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ +public interface PrivateKeyInterface { + public byte[] getBytes(); + public boolean setBytes(byte[] bytes); + public byte[] decrypt(byte[] content) throws InvalidCipherTextException; + public byte[] sign(byte[] content) throws CryptoException; +} diff --git a/src/main/java/minsecurity/PublicKeyInterface.java b/src/main/java/minsecurity/PublicKeyInterface.java new file mode 100644 index 0000000..3e1be32 --- /dev/null +++ b/src/main/java/minsecurity/PublicKeyInterface.java @@ -0,0 +1,17 @@ +package minsecurity; +/* + * @Author: hongyu guo + * @Description: 所有对称加密方法必须实现的基本方法 + * @Version: 1.0.0 + * @Date: 17:06 2021/03/03 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ + +import org.bouncycastle.crypto.InvalidCipherTextException; + +public interface PublicKeyInterface { + public byte[] getBytes(); + public boolean setBytes(byte[] bytes); + public byte[] encrypt(byte[] content) throws InvalidCipherTextException; + public boolean verify(byte[] content, byte[] digest); +} \ No newline at end of file diff --git a/src/main/java/minsecurity/crypto/sm2/Sm2Base.java b/src/main/java/minsecurity/crypto/sm2/Sm2Base.java new file mode 100644 index 0000000..b489aba --- /dev/null +++ b/src/main/java/minsecurity/crypto/sm2/Sm2Base.java @@ -0,0 +1,49 @@ +package minsecurity.crypto.sm2; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +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.math.ec.ECPoint; +import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/* + * @Author: hongyu guo + * @Description: SM2推荐曲线参数 + * @Version: 1.0.0 + * @Date: 18:48 2021/03/03 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ +public class Sm2Base { + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Sm2Base.class); + public static final SM2P256V1Curve CURVE = new SM2P256V1Curve(); + public final static BigInteger SM2_ECC_P = CURVE.getQ(); + public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger(); + public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger(); + public final static BigInteger SM2_ECC_N = CURVE.getOrder(); + public final static BigInteger SM2_ECC_H = CURVE.getCofactor(); + public final static BigInteger SM2_ECC_GX = new BigInteger( + "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); + public final static BigInteger SM2_ECC_GY = new BigInteger( + "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); + public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY); + public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, + SM2_ECC_N, SM2_ECC_H); + + public static AsymmetricCipherKeyPair generateKeyPairParameter(){ + SecureRandom random = new SecureRandom(); + ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(DOMAIN_PARAMS, + random); + ECKeyPairGenerator keyGen = new ECKeyPairGenerator(); + keyGen.init(keyGenerationParams); + AsymmetricCipherKeyPair keyPair = keyGen.generateKeyPair(); + ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) keyPair.getPrivate(); + logger.debug("priv D: bitlen = {}, d.len = {}",privateKeyParameters.getD().bitLength(),privateKeyParameters.getD().toByteArray().length); + return keyPair; + } +} diff --git a/src/main/java/minsecurity/crypto/sm2/Sm2Exception.java b/src/main/java/minsecurity/crypto/sm2/Sm2Exception.java new file mode 100644 index 0000000..0f31a91 --- /dev/null +++ b/src/main/java/minsecurity/crypto/sm2/Sm2Exception.java @@ -0,0 +1,14 @@ +package minsecurity.crypto.sm2; + +/* + * @Author: hongyu guo + * @Description: sm2的自定义异常 + * @Version: 1.0.0 + * @Date: 19:00 2021/03/03 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ +public class Sm2Exception extends java.lang.Exception { + public Sm2Exception(String msg){ + super(msg); + } +} diff --git a/src/main/java/minsecurity/crypto/sm2/Sm2PrivateKey.java b/src/main/java/minsecurity/crypto/sm2/Sm2PrivateKey.java new file mode 100644 index 0000000..60377d8 --- /dev/null +++ b/src/main/java/minsecurity/crypto/sm2/Sm2PrivateKey.java @@ -0,0 +1,83 @@ +package minsecurity.crypto.sm2; + +import minsecurity.PrivateKeyInterface; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithID; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.signers.SM2Signer; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/* + * @Author: hongyu guo + * @Description: 国密sm2 非对称加密私钥 + * @Version: 1.0.0 + * @Date: 19:40 2021/03/03 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ +public class Sm2PrivateKey implements PrivateKeyInterface { + + Sm2PrivateKey(){} + + Sm2PrivateKey(byte[] d) { + if(d.length == 32 || d.length == 33) + privateKey = new ECPrivateKeyParameters(new BigInteger(d), Sm2Base.DOMAIN_PARAMS); + else { + try { + throw new Sm2Exception("SM2私钥中,参数d长度应为32/33"); + } catch (Sm2Exception e) { + e.printStackTrace(); + } + } + } + Sm2PrivateKey(byte[] d, ECDomainParameters parameters){ + if(d.length == 32 || d.length == 33) + privateKey = new ECPrivateKeyParameters(new BigInteger(d), parameters); + else { + try { + throw new Sm2Exception("SM2私钥中,参数d长度应为32/33"); + } catch (Sm2Exception e) { + e.printStackTrace(); + } + } + } + + @Override + public byte[] getBytes() { + return privateKey.getD().toByteArray(); + } + + @Override + public boolean setBytes(byte[] d) { + if(d.length != 32 && d.length != 33) + return false; + privateKey = new ECPrivateKeyParameters(new BigInteger(d), Sm2Base.DOMAIN_PARAMS); + return true; + } + + @Override + public byte[] decrypt(byte[] cipher) throws InvalidCipherTextException { + SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C2C3); + engine.init(false, privateKey); + return engine.processBlock(cipher, 0, cipher.length); + } + + @Override + public byte[] sign(byte[] content) throws CryptoException { + SM2Signer signer = new SM2Signer(); + CipherParameters param = null; + ParametersWithRandom pwr = new ParametersWithRandom(privateKey, new SecureRandom()); + param = pwr; + signer.init(true, param); + signer.update(content, 0, content.length); + return signer.generateSignature(); + } + + private ECPrivateKeyParameters privateKey; +} diff --git a/src/main/java/minsecurity/crypto/sm2/Sm2PublicKey.java b/src/main/java/minsecurity/crypto/sm2/Sm2PublicKey.java new file mode 100644 index 0000000..a954d0c --- /dev/null +++ b/src/main/java/minsecurity/crypto/sm2/Sm2PublicKey.java @@ -0,0 +1,114 @@ +package minsecurity.crypto.sm2; + +import minsecurity.PublicKeyInterface; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.signers.SM2Signer; +import org.bouncycastle.math.ec.ECCurve; + +import java.nio.ByteBuffer; +import java.security.SecureRandom; + +/* + * @Author: hongyu guo + * @Description: 国密sm2 非对称加密公钥 + * @Version: 1.0.0 + * @Date: 17:32 2021/03/03 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ +public class Sm2PublicKey implements PublicKeyInterface { + + Sm2PublicKey(){} + + public Sm2PublicKey(byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters){ + publicKey = createPublicKey(xBytes,yBytes,curve,domainParameters); + } + + public Sm2PublicKey(byte[] xBytes, byte[] yBytes){ + this(xBytes,yBytes, Sm2Base.CURVE, Sm2Base.DOMAIN_PARAMS); + } + + + + @Override + public byte[] getBytes() { + if(publicKey != null) + return publicKey.getQ().getEncoded(false); //不压缩 + return new byte[0]; + } + + @Override + public boolean setBytes(byte[] bytes) { + if(bytes.length != 65) + return false; + ByteBuffer encoded = ByteBuffer.wrap(bytes); + byte[] x = new byte[32]; + byte[] y = new byte[32]; + encoded.position(1); + encoded.get(x,0,32); + encoded.get(y,0,32); + publicKey = createPublicKey(x,y); + return true; + } + + @Override + public byte[] encrypt(byte[] content) throws InvalidCipherTextException { + SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C2C3); + ParametersWithRandom pwr = new ParametersWithRandom(publicKey, new SecureRandom()); + engine.init(true, pwr); + return engine.processBlock(content, 0, content.length); + } + + @Override + public boolean verify(byte[] content, byte[] digest) { + SM2Signer signer = new SM2Signer(); + CipherParameters param = publicKey; + signer.init(false, param); + signer.update(content, 0, content.length); + return signer.verifySignature(digest); + } + + + ////////////////////////////////////////////// private start ////////////////////////////////////////////// + private static ECPublicKeyParameters createPublicKey( + byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters){ + final byte uncompressedFlag = 0x04; + int curveLength = getCurveLength(domainParameters); + xBytes = fixToCurveLengthBytes(curveLength, xBytes); + yBytes = fixToCurveLengthBytes(curveLength, yBytes); + byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length]; + encodedPubKey[0] = uncompressedFlag; + System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length); + System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length); + return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters); + } + + private static ECPublicKeyParameters createPublicKey(byte[] xBytes, byte[] yBytes){ + return createPublicKey(xBytes,yBytes,Sm2Base.CURVE,Sm2Base.DOMAIN_PARAMS); + } + + private static int getCurveLength(ECDomainParameters domainParams) { + return (domainParams.getCurve().getFieldSize() + 7) / 8; + } + + private static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) { + if (src.length == curveLength) { + return src; + } + + byte[] result = new byte[curveLength]; + if (src.length > curveLength) { + System.arraycopy(src, src.length - result.length, result, 0, result.length); + } else { + System.arraycopy(src, 0, result, result.length - src.length, src.length); + } + return result; + } + ////////////////////////////////////////////// private end ////////////////////////////////////////////// + + ECPublicKeyParameters publicKey; +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..efa15ce --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log_dir}/error/%d{yyyy-MM-dd}/error-log.log + + + ${maxHistory} + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + + + WARN + + ACCEPT + + DENY + + + + ${log_dir}/warn/%d{yyyy-MM-dd}/warn-log.log + ${maxHistory} + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + INFO + ACCEPT + DENY + + + ${log_dir}/info/%d{yyyy-MM-dd}/info-log.log + ${maxHistory} + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + DEBUG + ACCEPT + DENY + + + ${log_dir}/debug/%d{yyyy-MM-dd}/debug-log.log + ${maxHistory} + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + TRACE + ACCEPT + DENY + + + ${log_dir}/trace/%d{yyyy-MM-dd}/trace-log.log + ${maxHistory} + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/minsecurity/crypto/sm2/TestSm2.java b/src/test/java/minsecurity/crypto/sm2/TestSm2.java new file mode 100644 index 0000000..9ab52f7 --- /dev/null +++ b/src/test/java/minsecurity/crypto/sm2/TestSm2.java @@ -0,0 +1,99 @@ +package minsecurity.crypto.sm2; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.junit.Test; +import static org.junit.Assert.*; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +/* + * @Author: hongyu guo + * @Description: 测试Sm2方法 + * @Version: 1.0.0 + * @Date: 20:15 2021/03/03 + * @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室 + */ +public class TestSm2 { + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(TestSm2.class); + @Test + public void testSetGet() throws InvalidCipherTextException { + AsymmetricCipherKeyPair keyPair = Sm2Base.generateKeyPairParameter(); + ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate(); + ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic(); + byte[] d = priKey.getD().toByteArray(); + byte[] x = pubKey.getQ().getAffineXCoord().getEncoded(); + byte[] y = pubKey.getQ().getAffineYCoord().getEncoded(); + logger.debug("d.len = {}, x.len = {}, y.len = {}",d.length, x.length, y.length); + // BigInteger bigInteger = priKey.getD(); + Sm2PrivateKey sm2PrivateKey = new Sm2PrivateKey(); + Sm2PublicKey sm2PublicKey = new Sm2PublicKey(); + + sm2PrivateKey.setBytes(d); + + ByteBuffer buffer = ByteBuffer.allocate(65); + buffer.put((byte)4); + buffer.put(x); + buffer.put(y); + sm2PublicKey.setBytes(buffer.array()); + + assertArrayEquals(d, sm2PrivateKey.getBytes()); + assertArrayEquals(buffer.array(), sm2PublicKey.getBytes()); + + String content = "hello world"; + byte[] encBytes = sm2PublicKey.encrypt(content.getBytes()); + byte[] decBytes = sm2PrivateKey.decrypt(encBytes); + // logger.debug(new String(decBytes, StandardCharsets.UTF_8)); + assertArrayEquals(content.getBytes(), decBytes); + + + } + + + @Test + public void testEncAndDec() throws InvalidCipherTextException { + AsymmetricCipherKeyPair keyPair = Sm2Base.generateKeyPairParameter(); + ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate(); + ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic(); + byte[] d = priKey.getD().toByteArray(); + // d = Arrays.copyOf(d,32); + byte[] x = pubKey.getQ().getAffineXCoord().getEncoded(); + byte[] y = pubKey.getQ().getAffineYCoord().getEncoded(); + logger.debug("d.len = {}, x.len = {}, y.len = {}",d.length, x.length, y.length); + // BigInteger bigInteger = priKey.getD(); + Sm2PrivateKey sm2PrivateKey = new Sm2PrivateKey(d); + Sm2PublicKey sm2PublicKey = new Sm2PublicKey(x,y); + + String content = "hello world"; + byte[] encBytes = sm2PublicKey.encrypt(content.getBytes()); + byte[] decBytes = sm2PrivateKey.decrypt(encBytes); + // logger.debug(new String(decBytes, StandardCharsets.UTF_8)); + assertArrayEquals(content.getBytes(), decBytes); + } + + @Test + public void testSignAndVerify() throws CryptoException { + AsymmetricCipherKeyPair keyPair = Sm2Base.generateKeyPairParameter(); + ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate(); + ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic(); + byte[] d = priKey.getD().toByteArray(); + // d = Arrays.copyOf(d,32); + byte[] x = pubKey.getQ().getAffineXCoord().getEncoded(); + byte[] y = pubKey.getQ().getAffineYCoord().getEncoded(); + logger.debug("d.len = {}, x.len = {}, y.len = {}",d.length, x.length, y.length); + // BigInteger bigInteger = priKey.getD(); + Sm2PrivateKey sm2PrivateKey = new Sm2PrivateKey(d); + Sm2PublicKey sm2PublicKey = new Sm2PublicKey(x,y); + String content = "this is hell"; + byte[] digest = sm2PrivateKey.sign(content.getBytes()); + boolean flag = sm2PublicKey.verify(content.getBytes(), digest); + assertTrue(flag); + } +} diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml new file mode 100644 index 0000000..efa15ce --- /dev/null +++ b/src/test/resources/logback.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log_dir}/error/%d{yyyy-MM-dd}/error-log.log + + + ${maxHistory} + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + + + WARN + + ACCEPT + + DENY + + + + ${log_dir}/warn/%d{yyyy-MM-dd}/warn-log.log + ${maxHistory} + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + INFO + ACCEPT + DENY + + + ${log_dir}/info/%d{yyyy-MM-dd}/info-log.log + ${maxHistory} + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + DEBUG + ACCEPT + DENY + + + ${log_dir}/debug/%d{yyyy-MM-dd}/debug-log.log + ${maxHistory} + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + TRACE + ACCEPT + DENY + + + ${log_dir}/trace/%d{yyyy-MM-dd}/trace-log.log + ${maxHistory} + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + + + + + + + + + \ No newline at end of file