diff --git a/.gitignore b/.gitignore index 19f99f1..5e84b93 100644 --- a/.gitignore +++ b/.gitignore @@ -90,7 +90,8 @@ lint/tmp/ -/.idea/compiler.xml -/.idea/encodings.xml -/.idea/jarRepositories.xml -/.idea/misc.xml +/.idea/ +/target/ +/*.mpk +/*.txt +/*.pem diff --git a/README.md b/README.md index 9df6b1c..31f5aad 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ GmSSL-Java是采用JNI (Java Native Interface)方式实现的,也就是说所 GmSSL的项目组成主要包括C语言的本地代码、`src`目录下的Java类库代码、`examples`目录下面的例子代码。其中只有本地代码和`src`下面的Java类库代码会参与默认的编译,生成动态库和Jar包,而`examples`下的例子默认不编译也不进入Jar包。 -GmSSL-Java提供一个包`org.gmssl`,其中包含如下密码算法类 +GmSSL-Java提供两种实现,基于JCE的实现和基于java本身的基础实现。 +JCE实现内容在包`org.gmssl.crypto`,可按照JCE调用方式完成各种算法功能,JCE调用可参考项目test目录下的JceTest类。因cipher属于Oracle java的受限“服务”,因此JCE调用前提必须使用[openJDK](https://jdk.java.net/archive/)。 +基础实现内容在包`org.gmssl`,JDK来源不限制,其中包含如下密码算法类 * org.gmssl.Random * org.gmssl.Sm3 @@ -57,7 +59,7 @@ GmSSL-Java提供一个包`org.gmssl`,其中包含如下密码算法类 ## 编译和安装 ### 编译安装GmSSL -GmSSL-Java依赖GmSSL项目,在编译前需要先在系统上编译、安装并测试通过GmSSL库及工具。请在https://github.com/guanzhi/GmSSL 项目上下载最新发布的GmSSL代码,并完成编译、测试和安装。 +GmSSL-Java依赖GmSSL项目,在编译前需要先在系统上编译、安装并测试通过GmSSL库及工具。请在https://github.com/guanzhi/GmSSL 项目上下载同一版本的GmSSL代码,并完成编译、测试和安装。 ### 通过Maven编译安装GmSSL-java @@ -84,7 +86,7 @@ mvn clean install 最终会执行单元测试并在target目录下生成相应版本jar包。 ## 使用 -在其他项目中使用GmSSL-java,只需在pom.xml中添加如下依赖: +以上步骤操作完成后会在本地Maven仓库生成项目相应jar包,在其他项目中使用GmSSL-java,只需在pom.xml中添加如下依赖: ```xml com.gmssl diff --git a/src/main/java/org/gmssl/NativeLoader.java b/src/main/java/org/gmssl/NativeLoader.java index dc119b8..8ac47ec 100644 --- a/src/main/java/org/gmssl/NativeLoader.java +++ b/src/main/java/org/gmssl/NativeLoader.java @@ -12,6 +12,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -26,7 +28,9 @@ public class NativeLoader { /* custom jni library prefix path relative to project resources */ private static final String RESOURCELIB_PREFIXPATH = "lib"; - static final String GMSSLJNILIB_NAME="libgmssljni"; + static final String GMSSLJNILIB_NAME = "libgmssljni"; + + private static final Map loadedLibraries = new HashMap<>(); private static final Properties PROPERTIES = new Properties(); @@ -44,64 +48,80 @@ public class NativeLoader { /** * load jni lib from resources path,the parameter does not contain the path and suffix. - * @param libaray libarayName * + * @param library libraryName */ - public synchronized static void load (String libaray){ - String resourceLibPath = RESOURCELIB_PREFIXPATH + "/" + libaray + "." + libExtension(); + public static void load(String library) { + if (loadedLibraries.containsKey(library)) { + return; + } + Path tempFile = null; + String resourceLibPath = RESOURCELIB_PREFIXPATH + "/" + library + "." + libExtension(); try (InputStream inputStream = NativeLoader.class.getClassLoader().getResourceAsStream(resourceLibPath)) { - if (null == inputStream) { - throw new GmSSLException("lib file not found in JAR: " + resourceLibPath); - } - Path tempFile = Files.createTempFile(libaray, "."+libExtension()); + tempFile = Files.createTempFile(library, "." + libExtension()); tempFile.toFile().deleteOnExit(); Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING); checkReferencedLib(); System.load(tempFile.toAbsolutePath().toString()); + loadedLibraries.put(library, tempFile); + }catch (IOException e){ + throw new GmSSLException("lib file not found:"+ e.getMessage()); + }catch (UnsatisfiedLinkError e){ + throw new GmSSLException("Failed to load native library:"+ e.getMessage()); } catch (Exception e) { - throw new GmSSLException("Unable to load lib from JAR"); + throw new GmSSLException("Unable to load lib!"); + }finally { + if (null != tempFile) { + tempFile.toFile().delete(); + } } } /** * Get the operating system type. + * * @return operating system name */ - static String osType(){ - String os="unknown"; - String osName = System.getProperty("os.name").toLowerCase(); - if(osName.startsWith("windows")){ - os="win"; - } - if(osName.startsWith("linux")){ - if ("dalvik".equalsIgnoreCase(System.getProperty("java.vm.name"))) { - os = "android"; - System.setProperty("jna.nounpack", "true"); - } else { - os="linux"; - } + static String osType() { + String os = "unknown"; + String vmName = System.getProperty("java.vm.name"); + if ("dalvik".equalsIgnoreCase(vmName) || "art".equalsIgnoreCase(vmName)) { + os = "android"; } - if(osName.startsWith("mac os x") || osName.startsWith("darwin")){ - os="osx"; + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.startsWith("windows")) { + os = "win"; + } else if (osName.startsWith("linux")) { + os = "linux"; + } else if (osName.startsWith("mac os x") || osName.startsWith("darwin")) { + os = "osx"; + } else { + System.err.println("Unsupported OS: " + osName); } return os; } /** * Get the library extension name based on the operating system type. + * * @return extension name */ - static String libExtension(){ - String osType=osType(); - String libExtension=null; - if("win".equals(osType)){ - libExtension="dll"; - } - if("osx".equals(osType)){ - libExtension="dylib"; - } - if("linux".equals(osType)){ - libExtension="so"; + static String libExtension() { + String osType = osType(); + String libExtension = null; + switch (osType) { + case "win": + libExtension = "dll"; + break; + case "osx": + libExtension = "dylib"; + break; + case "linux": + case "android": + libExtension = "so"; + break; + default: + throw new IllegalArgumentException("Unsupported OS type!"); } return libExtension; } @@ -112,20 +132,21 @@ static String libExtension(){ * in order to correct the @rpath path issue. Alternatively, you can manually execute the command * "install_name_tool -change @rpath/libgmssl.3.dylib /usr/local/lib/libgmssl.3.dylib xxx/lib/libgmssljni.dylib" to fix the library reference path issue. * This has already been loaded and manual execution is unnecessary. - * */ - private static void checkReferencedLib(){ - if("osx".equals(osType())){ - String macReferencedLib=PROPERTIES.getProperty("macReferencedLib"); - Optional optionalStr = Optional.ofNullable(macReferencedLib); - if(optionalStr.isPresent() && !optionalStr.get().isEmpty()){ - File libFile = new File(macReferencedLib); - if(libFile.exists()){ - System.load(macReferencedLib); - } - } - - } - } + private static void checkReferencedLib() { + if ("osx".equals(osType())) { + String macReferencedLib = PROPERTIES.getProperty("macReferencedLib"); + if (null != macReferencedLib) { + System.load(macReferencedLib); + Optional optionalStr = Optional.ofNullable(macReferencedLib); + if (optionalStr.isPresent() && !optionalStr.get().isEmpty()) { + File libFile = new File(macReferencedLib); + if (libFile.exists()) { + System.load(macReferencedLib); + } + } + } + } + } } diff --git a/src/main/java/org/gmssl/Sm4.java b/src/main/java/org/gmssl/Sm4.java index d1b593c..bf9de4f 100644 --- a/src/main/java/org/gmssl/Sm4.java +++ b/src/main/java/org/gmssl/Sm4.java @@ -41,7 +41,6 @@ public Sm4(byte[] key, boolean do_encrypt) { } public void encrypt(byte[] in, int in_offset, byte[] out, int out_offset) { - if (in == null || in_offset < 0 || in_offset + this.BLOCK_SIZE <= 0 diff --git a/src/main/java/org/gmssl/crypto/CipherPaddingEnum.java b/src/main/java/org/gmssl/crypto/CipherPaddingEnum.java new file mode 100644 index 0000000..a11b709 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/CipherPaddingEnum.java @@ -0,0 +1,23 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public enum CipherPaddingEnum { + NoPadding, + ZeroPadding, + PKCS5Padding, + PKCS7Padding; +} diff --git a/src/main/java/org/gmssl/crypto/GmSSLProvider.java b/src/main/java/org/gmssl/crypto/GmSSLProvider.java new file mode 100644 index 0000000..60d1e31 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/GmSSLProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto; + +import java.security.Provider; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * GmSSL-Java currently provides functionality for random number generation, SM3 hash, SM3 message authentication code (HMAC-SM3), + * SM4 encryption (including block encryption and CBC/CTR/GCM encryption modes), ZUC encryption, SM2 encryption/signature, SM9 encryption/signature, and SM2 certificate parsing. + * These features cover the main application development scenarios for the current Chinese cryptographic algorithms. + */ +public class GmSSLProvider extends Provider { + + public GmSSLProvider() { + super("GmSSL", "3.1.1", "GmSSL Provider"); + + put("SecureRandom.Random", "org.gmssl.crypto.Random"); + put("Cipher.SM2", "org.gmssl.crypto.asymmetric.SM2Cipher"); + put("KeyPairGenerator.SM2", "org.gmssl.crypto.asymmetric.SM2KeyPairGenerator"); + put("Signature.SM2", "org.gmssl.crypto.asymmetric.SM2Signature"); + put("MessageDigest.SM3", "org.gmssl.crypto.digest.SM3Digest"); + put("Mac.SM3", "org.gmssl.crypto.digest.SM3Hmac"); + put("SecretKeyFactory.SM3Pbkdf2", "org.gmssl.crypto.digest.SM3Pbkdf2"); + put("Cipher.SM4", "org.gmssl.crypto.symmetric.SM4Cipher"); + put("Cipher.SM9", "org.gmssl.crypto.asymmetric.SM9Cipher"); + put("Signature.SM9", "org.gmssl.crypto.asymmetric.SM9Signature"); + put("KeyPairGenerator.SM9", "org.gmssl.crypto.asymmetric.SM9KeyPairGeneratorSpi"); + put("Cipher.ZUC", "org.gmssl.crypto.symmetric.ZucCipher"); + } + + +} diff --git a/src/main/java/org/gmssl/crypto/PKCS7PaddingScheme.java b/src/main/java/org/gmssl/crypto/PKCS7PaddingScheme.java new file mode 100644 index 0000000..79e01a5 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/PKCS7PaddingScheme.java @@ -0,0 +1,51 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto; + +import java.util.Arrays; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description PKCS#7 + * + */ +public class PKCS7PaddingScheme implements PaddingScheme{ + @Override + public String getPaddingName() { + return "PKCS#7"; + } + + @Override + public byte[] pad(byte[] input, int blockSize) { + int paddingLength = blockSize - (input.length % blockSize); + byte[] padding = new byte[paddingLength]; + Arrays.fill(padding, (byte) paddingLength); + byte[] result = new byte[input.length + padding.length]; + System.arraycopy(input, 0, result, 0, input.length); + System.arraycopy(padding, 0, result, input.length, padding.length); + return result; + } + + @Override + public byte[] unpad(byte[] input) { + int paddingSize = input[input.length - 1]; + if (paddingSize <= 0 || paddingSize > input.length) { + throw new IllegalArgumentException("Invalid pkcs#7 padding!"); + } + for (int i = input.length - paddingSize; i < input.length; i++) { + if (input[i] != paddingSize) { + throw new IllegalArgumentException("Invalid pkcs#7 padding!"); + } + } + return Arrays.copyOfRange(input, 0, input.length - paddingSize); + } + +} diff --git a/src/main/java/org/gmssl/crypto/PaddingScheme.java b/src/main/java/org/gmssl/crypto/PaddingScheme.java new file mode 100644 index 0000000..1c18535 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/PaddingScheme.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public interface PaddingScheme { + + /** + * get padding name + * @return paddingName + */ + String getPaddingName(); + + /** + * Pad according to fixed block size + * @param input Data to be padded + * @param blockSize block size + * @return padded data + */ + byte[] pad(byte[] input, int blockSize); + + /** + * Unpad according to fixed block size + * @param input Data to be unpadded + * @return unpadded data + */ + byte[] unpad(byte[] input); +} diff --git a/src/main/java/org/gmssl/crypto/Random.java b/src/main/java/org/gmssl/crypto/Random.java new file mode 100644 index 0000000..a19079a --- /dev/null +++ b/src/main/java/org/gmssl/crypto/Random.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import java.security.SecureRandomSpi; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public class Random extends SecureRandomSpi { + + public Random() { + super(); + } + + @Override + protected void engineSetSeed(byte[] seed) { + if (seed == null || seed.length == 0) { + throw new IllegalArgumentException("Seed cannot be null or empty"); + } + //rand_seed + throw new GmSSLException("The current method is not supported."); + } + + @Override + protected void engineNextBytes(byte[] bytes) { + if (bytes == null) { + throw new IllegalArgumentException("Output buffer cannot be null"); + } + randBytes(bytes,0, bytes.length); + } + + @Override + protected byte[] engineGenerateSeed(int numBytes) { + if (numBytes <= 0) { + throw new IllegalArgumentException("Number of bytes must be positive"); + } + return randBytes(numBytes); + } + + public byte[] randBytes(int len) { + byte[] out = new byte[len]; + if (GmSSLJNI.rand_bytes(out, 0, len) != 1) { + throw new GmSSLException("Failed to generate seed"); + } + return out; + } + + public void randBytes(byte[] out, int offset, int len) { + if (out == null + || offset < 0 + || len < 0 + || offset + len <= 0 + || out.length < offset + len) { + throw new GmSSLException(""); + } + if (GmSSLJNI.rand_bytes(out, offset, len) != 1) { + throw new GmSSLException(""); + } + } +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM2Certificate.java b/src/main/java/org/gmssl/crypto/asymmetric/SM2Certificate.java new file mode 100644 index 0000000..a4cab9f --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM2Certificate.java @@ -0,0 +1,127 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import java.security.*; +import java.security.cert.*; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * The certificate format is the standard X.509v3 certificate. Currently, only the SM2 signature algorithm is supported. + * This includes functions for parsing and verifying SM2 certificates. However, issuing and generating SM2 certificates are not supported. + * If the application needs to implement certificate request (i.e., generating CSR files) or self-built CA certificate issuance, + * these functionalities can be achieved using the GmSSL library or the gmssl command-line tool. + */ +public class SM2Certificate{ + + private byte[] cert; + + public byte[] getEncoded() throws CertificateEncodingException { + if (this.cert == null) { + throw new GmSSLException(""); + } + return this.cert; + } + + public String toString() { + return null; + } + + public PublicKey getPublicKey() { + if (this.cert == null) { + throw new GmSSLException(""); + } + long pub_key; + if ((pub_key = GmSSLJNI.cert_get_subject_public_key(this.cert)) == 0) { + throw new GmSSLException(""); + } + boolean has_private_key = false; + return new SM2PublicKey(pub_key, has_private_key); + } + + public void importPem(String file) { + if ((this.cert = GmSSLJNI.cert_from_pem(file)) == null) { + throw new GmSSLException(""); + } + } + + public void exportPem(String file) { + if (this.cert == null) { + throw new GmSSLException(""); + } + if (GmSSLJNI.cert_to_pem(this.cert, file) != 1) { + throw new GmSSLException(""); + } + } + + public boolean verifyByCaCertificate(SM2Certificate caCert, String sm2Id) throws CertificateEncodingException { + if (this.cert == null) { + throw new GmSSLException(""); + } + int ret = GmSSLJNI.cert_verify_by_ca_cert(this.cert, caCert.getEncoded(), sm2Id); + if (ret == 1) { + return true; + } else { + return false; + } + } + + public byte[] getSerialNumber() { + if (this.cert == null) { + throw new GmSSLException(""); + } + byte[] serial; + if ((serial = GmSSLJNI.cert_get_serial_number(this.cert)) == null) { + throw new GmSSLException(""); + } + return serial; + } + + public java.util.Date getNotBefore() { + if (this.cert == null) { + throw new GmSSLException(""); + } + return new java.util.Date(GmSSLJNI.cert_get_not_before(this.cert)); + } + + public java.util.Date getNotAfter() { + if (this.cert == null) { + throw new GmSSLException(""); + } + return new java.util.Date(GmSSLJNI.cert_get_not_after(this.cert)); + } + + public String[] getIssuer() { + if (this.cert == null) { + throw new GmSSLException(""); + } + String[] issuer; + if ((issuer = GmSSLJNI.cert_get_issuer(this.cert)) == null) { + throw new GmSSLException(""); + } + return issuer; + } + + public String[] getSubject() { + if (this.cert == null) { + throw new GmSSLException(""); + } + String[] subject; + if ((subject = GmSSLJNI.cert_get_subject(this.cert)) == null) { + throw new GmSSLException(""); + } + return subject; + } +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM2Cipher.java b/src/main/java/org/gmssl/crypto/asymmetric/SM2Cipher.java new file mode 100644 index 0000000..2abc8c0 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM2Cipher.java @@ -0,0 +1,201 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import javax.crypto.*; +import java.nio.ByteBuffer; +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * The SM2Cipher class implements encryption and decryption methods. When calling the encrypt method, ensure that the length of the plaintext input does not exceed the MAX_PLAINTEXT_SIZE limit. + * If you need to encrypt a message at the reference layer, first generate a symmetric key, encrypt the message using SM4-GCM, and then encrypt the symmetric key using SM2. + */ +public class SM2Cipher extends CipherSpi { + + private int mode; + private SM2Key key; + private SecureRandom random; + private ByteBuffer buffer; + + /** + * SM2 uses the C1C2C3 encryption mode. + * @param mode the cipher mode + * + * @throws NoSuchAlgorithmException + */ + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + + } + + /** + * SM2 has adopted the corresponding padding rule and does not involve specific padding modes. + * @param padding the padding mechanism + * + * @throws NoSuchPaddingException + */ + @Override + protected void engineSetPadding(String padding) throws NoSuchPaddingException { + + } + + /** + * SM2 does not have a fixed block size. + * @return + */ + @Override + protected int engineGetBlockSize() { + return 0; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return 0; + } + + @Override + protected byte[] engineGetIV() { + return null; + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected void engineInit(int mode, Key key, SecureRandom secureRandom) throws InvalidKeyException { + if (!(key instanceof SM2Key)) { + throw new InvalidKeyException("Invalid key type"); + } + this.key = (SM2Key)key; + this.mode = mode; + this.random = (secureRandom != null) ? secureRandom : new SecureRandom(); + this.buffer = ByteBuffer.allocate(1024); + } + + @Override + protected void engineInit(int mode, Key key, AlgorithmParameterSpec algorithmParameterSpec, SecureRandom secureRandom) throws InvalidKeyException, InvalidAlgorithmParameterException { + engineInit(mode, key, random); + } + + @Override + protected void engineInit(int i, Key key, AlgorithmParameters algorithmParameters, SecureRandom secureRandom) throws InvalidKeyException, InvalidAlgorithmParameterException { + engineInit(mode, key, random); + } + + /** + * + * @param input the input buffer + * @param inputOffset the offset in input where the input + * starts + * @param inputLen the input length + * + * @return null + * The SM2 algorithm typically does not return any data during the engineUpdate phase. + */ + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + if (input == null || inputOffset < 0 || inputLen < 0 || inputOffset + inputLen > input.length) { + throw new IllegalArgumentException("Invalid input parameters"); + } + buffer.put(input, inputOffset, inputLen); + return null; + } + + /** + * The method does not perform any encryption or decryption; it only stores the input data. The returned result is meaningless, and the final result is output through `doFinal`. + * @param input the input buffer + * @param inputOffset the offset in input where the input + * starts + * @param inputLen the input length + * @param output the buffer for the result + * @param outputOffset the offset in output where the result + * is stored + * + * @return + * @throws ShortBufferException + */ + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException { + if (input == null || inputOffset < 0 || inputLen < 0 || inputOffset + inputLen > input.length) { + throw new IllegalArgumentException("Invalid input parameters"); + } + buffer.put(input, inputOffset, inputLen); + return 0; + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { + if(null != input){ + buffer.put(input, inputOffset, inputLen); + } + byte[] data = new byte[buffer.position()]; + buffer.flip(); + buffer.get(data); + buffer.clear(); + + if (mode == Cipher.ENCRYPT_MODE) { + return encrypt(data); + } else if (mode == Cipher.DECRYPT_MODE) { + return decrypt(data); + } else { + throw new GmSSLException("Cipher not initialized properly"); + } + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + byte[] result = engineDoFinal(input, inputOffset, inputLen); + System.arraycopy(result, 0, output, outputOffset, result.length); + return result.length; + } + + public byte[] encrypt(byte[] plaintext) { + if (this.key.sm2_key == 0) { + throw new GmSSLException(""); + } + if (plaintext == null + || plaintext.length > this.key.MAX_PLAINTEXT_SIZE) { + throw new GmSSLException(""); + } + + byte[] ciphertext; + if ((ciphertext = GmSSLJNI.sm2_encrypt(this.key.sm2_key, plaintext)) == null) { + throw new GmSSLException(""); + } + return ciphertext; + } + + public byte[] decrypt(byte[] ciphertext) { + if (this.key.sm2_key == 0) { + throw new GmSSLException(""); + } + if (this.key.has_private_key == false) { + throw new GmSSLException(""); + } + if (ciphertext == null) { + throw new GmSSLException(""); + } + + byte[] plaintext; + if ((plaintext = GmSSLJNI.sm2_decrypt(this.key.sm2_key, ciphertext)) == null) { + throw new GmSSLException(""); + } + return plaintext; + } +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM2Key.java b/src/main/java/org/gmssl/crypto/asymmetric/SM2Key.java new file mode 100644 index 0000000..ac006e2 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM2Key.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import java.security.Key; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description SM2Key + * + */ +public abstract class SM2Key implements Key { + + public final static int MAX_PLAINTEXT_SIZE = GmSSLJNI.SM2_MAX_PLAINTEXT_SIZE; + + protected long sm2_key; + protected boolean has_private_key; + + protected SM2Key() { + this.sm2_key = 0; + this.has_private_key = false; + } + + protected SM2Key(long sm2_key, boolean has_private_key) { + this.sm2_key = sm2_key; + this.has_private_key = has_private_key; + } + + @Override + public String getAlgorithm() { + return "SM2"; + } + + long getPrivateKey() { + if (this.sm2_key == 0) { + throw new GmSSLException(""); + } + if (this.has_private_key == false) { + throw new GmSSLException(""); + } + return this.sm2_key; + } + + long getPublicKey() { + if (this.sm2_key == 0) { + throw new GmSSLException(""); + } + return this.sm2_key; + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM2KeyPairGenerator.java b/src/main/java/org/gmssl/crypto/asymmetric/SM2KeyPairGenerator.java new file mode 100644 index 0000000..3fe591e --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM2KeyPairGenerator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import java.security.*; +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM2KeyPairGenerator extends KeyPairGeneratorSpi { + + private long sm2_key = 0; + private boolean has_private_key = false; + + @Override + public void initialize(int keysize, SecureRandom random) { + generateKey(); + } + + @Override + public KeyPair generateKeyPair() { + PublicKey publicKey = new SM2PublicKey(sm2_key, has_private_key); + PrivateKey privateKey = new SM2PrivateKey(sm2_key, has_private_key); + return new KeyPair(publicKey, privateKey); + } + + private void generateKey() { + if (this.sm2_key != 0) { + GmSSLJNI.sm2_key_free(this.sm2_key); + } + if ((sm2_key = GmSSLJNI.sm2_key_generate()) == 0) { + throw new GmSSLException(""); + } + this.has_private_key = true; + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM2PrivateKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM2PrivateKey.java new file mode 100644 index 0000000..bd5ac98 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM2PrivateKey.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import java.security.PrivateKey; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM2PrivateKey extends SM2Key implements PrivateKey{ + + protected SM2PrivateKey() { + super(); + } + + public SM2PrivateKey(byte[] der) { + importPrivateKeyInfoDer(der); + } + + public SM2PrivateKey(String password, String file) { + importEncryptedPrivateKeyInfoPem(password, file); + } + + protected SM2PrivateKey(long sm2_key, boolean has_private_key) { + super(sm2_key,has_private_key); + } + + public String getAlgorithm() { + return "SM2"; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return exportPrivateKeyInfoDer(); + } + + public void importPrivateKeyInfoDer(byte[] der) { + if (der == null) { + throw new GmSSLException(""); + } + if (this.sm2_key != 0) { + GmSSLJNI.sm2_key_free(this.sm2_key); + } + if ((this.sm2_key = GmSSLJNI.sm2_private_key_info_from_der(der)) == 0) { + throw new GmSSLException(""); + } + this.has_private_key = true; + } + + public byte[] exportPrivateKeyInfoDer() { + if (this.sm2_key == 0) { + throw new GmSSLException(""); + } + if (this.has_private_key == false) { + throw new GmSSLException(""); + } + byte[] der; + if ((der = GmSSLJNI.sm2_private_key_info_to_der(this.sm2_key)) == null) { + throw new GmSSLException(""); + } + return der; + } + + public void importEncryptedPrivateKeyInfoPem(String pass, String file) { + if (this.sm2_key != 0) { + GmSSLJNI.sm2_key_free(this.sm2_key); + } + if ((sm2_key = GmSSLJNI.sm2_private_key_info_decrypt_from_pem(pass, file)) == 0) { + throw new GmSSLException(""); + } + this.has_private_key = true; + } + + public void exportEncryptedPrivateKeyInfoPem(String pass, String file) { + if (this.sm2_key == 0) { + throw new GmSSLException(""); + } + if (this.has_private_key == false) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm2_private_key_info_encrypt_to_pem(this.sm2_key, pass, file) != 1) { + throw new GmSSLException(""); + } + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM2PublicKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM2PublicKey.java new file mode 100644 index 0000000..5d301f8 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM2PublicKey.java @@ -0,0 +1,111 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; +import org.gmssl.Sm3; + +import java.security.PublicKey; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM2PublicKey extends SM2Key implements PublicKey{ + + protected SM2PublicKey() { + super(); + } + + public SM2PublicKey(byte[] der) { + importPublicKeyInfoDer(der); + } + + public SM2PublicKey(String file) { + importPublicKeyInfoPem(file); + } + + protected SM2PublicKey(long sm2_key, boolean has_private_key) { + super(sm2_key,has_private_key); + } + + @Override + public String getAlgorithm() { + return "SM2"; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return exportPublicKeyInfoDer(); + } + + public void importPublicKeyInfoDer(byte[] der) { + if (der == null) { + throw new GmSSLException(""); + } + if (this.sm2_key != 0) { + GmSSLJNI.sm2_key_free(this.sm2_key); + } + if ((this.sm2_key = GmSSLJNI.sm2_public_key_info_from_der(der)) == 0) { + throw new GmSSLException(""); + } + this.has_private_key = false; + } + + public byte[] exportPublicKeyInfoDer() { + if (this.sm2_key == 0) { + throw new GmSSLException(""); + } + byte[] der; + if ((der = GmSSLJNI.sm2_public_key_info_to_der(this.sm2_key)) == null) { + throw new GmSSLException(""); + } + return der; + } + + public void importPublicKeyInfoPem(String file) { + if (this.sm2_key != 0) { + GmSSLJNI.sm2_key_free(this.sm2_key); + } + if ((this.sm2_key = GmSSLJNI.sm2_public_key_info_from_pem(file)) == 0) { + throw new GmSSLException(""); + } + this.has_private_key = false; + } + + public void exportPublicKeyInfoPem(String file) { + if (this.sm2_key == 0) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm2_public_key_info_to_pem(this.sm2_key, file) != 1) { + throw new GmSSLException(""); + } + } + + public byte[] computeZ(String id) { + if (this.sm2_key == 0) { + throw new GmSSLException(""); + } + byte[] z = new byte[Sm3.DIGEST_SIZE]; + if (GmSSLJNI.sm2_compute_z(this.sm2_key, id, z) != 1) { + throw new GmSSLException(""); + } + return z; + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM2Signature.java b/src/main/java/org/gmssl/crypto/asymmetric/SM2Signature.java new file mode 100644 index 0000000..3d6bd8b --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM2Signature.java @@ -0,0 +1,162 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import java.security.*; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description SM2Signature + * It provides signing and verification functionality for messages of arbitrary length. + */ +public class SM2Signature extends SignatureSpi { + + public final static String DEFAULT_ID = GmSSLJNI.SM2_DEFAULT_ID; + + private long sm2_sign_ctx = 0; + private boolean inited = false; + + private boolean do_sign = true; + + public SM2Signature() { + super(); + } + + @Override + protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { + if (!(publicKey instanceof SM2PublicKey)) { + throw new GmSSLException("Invalid publicKey type"); + } + init(); + initVerify((SM2PublicKey) publicKey,DEFAULT_ID); + } + + @Override + protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { + if (!(privateKey instanceof SM2PrivateKey)) { + throw new GmSSLException("Invalid privateKey type"); + } + init(); + initSign((SM2PrivateKey) privateKey,DEFAULT_ID); + } + + @Override + protected void engineUpdate(byte b) throws SignatureException { + byte[] data= new byte[]{b}; + update(data, 0, data.length); + } + + @Override + protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { + update(b, off, len); + } + + @Override + protected byte[] engineSign() throws SignatureException { + byte[] data = sign(); + return data; + } + + @Override + protected boolean engineVerify(byte[] sigBytes) throws SignatureException { + boolean verifyResult= verify(sigBytes); + return verifyResult; + } + + @Override + protected void engineSetParameter(String param, Object value) throws InvalidParameterException { + } + + @Override + protected Object engineGetParameter(String param) throws InvalidParameterException { + return null; + } + + private void init(){ + if ((this.sm2_sign_ctx = GmSSLJNI.sm2_sign_ctx_new()) == 0) { + throw new GmSSLException(""); + } + this.inited = true; + } + + private void initSign(SM2PrivateKey privateKey,String id){ + if (GmSSLJNI.sm2_sign_init(this.sm2_sign_ctx, privateKey.getPrivateKey(), id) != 1) { + throw new GmSSLException(""); + } + this.do_sign = true; + } + + private void initVerify(SM2PublicKey publicKey,String id){ + if (GmSSLJNI.sm2_verify_init(sm2_sign_ctx, publicKey.getPublicKey(), id) != 1) { + throw new GmSSLException(""); + } + this.do_sign = false; + } + + private void update(byte[] data, int offset, int len) { + if (this.inited == false) { + throw new GmSSLException(""); + } + + if (data == null + || offset < 0 + || len < 0 + || offset + len <= 0 + || data.length < offset + len) { + throw new GmSSLException(""); + } + + if (this.do_sign == true) { + if (GmSSLJNI.sm2_sign_update(this.sm2_sign_ctx, data, offset, len) != 1) { + throw new GmSSLException(""); + } + } else { + if (GmSSLJNI.sm2_verify_update(this.sm2_sign_ctx, data, offset, len) != 1) { + throw new GmSSLException(""); + } + } + } + + private byte[] sign() { + if (this.inited == false) { + throw new GmSSLException(""); + } + if (this.do_sign == false) { + throw new GmSSLException(""); + } + + byte[] sig; + if ((sig = GmSSLJNI.sm2_sign_finish(this.sm2_sign_ctx)) == null) { + throw new GmSSLException(""); + } + this.inited = false; + return sig; + } + + private boolean verify(byte[] signature) { + if (this.sm2_sign_ctx == 0) { + throw new GmSSLException(""); + } + if (this.do_sign == true) { + throw new GmSSLException(""); + } + this.inited = false; + int ret; + if ((ret = GmSSLJNI.sm2_verify_finish(sm2_sign_ctx, signature)) != 1) { + return false; + } + return true; + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9Cipher.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9Cipher.java new file mode 100644 index 0000000..5f1564c --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9Cipher.java @@ -0,0 +1,180 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; + +import javax.crypto.*; +import java.nio.ByteBuffer; +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description SM9Cipher + * The SM9 algorithm belongs to an identity-based encryption (IBE) system. + * Since IBE does not require a Certificate Authority (CA) or a digital certificate infrastructure, + * if the application operates in a closed internal environment where all participating users are within the system, adopting the SM9 solution is a better choice. + */ +public class SM9Cipher extends CipherSpi { + + private int opmode; + + private Key key; + + private ByteBuffer buffer; + + private String id; + + /** + * + * @param mode the cipher mode + * @throws NoSuchAlgorithmException + * @description + * SM9 is an identity-based encryption and signature algorithm that does not support traditional block cipher modes. + */ + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + } + + /** + * + * @param padding the padding mechanism + * @throws NoSuchPaddingException + * @description + * SM9 is an identity-based encryption and signature algorithm that does not support common padding modes. + */ + @Override + protected void engineSetPadding(String padding) throws NoSuchPaddingException { + } + + /** + * SM9 is a public key encryption algorithm that does not have a fixed block size and does not use blocks. + * @return + */ + @Override + protected int engineGetBlockSize() { + return 0; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return 0; + } + + @Override + protected byte[] engineGetIV() { + return new byte[0]; + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + if (!(key instanceof SM9PrivateKey)) { + throw new GmSSLException("Invalid privateKey type"); + } + this.opmode = opmode; + this.key = key; + SM9PrivateKey privateKey = (SM9PrivateKey)key; + SM9UserKey userKey = (SM9UserKey)privateKey.getSecretKey(); + this.id = userKey.getId(); + this.buffer = ByteBuffer.allocate(1024); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + if (!(key instanceof SM9PublicKey)) { + throw new GmSSLException("Invalid publicKey type"); + } + this.opmode = opmode; + this.key = key; + this.id = ((SM9EncMasterKeyGenParameterSpec)params).getId(); + this.buffer = ByteBuffer.allocate(1024); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + } + + /** + * SM9 encryption and decryption are completed during the engineDoFinal phase. During the update phase, data is only cached, and no partial encryption or decryption results are returned. + * @param input the input buffer + * @param inputOffset the offset in input where the input + * starts + * @param inputLen the input length + * + * @return + */ + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + buffer.put(input, inputOffset, inputLen); + return null; + } + + /** + * SM9 encryption and decryption are completed during the engineDoFinal phase. During the update phase, data is only cached, and no partial encryption or decryption results are returned. + * @param input the input buffer + * @param inputOffset the offset in input where the input + * starts + * @param inputLen the input length + * @param output the buffer for the result + * @param outputOffset the offset in output where the result + * is stored + * + * @return + * @throws ShortBufferException + */ + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException { + buffer.put(input, inputOffset, inputLen); + return 0; + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { + buffer.put(input, inputOffset, inputLen); + byte[] data = new byte[buffer.position()]; + buffer.flip(); + buffer.get(data); + buffer.clear(); + + if (opmode == Cipher.ENCRYPT_MODE) { + return encrypt(data); + } else if (opmode == Cipher.DECRYPT_MODE) { + return decrypt(data); + } else { + throw new GmSSLException("Cipher not initialized properly"); + } + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + byte[] result = engineDoFinal(input, inputOffset, inputLen); + System.arraycopy(result, 0, output, outputOffset, result.length); + return result.length; + } + + private byte[] encrypt(byte[] plaintext) { + SM9PublicKey encMasterKey = (SM9PublicKey) key; + byte[] ciphertext = encMasterKey.encrypt(plaintext,id); + return ciphertext; + } + + private byte[] decrypt(byte[] ciphertext) { + SM9PrivateKey privateKey = (SM9PrivateKey)key; + byte[] plaintext = privateKey.decrypt(ciphertext); + return plaintext; + } +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9EncMasterKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9EncMasterKey.java new file mode 100644 index 0000000..f549cda --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9EncMasterKey.java @@ -0,0 +1,139 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM9EncMasterKey extends SM9MasterKey{ + + public final static int MAX_PLAINTEXT_SIZE = GmSSLJNI.SM9_MAX_PLAINTEXT_SIZE; + + public SM9EncMasterKey(){ + publicKey = new SM9EncPublicKey(); + privateKey = new SM9EncPrivateKey(); + } + + class SM9EncPublicKey extends SM9PublicKey { + + public long getPublicKey() { + if (master_key == 0) { + throw new GmSSLException(""); + } + return master_key; + } + + public void importPublicKeyPem(String file) { + if (master_key != 0) { + GmSSLJNI.sm9_enc_master_key_free(master_key); + } + if ((master_key = GmSSLJNI.sm9_enc_master_public_key_from_pem(file)) == 0) { + throw new GmSSLException(""); + } + has_private_key = false; + } + + public void exportPublicKeyPem(String file) { + if (master_key == 0) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm9_enc_master_public_key_to_pem(master_key, file) != 1) { + throw new GmSSLException(""); + } + } + + public byte[] encrypt(byte[] plaintext, String id) { + if (master_key == 0) { + throw new GmSSLException(""); + } + if (plaintext == null + || plaintext.length > MAX_PLAINTEXT_SIZE) { + throw new GmSSLException(""); + } + + byte[] ciphertext; + if ((ciphertext = GmSSLJNI.sm9_encrypt(master_key, id, plaintext)) == null) { + throw new GmSSLException(""); + } + return ciphertext; + } + + } + + class SM9EncPrivateKey extends SM9PrivateKey{ + public void importEncryptedPrivateKeyInfoPem(String pass, String file) { + if (master_key != 0) { + GmSSLJNI.sm9_enc_master_key_free(master_key); + } + if ((master_key = GmSSLJNI.sm9_enc_master_key_info_decrypt_from_pem(pass, file)) == 0) { + throw new GmSSLException(""); + } + has_private_key = true; + } + + public void exportEncryptedPrivateKeyInfoPem(String pass, String file) { + if (master_key == 0) { + throw new GmSSLException(""); + } + if (has_private_key == false) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm9_enc_master_key_info_encrypt_to_pem(master_key, pass, file) != 1) { + throw new GmSSLException(""); + } + } + + public SM9EncMasterKey getSecretKey() { + return SM9EncMasterKey.this; + } + + } + + public void generateMasterKey() { + if (master_key != 0) { + GmSSLJNI.sm9_enc_master_key_free(master_key); + } + if ((master_key = GmSSLJNI.sm9_enc_master_key_generate()) == 0) { + throw new GmSSLException(""); + } + has_private_key = true; + } + + public long getSecretKey() { + if (master_key == 0) { + throw new GmSSLException(""); + } + if (has_private_key == false) { + throw new GmSSLException(""); + } + return master_key; + } + + public SM9UserKey extractKey(String id) { + if (this.master_key == 0) { + throw new GmSSLException(""); + } + if (this.has_private_key == false) { + throw new GmSSLException(""); + } + long key; + if ((key = GmSSLJNI.sm9_enc_master_key_extract_key(this.master_key, id)) == 0) { + throw new GmSSLException(""); + } + return new SM9EncUserKey(key, id); + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9EncMasterKeyGenParameterSpec.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9EncMasterKeyGenParameterSpec.java new file mode 100644 index 0000000..da982ad --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9EncMasterKeyGenParameterSpec.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLJNI; + +import java.security.spec.AlgorithmParameterSpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM9EncMasterKeyGenParameterSpec implements AlgorithmParameterSpec { + + private String id; + + protected SM9EncMasterKeyGenParameterSpec() { + + } + + public SM9EncMasterKeyGenParameterSpec(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} + diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9EncUserKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9EncUserKey.java new file mode 100644 index 0000000..4a417da --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9EncUserKey.java @@ -0,0 +1,66 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; +import org.gmssl.Sm9SignKey; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM9EncUserKey extends SM9UserKey{ + protected SM9EncUserKey(long sm9_key, String id) { + super(sm9_key, id); + this.privateKey = new SM9EncPrivateKey(); + } + + class SM9EncPrivateKey extends SM9PrivateKey { + public void importEncryptedPrivateKeyInfoPem(String pass, String file) { + if (sm9_key != 0) { + GmSSLJNI.sm9_enc_key_free(sm9_key); + } + if ((sm9_key = GmSSLJNI.sm9_enc_key_info_decrypt_from_pem(pass, file)) == 0) { + throw new GmSSLException(""); + } + } + + public void exportEncryptedPrivateKeyInfoPem(String pass, String file) { + if (sm9_key == 0) { + throw new GmSSLException("Key not initialized"); + } + if (GmSSLJNI.sm9_enc_key_info_encrypt_to_pem(sm9_key, pass, file) != 1) { + throw new GmSSLException(""); + } + } + + public byte[] decrypt(byte[] ciphertext) { + if (sm9_key == 0) { + throw new GmSSLException(""); + } + if (ciphertext == null) { + throw new GmSSLException(""); + } + + byte[] plaintext; + if ((plaintext = GmSSLJNI.sm9_decrypt(sm9_key, id, ciphertext)) == null) { + throw new GmSSLException(""); + } + return plaintext; + } + public SM9EncUserKey getSecretKey() { + return SM9EncUserKey.this; + } + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9KeyPairGeneratorSpi.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9KeyPairGeneratorSpi.java new file mode 100644 index 0000000..a3962cc --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9KeyPairGeneratorSpi.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGeneratorSpi; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM9KeyPairGeneratorSpi extends KeyPairGeneratorSpi { + + private SM9MasterKey masterKey; + + @Override + public void initialize(int keysize, SecureRandom random) { + + } + + @Override + public void initialize(AlgorithmParameterSpec params, SecureRandom random) throws InvalidAlgorithmParameterException { + if (params == null) { + throw new InvalidAlgorithmParameterException("The parameter must not be null"); + } + if (params instanceof SM9SignMasterKeyGenParameterSpec) { + SM9SignMasterKeyGenParameterSpec spec = (SM9SignMasterKeyGenParameterSpec) params; + this.masterKey = new SM9SignMasterKey(); + } else if (params instanceof SM9EncMasterKeyGenParameterSpec) { + SM9EncMasterKeyGenParameterSpec spec = (SM9EncMasterKeyGenParameterSpec) params; + this.masterKey = new SM9EncMasterKey(); + } else { + throw new InvalidAlgorithmParameterException(""); + } + masterKey.generateMasterKey(); + } + + @Override + public KeyPair generateKeyPair() { + return new KeyPair(masterKey.publicKey, masterKey.privateKey); + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9MasterKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9MasterKey.java new file mode 100644 index 0000000..3ad7fe3 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9MasterKey.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import java.security.spec.KeySpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public abstract class SM9MasterKey implements KeySpec { + + protected long master_key; + + protected boolean has_private_key; + + protected SM9PublicKey publicKey; + + protected SM9PrivateKey privateKey; + + public abstract void generateMasterKey(); + + public abstract long getSecretKey(); + + public abstract SM9UserKey extractKey(String id); + + public SM9PublicKey getPublicKey() { + return this.publicKey; + } + + public SM9PrivateKey getPrivateKey() { + return this.privateKey; + } +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9PrivateKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9PrivateKey.java new file mode 100644 index 0000000..d34c9de --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9PrivateKey.java @@ -0,0 +1,51 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import java.security.PrivateKey; +import java.security.spec.KeySpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public abstract class SM9PrivateKey implements PrivateKey { + + @Override + public String getAlgorithm() { + return null; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return new byte[0]; + } + + public abstract KeySpec getSecretKey(); + + public abstract void importEncryptedPrivateKeyInfoPem(String pass, String file); + + public abstract void exportEncryptedPrivateKeyInfoPem(String pass, String file); + + public byte[] decrypt(byte[] ciphertext) { + return null; + } + + public byte[] sign(long sign_ctx) { + return null; + } +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9PublicKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9PublicKey.java new file mode 100644 index 0000000..474776c --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9PublicKey.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import java.security.PublicKey; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public abstract class SM9PublicKey implements PublicKey { + + @Override + public String getAlgorithm() { + return null; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return new byte[0]; + } + + public abstract long getPublicKey(); + + public abstract void importPublicKeyPem(String file); + + public abstract void exportPublicKeyPem(String file); + + public byte[] encrypt(byte[] plaintext, String id){ + return null; + }; + + public Boolean verify(byte[] signature, String id,long sign_ctx){ + return null; + } +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9SignMasterKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9SignMasterKey.java new file mode 100644 index 0000000..6b4dbe7 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9SignMasterKey.java @@ -0,0 +1,129 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM9SignMasterKey extends SM9MasterKey{ + + public SM9SignMasterKey(){ + publicKey = new SM9SignPublicKey(); + privateKey = new SM9SignPrivateKey(); + } + + class SM9SignPublicKey extends SM9PublicKey { + public long getPublicKey() { + if (master_key == 0) { + throw new GmSSLException(""); + } + return master_key; + } + + public void importPublicKeyPem(String file) { + if (master_key != 0) { + GmSSLJNI.sm9_sign_master_key_free(master_key); + } + if ((master_key = GmSSLJNI.sm9_sign_master_public_key_from_pem(file)) == 0) { + throw new GmSSLException(""); + } + has_private_key = false; + } + + public void exportPublicKeyPem(String file) { + if (master_key == 0) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm9_sign_master_public_key_to_pem(master_key, file) != 1) { + throw new GmSSLException(""); + } + } + + public Boolean verify(byte[] signature, String id,long sm9_sign_ctx) { + int ret; + ret = GmSSLJNI.sm9_verify_finish(sm9_sign_ctx, signature, master_key, id); + if (ret == 1) { + return true; + } else { + return false; + } + } + + } + + class SM9SignPrivateKey extends SM9PrivateKey{ + public void importEncryptedPrivateKeyInfoPem(String pass, String file) { + if (master_key != 0) { + GmSSLJNI.sm9_sign_master_key_free(master_key); + } + if ((master_key = GmSSLJNI.sm9_sign_master_key_info_decrypt_from_pem(pass, file)) == 0) { + throw new GmSSLException(""); + } + has_private_key = true; + } + + public void exportEncryptedPrivateKeyInfoPem(String pass, String file) { + if (master_key == 0) { + throw new GmSSLException(""); + } + if (has_private_key == false) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm9_sign_master_key_info_encrypt_to_pem(master_key, pass, file) != 1) { + throw new GmSSLException(""); + } + } + + public SM9SignMasterKey getSecretKey() { + return SM9SignMasterKey.this; + } + + } + public void generateMasterKey() { + if (this.master_key != 0) { + GmSSLJNI.sm9_sign_master_key_free(this.master_key); + } + if ((this.master_key = GmSSLJNI.sm9_sign_master_key_generate()) == 0) { + throw new GmSSLException(""); + } + this.has_private_key = true; + } + + public long getSecretKey() { + if (this.master_key == 0) { + throw new GmSSLException(""); + } + if (this.has_private_key == false) { + throw new GmSSLException(""); + } + return this.master_key; + } + + public SM9UserKey extractKey(String id) { + if (this.master_key == 0) { + throw new GmSSLException(""); + } + if (this.has_private_key == false) { + throw new GmSSLException(""); + } + long key; + if ((key = GmSSLJNI.sm9_sign_master_key_extract_key(this.master_key, id)) == 0) { + throw new GmSSLException(""); + } + return new SM9SignUserKey(key, id); + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9SignMasterKeyGenParameterSpec.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9SignMasterKeyGenParameterSpec.java new file mode 100644 index 0000000..51749c8 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9SignMasterKeyGenParameterSpec.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import java.security.spec.AlgorithmParameterSpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM9SignMasterKeyGenParameterSpec implements AlgorithmParameterSpec { + + private String id; + + protected SM9SignMasterKeyGenParameterSpec() { + + } + + public SM9SignMasterKeyGenParameterSpec(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9SignUserKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9SignUserKey.java new file mode 100644 index 0000000..815d338 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9SignUserKey.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; +import org.gmssl.Sm9SignKey; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM9SignUserKey extends SM9UserKey{ + protected SM9SignUserKey(long sm9_key, String id) { + super(sm9_key, id); + this.privateKey = new SM9SignPrivateKey(); + } + + class SM9SignPrivateKey extends SM9PrivateKey { + + public void exportEncryptedPrivateKeyInfoPem(String pass, String file) { + if (sm9_key == 0) { + throw new GmSSLException("Key not initialized"); + } + if (GmSSLJNI.sm9_sign_key_info_encrypt_to_pem(sm9_key, pass, file) != 1) { + throw new GmSSLException(""); + } + } + + public void importEncryptedPrivateKeyInfoPem(String pass, String file) { + if (sm9_key != 0) { + GmSSLJNI.sm9_sign_key_free(sm9_key); + } + if ((sm9_key = GmSSLJNI.sm9_sign_key_info_decrypt_from_pem(pass, file)) == 0) { + throw new GmSSLException("Import key failure"); + } + } + + public byte[] sign(long sm9_sign_ctx) { + byte[] signature; + if ((signature = GmSSLJNI.sm9_sign_finish(sm9_sign_ctx, sm9_key)) == null) { + throw new GmSSLException(""); + } + return signature; + } + + public SM9SignUserKey getSecretKey() { + return SM9SignUserKey.this; + } + + } + +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9Signature.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9Signature.java new file mode 100644 index 0000000..704328e --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9Signature.java @@ -0,0 +1,160 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public class SM9Signature extends SignatureSpi { + + private long sm9_sign_ctx; + + private boolean inited; + + private boolean do_sign; + + private Key key; + + private String id; + + public SM9Signature() { + super(); + } + + @Override + protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { + if (!(publicKey instanceof SM9PublicKey)) { + throw new GmSSLException("Invalid publicKey type"); + } + this.key = publicKey; + init(); + initVerify(); + } + + @Override + protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { + if (!(privateKey instanceof SM9PrivateKey)) { + throw new GmSSLException("Invalid privateKey type"); + } + this.key = privateKey; + init(); + initSign(); + } + + @Override + protected void engineUpdate(byte b) throws SignatureException { + byte[] data= new byte[]{b}; + update(data, 0, data.length); + } + + @Override + protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { + update(b, off, len); + } + + @Override + protected byte[] engineSign() { + SM9PrivateKey sm9_private_key = (SM9PrivateKey)key; + byte[] signature = sm9_private_key.sign(sm9_sign_ctx); + this.inited = false; + return signature; + } + + @Override + protected boolean engineVerify(byte[] sigBytes) { + SM9PublicKey sm9_public_key = (SM9PublicKey)key; + boolean verify = sm9_public_key.verify(sigBytes,id,sm9_sign_ctx); + this.inited = false; + return verify; + } + + @Deprecated + @Override + protected void engineSetParameter(String param, Object value) throws InvalidParameterException { + + } + + @Deprecated + @Override + protected Object engineGetParameter(String param) throws InvalidParameterException { + return null; + } + + @Override + protected void engineSetParameter(AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException { + this.id = ((SM9SignMasterKeyGenParameterSpec)params).getId(); + } + + private void init(){ + if ((this.sm9_sign_ctx = GmSSLJNI.sm9_sign_ctx_new()) == 0) { + throw new GmSSLException(""); + } + this.inited = true; + } + + private void initSign() { + if (GmSSLJNI.sm9_sign_init(this.sm9_sign_ctx) != 1) { + throw new GmSSLException(""); + } + this.do_sign = true; + } + + private void initVerify() { + if (GmSSLJNI.sm9_verify_init(this.sm9_sign_ctx) != 1) { + throw new GmSSLException(""); + } + this.do_sign = false; + } + + public void reset(boolean do_sign) { + if (do_sign == true) { + if (GmSSLJNI.sm9_sign_init(this.sm9_sign_ctx) != 1) { + throw new GmSSLException(""); + } + } else { + if (GmSSLJNI.sm9_verify_init(this.sm9_sign_ctx) != 1) { + throw new GmSSLException(""); + } + } + this.inited = true; + this.do_sign = do_sign; + } + + public void update(byte[] data, int offset, int len) { + if (this.inited == false) { + throw new GmSSLException(""); + } + if (data == null + || offset < 0 + || len < 0 + || offset + len <= 0 + || data.length < offset + len) { + throw new GmSSLException(""); + } + if (this.do_sign == true) { + if (GmSSLJNI.sm9_sign_update(this.sm9_sign_ctx, data, offset, len) != 1) { + throw new GmSSLException(""); + } + } else { + if (GmSSLJNI.sm9_verify_update(this.sm9_sign_ctx, data, offset, len) != 1) { + throw new GmSSLException(""); + } + } + } +} diff --git a/src/main/java/org/gmssl/crypto/asymmetric/SM9UserKey.java b/src/main/java/org/gmssl/crypto/asymmetric/SM9UserKey.java new file mode 100644 index 0000000..7e219b5 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/asymmetric/SM9UserKey.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.asymmetric; + +import java.security.spec.KeySpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/08/11 + * @description + * + */ +public abstract class SM9UserKey implements KeySpec { + + protected long sm9_key; + + protected String id; + + protected SM9PrivateKey privateKey; + + protected SM9UserKey(long key, String id) { + this.sm9_key = key; + this.id = id; + } + + public String getId() { + return this.id; + } + + public SM9PrivateKey getPrivateKey() { + return this.privateKey; + } +} diff --git a/src/main/java/org/gmssl/crypto/digest/SM3Digest.java b/src/main/java/org/gmssl/crypto/digest/SM3Digest.java new file mode 100644 index 0000000..15273e4 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/digest/SM3Digest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.digest; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import java.security.MessageDigestSpi; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/09/07 + * @description + * The SM3 cryptographic hash function can compute input data of arbitrary length into a fixed hash value of 32 bytes. + */ +public class SM3Digest extends MessageDigestSpi { + + private final static int DIGEST_SIZE = GmSSLJNI.SM3_DIGEST_SIZE; + + private long sm3_ctx = 0; + + public SM3Digest() { + init(); + } + + /** + * You can call the update method multiple times. After all the data has been input, finally call the digest method to obtain the SM3 hash value of the entire data. + * @param input the input byte to be processed. + */ + @Override + protected void engineUpdate(byte input) { + byte[] data = new byte[]{input}; + this.update(data, 0, data.length); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + this.update(input, offset, len); + } + + @Override + protected byte[] engineDigest() { + return this.digest(); + } + + /** + * If you need to calculate different SM3 hash values for multiple sets of data, you can use the reset method to reset, + * and then call the update and digest methods again to compute the hash value of a new set of data. + */ + @Override + protected void engineReset() { + this.reset(); + } + + private void init(){ + if ((sm3_ctx = GmSSLJNI.sm3_ctx_new()) == 0) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm3_init(sm3_ctx) != 1) { + throw new GmSSLException(""); + } + } + + private void update(byte[] data, int offset, int len) { + if (data == null + || offset < 0 + || len < 0 + || offset + len <= 0 + || data.length < offset + len) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm3_update(sm3_ctx, data, offset, len) != 1) { + throw new GmSSLException(""); + } + } + + private byte[] digest() { + byte[] dgst = new byte[DIGEST_SIZE]; + if (GmSSLJNI.sm3_finish(sm3_ctx, dgst) != 1) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm3_init(sm3_ctx) != 1) { + throw new GmSSLException(""); + } + return dgst; + } + + private void reset() { + if (GmSSLJNI.sm3_init(sm3_ctx) != 1) { + throw new GmSSLException(""); + } + } + +} diff --git a/src/main/java/org/gmssl/crypto/digest/SM3Hmac.java b/src/main/java/org/gmssl/crypto/digest/SM3Hmac.java new file mode 100644 index 0000000..11d6243 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/digest/SM3Hmac.java @@ -0,0 +1,144 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.digest; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import javax.crypto.MacSpi; +import javax.crypto.SecretKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/09/07 + * @description + * HMAC-SM3 is a Message Authentication Code (MAC) algorithm based on the SM3 cryptographic hash algorithm. + * A MAC algorithm can be viewed as a keyed hash function, primarily used to protect messages from tampering. + * Both communicating parties need to agree on a key in advance, such as a 32-byte random byte sequence. + * The data sender uses this key to compute the MAC value of the message and appends the MAC value to the message. + * Upon receiving the message, the recipient uses the same key to compute the MAC value of the message and compares it with the MAC value attached to the sent message. + * If they match, it indicates that the message has not been tampered with; if they do not match, it indicates that the message has been altered. + */ +public class SM3Hmac extends MacSpi { + + public final static int MAC_SIZE = GmSSLJNI.SM3_HMAC_SIZE; + + private Key key; + + private long sm3_hmac_ctx = 0; + + public SM3Hmac() { + super(); + ctx(); + } + + @Override + protected int engineGetMacLength() { + return MAC_SIZE; + } + + /** + * The HMAC-SM3 algorithm can be seen as the SM3 algorithm with a key, so when creating an Sm3Hmac object, a key must be passed as an input parameter. + * Although HMAC-SM3 does not have any restrictions on key length in terms of the algorithm and implementation, for considerations of security and efficiency, the key length for the HMAC-SM3 algorithm is recommended to be 32 bytes (equivalent to the length of the SM3 hash value) and should not be less than 16 bytes. + * Using a key length longer than 32 bytes would increase computational overhead without enhancing security. + * @param key the (secret) key. + * @param params the algorithm parameters. + * + * @throws InvalidKeyException + * @throws InvalidAlgorithmParameterException + */ + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException { + if (!(key instanceof SecretKey)) { + throw new GmSSLException("Invalid key for HMAC-SM3"); + } + this.key = key; + init(); + } + + /** + * You can call update multiple times and ultimately execute doFinal. The HMAC-SM3 output is a fixed 32 bytes, which is a binary message authentication code of length MAC_SIZE. + * @param input the input byte to be processed. + */ + @Override + protected void engineUpdate(byte input) { + byte[] data = new byte[]{input}; + this.update(data, 0, data.length); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + this.update(input, offset, len); + } + + @Override + protected byte[] engineDoFinal() { + return generateMac(); + } + + @Override + protected void engineReset() { + this.reset(this.key); + } + + private void ctx(){ + if ((this.sm3_hmac_ctx = GmSSLJNI.sm3_hmac_ctx_new()) == 0) { + throw new GmSSLException(""); + } + } + + private void init() { + if (GmSSLJNI.sm3_hmac_init(this.sm3_hmac_ctx, key.getEncoded()) != 1) { + throw new GmSSLException(""); + } + } + + private void update(byte[] data, int offset, int len) { + if (data == null + || offset < 0 + || len < 0 + || offset + len <= 0 + || data.length < offset + len) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm3_hmac_update(this.sm3_hmac_ctx, data, offset, len) != 1) { + throw new GmSSLException(""); + } + } + + private void update(byte[] data) { + this.update(data, 0, data.length); + } + + private byte[] generateMac() { + byte[] mac = new byte[this.MAC_SIZE]; + if (GmSSLJNI.sm3_hmac_finish(this.sm3_hmac_ctx, mac) != 1) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm3_hmac_init(this.sm3_hmac_ctx, this.key.getEncoded()) != 1) { + throw new GmSSLException(""); + } + return mac; + } + + private void reset(Key key) { + if (key == null) { + throw new GmSSLException(""); + } + if (GmSSLJNI.sm3_hmac_init(this.sm3_hmac_ctx, key.getEncoded()) != 1) { + throw new GmSSLException(""); + } + this.key = key; + } +} diff --git a/src/main/java/org/gmssl/crypto/digest/SM3Pbkdf2.java b/src/main/java/org/gmssl/crypto/digest/SM3Pbkdf2.java new file mode 100644 index 0000000..1e29f1a --- /dev/null +++ b/src/main/java/org/gmssl/crypto/digest/SM3Pbkdf2.java @@ -0,0 +1,97 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.digest; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactorySpi; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/09/07 + * @description + * PBKDF2 is one of the secure and widely used PBKDF algorithm standards. The algorithm uses a hash function as the primary component to map passwords to keys. + * It employs a random and public salt value (Salt) to resist precomputation attacks, increases the difficulty of online cracking by adding multiple rounds of iterative computation, and supports variable derived key lengths. + */ +public class SM3Pbkdf2 extends SecretKeyFactorySpi { + + public final static int MAX_SALT_SIZE = GmSSLJNI.SM3_PBKDF2_MAX_SALT_SIZE; + public final static int DEFAULT_SALT_SIZE = GmSSLJNI.SM3_PBKDF2_DEFAULT_SALT_SIZE; + public final static int MIN_ITER = GmSSLJNI.SM3_PBKDF2_MIN_ITER; + public final static int MAX_ITER = GmSSLJNI.SM3_PBKDF2_MAX_ITER; + public final static int MAX_KEY_SIZE = GmSSLJNI.SM3_PBKDF2_MAX_KEY_SIZE; + + public final static String ALGORITHM = "SM3Pbkdf2"; + + public SM3Pbkdf2() { + super(); + } + + /** + * + * @param keySpec PBEKeySpec the specification (key material) of the secret key + * pass is the user password used for deriving the key. + * salt is the value used to resist precomputation attacks. This value should be randomly generated (for example, using the Random class) and should have a certain length. + * The iter parameter represents the number of times the SM3 algorithm is called iteratively when deriving the key. A larger iter value increases the difficulty of brute-force attacks but also increases the computational overhead for users calling this function. + * The keylen parameter indicates the desired length of the derived key, which must not exceed the constant MAX_KEY_SIZE. + * @return + * @throws InvalidKeySpecException + */ + @Override + protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException { + if (!(keySpec instanceof PBEKeySpec)) { + throw new GmSSLException("Invalid KeySpec"); + } + PBEKeySpec pbeKeySpec = (PBEKeySpec) keySpec; + char[] password = pbeKeySpec.getPassword(); + byte[] salt = pbeKeySpec.getSalt(); + int iterations = pbeKeySpec.getIterationCount(); + int derivedKeyLength = pbeKeySpec.getKeyLength(); + byte[] key = deriveKey(new String(password), salt, iterations, derivedKeyLength); + return new SecretKeySpec(key, ALGORITHM); + } + + @Override + protected KeySpec engineGetKeySpec(SecretKey key, Class keySpec) throws InvalidKeySpecException { + throw new GmSSLException("Not supported"); + } + + @Override + protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException { + throw new GmSSLException("Not supported"); + } + + private byte[] deriveKey(String pass, byte[] salt, int iter, int keylen) { + if (pass == null) { + throw new GmSSLException(""); + } + if (salt == null || salt.length > MAX_SALT_SIZE) { + throw new GmSSLException(""); + } + if (iter < MIN_ITER || iter > MAX_ITER) { + throw new GmSSLException(""); + } + if (keylen < 0 || keylen > MAX_KEY_SIZE) { + throw new GmSSLException(""); + } + byte[] key = GmSSLJNI.sm3_pbkdf2(pass, salt, iter, keylen); + if (key == null) { + throw new GmSSLException(""); + } + return key; + } +} diff --git a/src/main/java/org/gmssl/crypto/symmetric/SM4CBC.java b/src/main/java/org/gmssl/crypto/symmetric/SM4CBC.java new file mode 100644 index 0000000..5f2a4b9 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/symmetric/SM4CBC.java @@ -0,0 +1,201 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.symmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public class SM4CBC extends SM4Engine { + + public final static int IV_SIZE = GmSSLJNI.SM4_BLOCK_SIZE; + + private long sm4_cbc_ctx; + + private byte[] iv; + + private boolean do_encrypt = true; + + private boolean inited; + + private int offset; + + private byte[] outputByteArray; + + protected SM4CBC() { + super(); + ctx(); + } + + @Override + protected byte[] engineGetIV() { + return iv; + } + + @Override + protected void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + throw new GmSSLException("Initialization method not supported!"); + } + + @Override + protected void init(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random){ + if (!(params instanceof IvParameterSpec)) { + throw new GmSSLException("need the IvParameterSpec parameter"); + } + this.iv = ((IvParameterSpec) params).getIV(); + this.do_encrypt = (opmode == Cipher.ENCRYPT_MODE); + init(key.getEncoded(), iv, do_encrypt); + + outputByteArray = new byte[BLOCK_SIZE]; + } + + @Override + protected byte[] processUpdate(byte[] input, int inputOffset, int inputLen) { + int newOutputLength = BLOCK_SIZE + offset + inputLen; + if (outputByteArray.length < newOutputLength) { + int newSize = Math.max(outputByteArray.length * 3 / 2 + BLOCK_SIZE, newOutputLength); + outputByteArray = Arrays.copyOf(outputByteArray, newSize); + } + + int outLen = processUpdate(input, inputOffset, inputLen, outputByteArray, offset); + return Arrays.copyOfRange(outputByteArray,offset-outLen,offset); + } + + @Override + protected int processUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset){ + int outLen = update(input, inputOffset, inputLen, output, outputOffset); + this.offset+=outLen; + return outLen; + } + + @Override + protected byte[] processBlock(byte[] input, int inputOffset, int inputLen) { + if(null!=input){ + processUpdate(input, inputOffset, inputLen); + } + int outLen = doFinal(outputByteArray, this.offset); + outLen = outLen + this.offset; + this.offset = 0; + outputByteArray = Arrays.copyOfRange(outputByteArray,0,outLen); + return outputByteArray; + } + + @Override + protected int processBlock(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) { + int outLen = 0; + if(null!=input){ + outLen=this.processUpdate(input, inputOffset, inputLen, output, outputOffset); + } + outLen += doFinal(output, this.offset); + this.offset = 0; + return outLen; + } + + private void ctx() { + if ((this.sm4_cbc_ctx = GmSSLJNI.sm4_cbc_ctx_new()) == 0) { + throw new GmSSLException(""); + } + this.inited = false; + } + + private void init(byte[] key, byte[] iv, boolean do_encrypt) { + if (key == null + || key.length != this.KEY_SIZE + || iv == null + || iv.length != this.IV_SIZE) { + throw new GmSSLException(""); + } + + if (do_encrypt) { + if (GmSSLJNI.sm4_cbc_encrypt_init(this.sm4_cbc_ctx, key, iv) != 1) { + throw new GmSSLException(""); + } + } else { + if (GmSSLJNI.sm4_cbc_decrypt_init(this.sm4_cbc_ctx, key, iv) != 1) { + throw new GmSSLException(""); + } + } + + this.do_encrypt = do_encrypt; + this.inited = true; + } + + private int update(byte[] in, int in_offset, int inlen, byte[] out, int out_offset) { + if (this.inited == false) { + throw new GmSSLException(""); + } + + if (in == null + || in_offset < 0 + || inlen < 0 + || in_offset + inlen <= 0 + || in.length < in_offset + inlen) { + throw new GmSSLException(""); + } + if (out == null + || out_offset < 0 + || out.length < out_offset) { + throw new GmSSLException(""); + } + + int outlen; + if (this.do_encrypt) { + if ((outlen = GmSSLJNI.sm4_cbc_encrypt_update(this.sm4_cbc_ctx, in, in_offset, inlen, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + } else { + if ((outlen = GmSSLJNI.sm4_cbc_decrypt_update(this.sm4_cbc_ctx, in, in_offset, inlen, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + } + + return outlen; + } + + private int doFinal(byte[] out, int out_offset) { + if (this.inited == false) { + throw new GmSSLException(""); + } + + if (out == null + || out_offset < 0 + || out.length < out_offset) { + throw new GmSSLException(""); + } + + int outlen; + if (this.do_encrypt) { + if ((outlen = GmSSLJNI.sm4_cbc_encrypt_finish(this.sm4_cbc_ctx, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + } else { + if ((outlen = GmSSLJNI.sm4_cbc_decrypt_finish(this.sm4_cbc_ctx, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + } + + this.inited = false; + return outlen; + } + +} diff --git a/src/main/java/org/gmssl/crypto/symmetric/SM4CTR.java b/src/main/java/org/gmssl/crypto/symmetric/SM4CTR.java new file mode 100644 index 0000000..01732f3 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/symmetric/SM4CTR.java @@ -0,0 +1,177 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.symmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public class SM4CTR extends SM4Engine { + + public final static int IV_SIZE = GmSSLJNI.SM4_BLOCK_SIZE; + + private byte[] iv; + + private long sm4_ctr_ctx = 0; + private boolean inited = false; + + private int offset; + + private byte[] outputByteArray; + + protected SM4CTR() { + super(); + ctx(); + } + + @Override + protected void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + throw new GmSSLException("Initialization method not supported!"); + } + + @Override + protected void init(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) { + if (!(params instanceof IvParameterSpec)) { + throw new GmSSLException("need the IvParameterSpec parameter"); + } + this.iv = ((IvParameterSpec) params).getIV(); + init(key.getEncoded(), iv); + + outputByteArray = new byte[BLOCK_SIZE]; + } + + @Override + protected byte[] engineGetIV() { + return iv; + } + + @Override + protected byte[] processUpdate(byte[] input, int inputOffset, int inputLen) { + int newOutputLength = BLOCK_SIZE + offset + inputLen; + if (outputByteArray.length < newOutputLength) { + int newSize = Math.max(outputByteArray.length * 3 / 2 + BLOCK_SIZE, newOutputLength); + outputByteArray = Arrays.copyOf(outputByteArray, newSize); + } + + int outLen = processUpdate(input, inputOffset, inputLen, outputByteArray, offset); + return Arrays.copyOfRange(outputByteArray,offset-outLen,offset); + } + + @Override + protected int processUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) { + int outLen = update(input, inputOffset, inputLen, output, outputOffset); + this.offset += outLen; + return outLen; + } + + @Override + protected byte[] processBlock(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { + if(null!=input){ + processUpdate(input, inputOffset, inputLen); + } + int outLen = doFinal(outputByteArray, this.offset); + outLen = outLen + this.offset; + this.offset = 0; + outputByteArray = Arrays.copyOfRange(outputByteArray,0,outLen); + return outputByteArray; + } + + @Override + protected int processBlock(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + int outLen = 0; + if(null!=input){ + outLen=this.processUpdate(input, inputOffset, inputLen, output, outputOffset); + } + outLen += doFinal(output, this.offset); + this.offset = 0; + return outLen; + } + + public void ctx(){ + if ((this.sm4_ctr_ctx = GmSSLJNI.sm4_ctr_ctx_new()) == 0) { + throw new GmSSLException(""); + } + this.inited = false; + } + + public void init(byte[] key, byte[] iv) { + if (key == null + || key.length != this.KEY_SIZE + || iv == null + || iv.length != this.IV_SIZE) { + throw new GmSSLException(""); + } + + if (GmSSLJNI.sm4_ctr_encrypt_init(this.sm4_ctr_ctx, key, iv) != 1) { + throw new GmSSLException(""); + } + + this.inited = true; + } + + public int update(byte[] in, int in_offset, int inlen, byte[] out, int out_offset) { + if (this.inited == false) { + throw new GmSSLException(""); + } + + if (in == null + || in_offset < 0 + || inlen < 0 + || in_offset + inlen <= 0 + || in.length < in_offset + inlen) { + throw new GmSSLException(""); + } + if (out == null + || out_offset < 0 + || out.length < out_offset) { + throw new GmSSLException(""); + } + + int outlen; + if ((outlen = GmSSLJNI.sm4_ctr_encrypt_update(this.sm4_ctr_ctx, in, in_offset, inlen, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + return outlen; + } + + public int doFinal(byte[] out, int out_offset){ + if (this.inited == false) { + throw new GmSSLException(""); + } + + if (out == null + || out_offset < 0 + || out.length < out_offset) { + throw new GmSSLException(""); + } + + int outlen; + if ((outlen = GmSSLJNI.sm4_ctr_encrypt_finish(this.sm4_ctr_ctx, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + this.inited = false; + return outlen; + } +} diff --git a/src/main/java/org/gmssl/crypto/symmetric/SM4Cipher.java b/src/main/java/org/gmssl/crypto/symmetric/SM4Cipher.java new file mode 100644 index 0000000..2902c4c --- /dev/null +++ b/src/main/java/org/gmssl/crypto/symmetric/SM4Cipher.java @@ -0,0 +1,110 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.symmetric; + +import org.gmssl.GmSSLJNI; + +import javax.crypto.*; +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * CBC、CTR、ECB、GCM + */ +public class SM4Cipher extends CipherSpi { + + public static final int KEY_SIZE = GmSSLJNI.SM4_KEY_SIZE; + + public static final int BLOCK_SIZE = GmSSLJNI.SM4_BLOCK_SIZE; + + private SM4Engine sm4Engine; + + public SM4Cipher() { + super(); + } + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + this.sm4Engine = SM4CipherFactory.createCipher(mode); + } + + @Override + protected void engineSetPadding(String padding) throws NoSuchPaddingException { + SM4CipherFactory.setPaddingScheme(sm4Engine, padding); + } + + @Override + protected int engineGetBlockSize() { + return SM4Engine.BLOCK_SIZE; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return 0; + } + + @Override + protected byte[] engineGetIV() { + return sm4Engine.engineGetIV(); + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + sm4Engine.init(opmode,key,random); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + sm4Engine.init(opmode, key, params, random); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + byte[] result = sm4Engine.processUpdate(input,inputOffset,inputLen); + return result; + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException { + int outLen = sm4Engine.processUpdate(input, inputOffset, inputLen, output, outputOffset); + return outLen; + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { + byte[] result = sm4Engine.processBlock(input, inputOffset, inputLen); + return result; + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + int outLen = sm4Engine.processBlock(input, inputOffset, inputLen, output, outputOffset); + return outLen; + } + + @Override + protected void engineUpdateAAD(byte[] src, int offset, int len) { + sm4Engine.processUpdateAAD(src, offset, len); + } + +} diff --git a/src/main/java/org/gmssl/crypto/symmetric/SM4CipherFactory.java b/src/main/java/org/gmssl/crypto/symmetric/SM4CipherFactory.java new file mode 100644 index 0000000..cde6769 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/symmetric/SM4CipherFactory.java @@ -0,0 +1,66 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.symmetric; + +import org.gmssl.crypto.CipherPaddingEnum; +import org.gmssl.crypto.PKCS7PaddingScheme; + +import java.security.NoSuchAlgorithmException; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public class SM4CipherFactory { + + /** + * Create an SM4 encryption and decryption engine that supports ECB, CBC, CTR, and GCM modes. + * @param mode + * @return + */ + public static SM4Engine createCipher(String mode){ + SM4Engine cipher; + try { + switch (mode.toUpperCase()) { + case "ECB": + cipher = new SM4ECB(); + break; + case "CBC": + cipher = new SM4CBC(); + break; + case "CTR": + cipher = new SM4CTR(); + break; + case "GCM": + cipher = new SM4GCM(); + break; + default: + throw new NoSuchAlgorithmException("Unsupported mode: " + mode); + } + }catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + return cipher; + } + + /** + * Currently, only the PKCS7Padding padding mode for ECB algorithm mode is set. Other algorithms (SM4/CBC/PKCS5Padding, SM4/CTR/NoPadding, SM4/GCM/NoPadding) have their default padding implemented. + * @param engine + * @param padding + */ + public static void setPaddingScheme(SM4Engine engine, String padding) { + if(CipherPaddingEnum.PKCS7Padding.name().equals(padding)){ + engine.paddingScheme=new PKCS7PaddingScheme(); + } + } + +} diff --git a/src/main/java/org/gmssl/crypto/symmetric/SM4ECB.java b/src/main/java/org/gmssl/crypto/symmetric/SM4ECB.java new file mode 100644 index 0000000..27352c5 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/symmetric/SM4ECB.java @@ -0,0 +1,181 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.symmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; +import org.gmssl.Sm4; + +import javax.crypto.*; +import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public class SM4ECB extends SM4Engine { + + private Key key; + private long sm4_key; + + private boolean do_encrypt; + + private ByteBuffer buffer; + + @Override + protected void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + if (!(key instanceof SecretKey)) { + throw new GmSSLException("Invalid KeySpec"); + } + this.do_encrypt = (opmode == Cipher.ENCRYPT_MODE); + this.key = key; + // 初始化缓冲区 + this.buffer = ByteBuffer.allocate(2048); + init(); + } + + @Override + protected void init(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) { + throw new GmSSLException("Initialization method not supported!"); + } + + @Override + protected byte[] engineGetIV() { + return null; + } + + /** + * Mainly used for caching data; it will not immediately generate encryption or decryption results + * @param input + * @param inputOffset + * @param inputLen + * @return null + * Return a non-actual value; actual encryption or decryption operations are performed in processBlock + */ + @Override + protected byte[] processUpdate(byte[] input, int inputOffset, int inputLen) { + putBytes(input, inputOffset, inputLen); + return null; + } + + @Override + protected byte[] processBlock(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { + if(null!=input){ + putBytes(input, inputOffset, inputLen); + } + byte[] data = new byte[buffer.position()]; + buffer.flip(); + buffer.get(data); + + byte[] outPutByteArray = new byte[buffer.position()]; + if(do_encrypt){ + data = this.paddingScheme.pad(data,this.BLOCK_SIZE); + outPutByteArray = new byte[data.length]; + for (int i = 0; i < data.length; i += this.BLOCK_SIZE) { + encrypt(data,i,outPutByteArray,i); + } + }else{ + for (int i = 0; i < data.length; i += this.BLOCK_SIZE) { + encrypt(data,i,outPutByteArray,i); + } + outPutByteArray=this.paddingScheme.unpad(outPutByteArray); + } + + buffer.clear(); + return outPutByteArray; + } + + /** + * Mainly used for caching data; it will not immediately generate encryption or decryption results + * @param input + * @param inputOffset + * @param inputLen + * @param output + * @param outputOffset + * @return 0 + * Return a non-actual value; actual encryption or decryption operations are performed in processBlock + */ + @Override + protected int processUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) { + putBytes(input, inputOffset, inputLen); + return 0; + } + + /** + * + * @param input + * @param inputOffset + * @param inputLen + * @param output + * @param outputOffset + * @return actual encryption or decryption bytes length,not the whole length of the output data + * + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + @Override + protected int processBlock(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws IllegalBlockSizeException, BadPaddingException { + byte[] outPutByteArray = processBlock(input, inputOffset, inputLen); + System.arraycopy(outPutByteArray, 0,output, outputOffset, outPutByteArray.length); + return outPutByteArray.length; + } + + private void putBytes(byte[] input, int inputOffset, int inputLen){ + if(buffer.remaining() in.length) { + throw new GmSSLException(""); + } + if (out == null + || out_offset < 0 + || out_offset + this.BLOCK_SIZE <= 0 + || out_offset + this.BLOCK_SIZE > in.length) { + throw new GmSSLException(""); + } + + if (GmSSLJNI.sm4_encrypt(sm4_key, in, in_offset, out, out_offset) != 1) { + throw new GmSSLException(""); + } + } +} diff --git a/src/main/java/org/gmssl/crypto/symmetric/SM4Engine.java b/src/main/java/org/gmssl/crypto/symmetric/SM4Engine.java new file mode 100644 index 0000000..52bfe1c --- /dev/null +++ b/src/main/java/org/gmssl/crypto/symmetric/SM4Engine.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.symmetric; + +import org.gmssl.GmSSLJNI; +import org.gmssl.crypto.PaddingScheme; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public abstract class SM4Engine { + + public static final int KEY_SIZE = SM4Cipher.KEY_SIZE; + + public static final int BLOCK_SIZE = SM4Cipher.BLOCK_SIZE; + + protected PaddingScheme paddingScheme; + + protected abstract void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException; + + protected abstract void init(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random); + + protected abstract byte[] engineGetIV(); + + protected abstract byte[] processUpdate(byte[] input, int inputOffset, int inputLen); + + protected abstract int processUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException; + + protected abstract int processBlock(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException; + + protected abstract byte[] processBlock(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException; + protected void processUpdateAAD(byte[] src, int offset, int len){}; + +} diff --git a/src/main/java/org/gmssl/crypto/symmetric/SM4GCM.java b/src/main/java/org/gmssl/crypto/symmetric/SM4GCM.java new file mode 100644 index 0000000..a10f8b4 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/symmetric/SM4GCM.java @@ -0,0 +1,231 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.symmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; +import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public class SM4GCM extends SM4Engine { + + public final static int MIN_IV_SIZE = GmSSLJNI.SM4_GCM_MIN_IV_SIZE; + public final static int MAX_IV_SIZE = GmSSLJNI.SM4_GCM_MAX_IV_SIZE; + public final static int DEFAULT_IV_SIZE = GmSSLJNI.SM4_GCM_DEFAULT_IV_SIZE; + public final static int MIN_TAG_SIZE = 8; + public final static int MAX_TAG_SIZE = GmSSLJNI.SM4_GCM_MAX_TAG_SIZE; + + private long sm4_gcm_ctx = 0; + private boolean do_encrypt = true; + private boolean inited = false; + + private byte[] iv; + + private ByteBuffer aad; + + private Key key; + + private int tLen; + + private int offset; + + private byte[] outputByteArray; + + protected SM4GCM(){ + super(); + ctx(); + } + + @Override + protected void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + throw new GmSSLException("Initialization method not supported!"); + } + + @Override + protected void init(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) { + if (!(params instanceof GCMParameterSpec)) { + throw new GmSSLException("need the GCMParameterSpec parameter"); + } + this.key = key; + this.iv = ((GCMParameterSpec) params).getIV(); + this.tLen = ((GCMParameterSpec) params).getTLen(); + this.do_encrypt = (opmode == Cipher.ENCRYPT_MODE); + + outputByteArray = new byte[BLOCK_SIZE+tLen]; + aad=ByteBuffer.allocate(BLOCK_SIZE+tLen); + } + + @Override + protected byte[] engineGetIV() { + return iv; + } + + @Override + protected byte[] processUpdate(byte[] input, int inputOffset, int inputLen) { + int newOutputLength = BLOCK_SIZE + offset + tLen + inputLen; + if (outputByteArray.length < newOutputLength) { + int newSize = Math.max(outputByteArray.length * 3 / 2 + BLOCK_SIZE + tLen, newOutputLength); + outputByteArray = Arrays.copyOf(outputByteArray, newSize); + } + + int outLen = processUpdate(input, inputOffset, inputLen, outputByteArray, offset); + return Arrays.copyOfRange(outputByteArray,offset-outLen,offset); + } + + @Override + protected int processUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) { + int outLen = update(input, inputOffset, inputLen, output, outputOffset); + this.offset+=outLen; + return outLen; + } + + @Override + protected byte[] processBlock(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { + if(null!=input){ + processUpdate(input, inputOffset, inputLen); + } + int outLen = doFinal(outputByteArray, this.offset); + outLen = outLen + this.offset; + this.offset = 0; + outputByteArray = Arrays.copyOfRange(outputByteArray,0,outLen); + return outputByteArray; + } + + @Override + protected int processBlock(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + int outLen = 0; + if(null!=input){ + outLen=this.processUpdate(input, inputOffset, inputLen, output, outputOffset); + } + outLen += doFinal(output, this.offset); + this.offset = 0; + return outLen; + } + + @Override + protected void processUpdateAAD(byte[] src, int offset, int len) { + if(aad.remaining() this.MAX_IV_SIZE + || taglen < this.MIN_TAG_SIZE + || taglen > this.MAX_TAG_SIZE) { + throw new GmSSLException(""); + } + + if (do_encrypt == true) { + if (GmSSLJNI.sm4_gcm_encrypt_init(this.sm4_gcm_ctx, key, iv, aad, taglen) != 1) { + throw new GmSSLException(""); + } + } else { + if (GmSSLJNI.sm4_gcm_decrypt_init(this.sm4_gcm_ctx, key, iv, aad, taglen) != 1) { + throw new GmSSLException(""); + } + } + + this.do_encrypt = do_encrypt; + this.inited = true; + } + + private int update(byte[] in, int in_offset, int inlen, byte[] out, int out_offset){ + if (this.inited == false) { + throw new GmSSLException(""); + } + + if (in == null + || in_offset < 0 + || inlen < 0 + || in_offset + inlen <= 0 + || in.length < in_offset + inlen) { + throw new GmSSLException(""); + } + if (out == null + || out_offset < 0 + || out.length < out_offset) { + throw new GmSSLException(""); + } + + int outlen; + if (this.do_encrypt) { + if ((outlen = GmSSLJNI.sm4_gcm_encrypt_update(this.sm4_gcm_ctx, in, in_offset, inlen, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + } else { + if ((outlen = GmSSLJNI.sm4_gcm_decrypt_update(this.sm4_gcm_ctx, in, in_offset, inlen, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + } + + return outlen; + } + + private int doFinal(byte[] out, int out_offset) { + + if (this.inited == false) { + throw new GmSSLException(""); + } + + if (out == null + || out_offset < 0 + || out.length < out_offset) { + throw new GmSSLException(""); + } + + int outlen; + if (this.do_encrypt) { + if ((outlen = GmSSLJNI.sm4_gcm_encrypt_finish(this.sm4_gcm_ctx, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + } else { + if ((outlen = GmSSLJNI.sm4_gcm_decrypt_finish(this.sm4_gcm_ctx, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + } + + this.inited = false; + return outlen; + } +} diff --git a/src/main/java/org/gmssl/crypto/symmetric/ZucCipher.java b/src/main/java/org/gmssl/crypto/symmetric/ZucCipher.java new file mode 100644 index 0000000..5f43828 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/symmetric/ZucCipher.java @@ -0,0 +1,221 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.symmetric; + +import org.gmssl.GmSSLException; +import org.gmssl.GmSSLJNI; + +import javax.crypto.*; +import javax.crypto.spec.IvParameterSpec; +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * The Zu Chongzhi Cipher Algorithm (ZU Cipher, ZUC) is a stream cipher with both the key and IV lengths set to 16 bytes. + * As a stream cipher, ZUC can encrypt input data of variable length, and the ciphertext output has the same length as the input data. + * This makes it suitable for applications that do not allow ciphertext expansion. + * In terms of security, it is not recommended to use the ZUC algorithm to encrypt large amounts of data (such as GB or TB levels) with a single key and IV, to avoid a decrease in security when the stream cipher produces extremely long outputs. + */ +public class ZucCipher extends CipherSpi { + + public final static int IV_SIZE = GmSSLJNI.ZUC_IV_SIZE; + public final static int BLOCK_SIZE = 4; + + private long zuc_ctx; + private boolean inited; + + private byte[] iv; + + private int offset; + + private byte[] outputByteArray; + + public ZucCipher(){ + ctx(); + } + + /** + * As a stream cipher, ZUC generates a pseudo-random sequence for each encryption or decryption operation, which is then XORed bit-by-bit with the plaintext to achieve encryption or decryption. + * Therefore, ZUC does not require the use of specific modes like block ciphers; instead, it directly generates the encryption key stream and performs the encryption operation bit-by-bit. + * @param mode the cipher mode + * + */ + @Override + protected void engineSetMode(String mode){ + } + + /** + * ZUC does not require a padding mode and can directly handle plaintext of any length. + * @param padding the padding mechanism + * + */ + @Override + protected void engineSetPadding(String padding){ + } + + @Override + protected int engineGetBlockSize() { + return BLOCK_SIZE; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return 0; + } + + @Override + protected byte[] engineGetIV() { + return iv; + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + throw new GmSSLException("Initialization method not supported!"); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidAlgorithmParameterException { + if (params instanceof IvParameterSpec) { + IvParameterSpec ivSpec = (IvParameterSpec) params; + this.iv = ivSpec.getIV(); + } else { + throw new InvalidAlgorithmParameterException("Unsupported parameters"); + } + init(key.getEncoded(), iv); + + outputByteArray = new byte[BLOCK_SIZE]; + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new GmSSLException("Initialization method not supported!"); + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + int newOutputLength = BLOCK_SIZE + offset + inputLen; + if (outputByteArray.length < newOutputLength) { + int newSize = Math.max(outputByteArray.length * 3 / 2 + BLOCK_SIZE, newOutputLength); + outputByteArray = Arrays.copyOf(outputByteArray, newSize); + } + + int outLen = engineUpdate(input, inputOffset, inputLen, outputByteArray, offset); + return Arrays.copyOfRange(outputByteArray,offset-outLen,offset); + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset){ + int outLen = update(input, inputOffset, inputLen, output, outputOffset); + this.offset+=outLen; + return outLen; + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { + if(null!=input){ + engineUpdate(input, inputOffset, inputLen); + } + int outLen = doFinal(outputByteArray, this.offset); + outLen = outLen + this.offset; + this.offset = 0; + outputByteArray = Arrays.copyOfRange(outputByteArray,0,outLen); + return outputByteArray; + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + int outLen = 0; + if(null!=input){ + outLen=this.engineUpdate(input, inputOffset, inputLen, output, outputOffset); + } + outLen += doFinal(output, this.offset); + this.offset = 0; + return outLen; + } + + private void ctx(){ + if ((this.zuc_ctx = GmSSLJNI.zuc_ctx_new()) == 0) { + throw new GmSSLException(""); + } + this.inited = false; + } + + private void init(byte[] key, byte[] iv){ + if (key == null + || key.length != ZucKey.KEY_SIZE + || iv == null + || iv.length != this.IV_SIZE) { + throw new GmSSLException(""); + } + + if (GmSSLJNI.zuc_encrypt_init(this.zuc_ctx, key, iv) != 1) { + throw new GmSSLException(""); + } + + this.inited = true; + } + + private int update(byte[] in, int in_offset, int inlen, byte[] out, int out_offset) { + + if (this.inited == false) { + throw new GmSSLException(""); + } + + if (in == null + || in_offset < 0 + || inlen < 0 + || in_offset + inlen <= 0 + || in.length < in_offset + inlen) { + throw new GmSSLException(""); + } + if (out == null + || out_offset < 0 + || out.length < out_offset) { + throw new GmSSLException(""); + } + + int outlen; + if ((outlen = GmSSLJNI.zuc_encrypt_update(this.zuc_ctx, in, in_offset, inlen, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + + return outlen; + } + + private int doFinal(byte[] out, int out_offset) { + + if (this.inited == false) { + throw new GmSSLException(""); + } + + if (out == null + || out_offset < 0 + || out.length < out_offset) { + throw new GmSSLException(""); + } + + int outlen; + if ((outlen = GmSSLJNI.zuc_encrypt_finish(this.zuc_ctx, out, out_offset)) < 0) { + throw new GmSSLException(""); + } + + this.inited = false; + return outlen; + } +} diff --git a/src/main/java/org/gmssl/crypto/symmetric/ZucKey.java b/src/main/java/org/gmssl/crypto/symmetric/ZucKey.java new file mode 100644 index 0000000..f305f91 --- /dev/null +++ b/src/main/java/org/gmssl/crypto/symmetric/ZucKey.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014-2024 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.gmssl.crypto.symmetric; + +import org.gmssl.GmSSLJNI; + +import javax.crypto.SecretKey; + +/** + * @author yongfeili + * @email 290836576@qq.com + * @date 2024/07/27 + * @description + * + */ +public class ZucKey implements SecretKey { + + public final static int KEY_SIZE = GmSSLJNI.ZUC_KEY_SIZE; + + private byte[] key; + + public ZucKey(byte[] key){ + this.key = key; + } + + @Override + public String getAlgorithm() { + return "ZUC"; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return key; + } +} diff --git a/src/main/resources/lib/libgmssljni.dll b/src/main/resources/lib/libgmssljni.dll new file mode 100644 index 0000000..0d4e190 Binary files /dev/null and b/src/main/resources/lib/libgmssljni.dll differ diff --git a/src/test/java/org/gmssl/JceTest.java b/src/test/java/org/gmssl/JceTest.java new file mode 100644 index 0000000..39f2f35 --- /dev/null +++ b/src/test/java/org/gmssl/JceTest.java @@ -0,0 +1,406 @@ +package org.gmssl; + +import org.gmssl.crypto.asymmetric.*; +import org.gmssl.crypto.digest.SM3Hmac; +import org.gmssl.crypto.digest.SM3Pbkdf2; +import org.gmssl.crypto.symmetric.*; +import org.junit.Before; +import org.junit.Test; + +import javax.crypto.*; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; +import java.security.*; +import java.util.Arrays; + +/** + * @author yongfeili + * @date 2024/8/2 + * @description you must need to use openjdk! + * https://jdk.java.net/archive/ + * https://stackoverflow.com/questions/1756801/how-to-sign-a-custom-jce-security-provider + */ +public class JceTest { + + @Before + public void beforeTest(){ + Security.addProvider(new org.gmssl.crypto.GmSSLProvider()); + } + + @Test + public void SM2_test() throws Exception{ + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("SM2", "GmSSL"); + keyPairGen.initialize(256); + KeyPair keyPair = keyPairGen.generateKeyPair(); + byte[] pub= keyPair.getPublic().getEncoded(); + System.out.println(byteToHex(pub)); + byte[] pri= keyPair.getPrivate().getEncoded(); + // export private key + SM2PrivateKey SM2PrivateKey= (SM2PrivateKey)keyPair.getPrivate(); + SM2PrivateKey.exportEncryptedPrivateKeyInfoPem("123456", "D:\\private.key.pem"); + System.out.println(byteToHex(pri)); + + //Test "Z-value" hash + SM2PublicKey sm2PublicKey = new SM2PublicKey(pub); + byte[] zHash = sm2PublicKey.computeZ("Hello, GmSSL"); + System.out.println("zHash:"+byteToHex(zHash)); + + Cipher cipher = Cipher.getInstance("SM2", "GmSSL"); + // Test encryption + cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic()); + byte[] plaintext = "Hello, GmSSL".getBytes(); + byte[] ciphertext = cipher.doFinal(plaintext); + System.out.println("Ciphertext: " + byteToHex(ciphertext)); + // Test decryption + cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); + byte[] decrypted = cipher.doFinal(ciphertext); + System.out.println("Decrypted: " + new String(decrypted)); + + Signature signature = Signature.getInstance("SM2", "GmSSL"); + // Test signature + signature.initSign(keyPair.getPrivate()); + byte[] signatureText = "Hello, GmSSL".getBytes(); + signature.update(signatureText); + byte[] signatureByte = signature.sign(); + System.out.println("Signature:"+byteToHex(signatureByte)); + // Test signature verification + signature.initVerify(keyPair.getPublic()); + signature.update(signatureText); + boolean signatureResult = signature.verify(signatureByte); + System.out.println("SignatureResult:"+signatureResult); + + + Signature signatureImport = Signature.getInstance("SM2", "GmSSL"); + // import private key + String privateKeyInfoHex="308193020100301306072a8648ce3d020106082a811ccf5501822d0479307702010104207fef3e258348873c47117c15093266e9dad99e131f1778e53d362b2b70649f85a00a06082a811ccf5501822da14403420004f94c0abb6cd00c6f0918cb9c54162213501d5cc278f5d3fcf63886f4e1dc6322b1b110e33a25216f258c4cce5fd52ab320d3b086ee5390f7387218c92578c3ab"; + byte[] privateKeyInfo = hexToByte(privateKeyInfoHex); + signatureImport.initSign(new SM2PrivateKey(privateKeyInfo)); + signatureImport.update(signatureText); + byte[] signatureByteImport = signatureImport.sign(); + System.out.println("Signature:"+byteToHex(signatureByteImport)); + // export public key + String publicKeyInfoHex = "3059301306072a8648ce3d020106082a811ccf5501822d03420004f94c0abb6cd00c6f0918cb9c54162213501d5cc278f5d3fcf63886f4e1dc6322b1b110e33a25216f258c4cce5fd52ab320d3b086ee5390f7387218c92578c3ab"; + byte[] publicKeyInfo = hexToByte(publicKeyInfoHex); + signatureImport.initVerify(new SM2PublicKey(publicKeyInfo)); + signatureImport.update(signatureText); + boolean signatureResultImport = signatureImport.verify(signatureByteImport); + System.out.println("SignatureResult:"+signatureResultImport); + } + + @Test + public void sm2_certificate_test() throws Exception{ + SM2Certificate sm2Cert = new SM2Certificate(); + //sm2Cert.importPem("D:\\cert.pem"); + //System.out.println("NotAfter:"+sm2Cert.getNotAfter()); + } + + @Test + public void SM3_test() throws Exception{ + String text="Hello, GmSSL"; + // hash + MessageDigest sm3Digest = MessageDigest.getInstance("SM3","GmSSL"); + sm3Digest.update("abc".getBytes()); + sm3Digest.reset(); + sm3Digest.update(text.getBytes()); + byte[] digest = sm3Digest.digest(); + System.out.println("digest:"+byteToHex(digest)); + + //HMAC Message Authentication Code Algorithm Based on SM3 + Mac hmac = Mac.getInstance("SM3", "GmSSL"); + hmac.init(new SecretKeySpec(new Random().randBytes(SM3Hmac.MAC_SIZE), "SM3")); + hmac.update(text.getBytes()); + byte[] hmacFinal = hmac.doFinal(); + System.out.println("hmac:"+byteToHex(hmacFinal)); + + //Password-Based Key Derivation Function PBKDF2 + char[] password = "P@ssw0rd".toCharArray(); + byte[] salt = new Random().randBytes(SM3Pbkdf2.DEFAULT_SALT_SIZE); + int iterations = SM3Pbkdf2.MIN_ITER * 2; + int keyLength = SM3Pbkdf2.MAX_KEY_SIZE; + PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength); + SecretKeyFactory skf = SecretKeyFactory.getInstance("SM3Pbkdf2"); + SecretKey key = skf.generateSecret(spec); + byte[] keyBytes = key.getEncoded(); + System.out.println("DerivedKey: " + byteToHex(keyBytes)); + } + + @Test + public void SM4_ECB_test() throws Exception{ + String text="Hello, GmSSL!"; + SecureRandom secureRandom = SecureRandom.getInstance("Random", "GmSSL"); + // encryption + Cipher sm4Cipher = Cipher.getInstance("SM4/ECB/PKCS7Padding", "GmSSL"); + SecretKeySpec sm4Key = new SecretKeySpec(secureRandom.generateSeed(SM4ECB.KEY_SIZE), "SM4"); + sm4Cipher.init(Cipher.ENCRYPT_MODE, sm4Key); + sm4Cipher.update(text.getBytes()); + sm4Cipher.update("cipher.".getBytes(),0, 6); + byte[] ciphertext = sm4Cipher.doFinal(); + System.out.println("Ciphertext: " + byteToHex(ciphertext)); + // decryption + sm4Cipher.init(Cipher.DECRYPT_MODE, sm4Key); + byte[] plaintext = sm4Cipher.doFinal(ciphertext); + System.out.println("plaintext: " + new String(plaintext)); + } + + @Test + public void SM4_CBC_test1() throws Exception{ + String text="Hello,GmSSL!"; + SecureRandom secureRandom = SecureRandom.getInstance("Random", "GmSSL"); + byte[] randomBytes = new byte[32]; + secureRandom.nextBytes(randomBytes); + System.out.println("Generated Random Bytes: " + byteToHex(randomBytes)); + // encryption + Cipher sm4cbcCipher = Cipher.getInstance("SM4/CBC/PKCS5Padding", "GmSSL"); + byte[] key = secureRandom.generateSeed(SM4CBC.KEY_SIZE); + byte[] iv = secureRandom.generateSeed(SM4CBC.IV_SIZE); + sm4cbcCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv)); + sm4cbcCipher.update(text.getBytes()); + sm4cbcCipher.update(text.getBytes()); + sm4cbcCipher.update(text.getBytes()); + byte[] ciphertext = sm4cbcCipher.doFinal(); + System.out.println("Ciphertext: " + byteToHex(ciphertext)); + // decryption + sm4cbcCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv)); + sm4cbcCipher.update(ciphertext); + byte[] plaintext2 = sm4cbcCipher.doFinal(); + String plaintextStr=new String(plaintext2); + System.out.println("plaintext: " + plaintextStr); + } + + @Test + public void SM4_CBC_test2() throws Exception{ + String text="Hello,GmSSL!"; + SecureRandom secureRandom = SecureRandom.getInstance("Random", "GmSSL"); + byte[] randomBytes = new byte[32]; + secureRandom.nextBytes(randomBytes); + System.out.println("Generated Random Bytes: " + byteToHex(randomBytes)); + // encryption + Cipher sm4cbcCipher = Cipher.getInstance("SM4/CBC/PKCS5Padding", "GmSSL"); + byte[] key = secureRandom.generateSeed(SM4CBC.KEY_SIZE); + byte[] iv = secureRandom.generateSeed(SM4CBC.IV_SIZE); + sm4cbcCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv)); + byte[] ciphertext = new byte[100]; + int cipherlen = sm4cbcCipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, 0); + cipherlen += sm4cbcCipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, cipherlen); + cipherlen += sm4cbcCipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, cipherlen); + cipherlen += sm4cbcCipher.doFinal(text.getBytes(), 0, text.getBytes().length,ciphertext, cipherlen); + byte[] ciphertext1 = Arrays.copyOfRange(ciphertext,0,cipherlen); + System.out.println("Ciphertext: " + byteToHex(ciphertext1)); + // decryption + byte[] plaintext = new byte[ciphertext1.length + SM4CBC.BLOCK_SIZE]; + sm4cbcCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv)); + int plainLen = sm4cbcCipher.doFinal(ciphertext1, 0,ciphertext1.length, plaintext,0); + byte[] plaintext1 =Arrays.copyOfRange(plaintext,0,plainLen); + String plaintextStr=new String(plaintext1); + System.out.println("plaintext: " + plaintextStr); + } + + @Test + public void SM4_CTR_test1() throws Exception{ + String text="Hello, GmSSL!"; + SecureRandom secureRandom = SecureRandom.getInstance("Random", "GmSSL"); + Cipher sm4Cipher = Cipher.getInstance("SM4/CTR/NoPadding", "GmSSL"); + byte[] key = secureRandom.generateSeed(SM4CTR.KEY_SIZE); + byte[] iv = secureRandom.generateSeed(SM4CTR.IV_SIZE); + // encryption + sm4Cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv)); + byte[] ciphertext = new byte[100]; + int cipherlen = sm4Cipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, 0); + cipherlen += sm4Cipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, cipherlen); + cipherlen += sm4Cipher.doFinal(text.getBytes(), 0, text.getBytes().length, ciphertext, cipherlen); + byte[] ciphertext1 = Arrays.copyOfRange(ciphertext,0,cipherlen); + System.out.println("Ciphertext: " + byteToHex(ciphertext1)); + // decryption + byte[] plaintext = new byte[ciphertext1.length+SM4CTR.BLOCK_SIZE]; + sm4Cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv)); + int plainLen = sm4Cipher.doFinal(ciphertext1, 0, ciphertext1.length, plaintext, 0); + byte[] plaintext1 = Arrays.copyOfRange(plaintext,0,plainLen); + System.out.println("plaintext: " + new String(plaintext1)); + } + + @Test + public void SM4_CTR_test2() throws Exception{ + String text="Hello, GmSSL!"; + SecureRandom secureRandom = SecureRandom.getInstance("Random", "GmSSL"); + Cipher sm4Cipher = Cipher.getInstance("SM4/CTR/NoPadding", "GmSSL"); + byte[] key = secureRandom.generateSeed(SM4CTR.KEY_SIZE); + byte[] iv = secureRandom.generateSeed(SM4CTR.IV_SIZE); + // encryption + sm4Cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv)); + sm4Cipher.update(text.getBytes()); + sm4Cipher.update(text.getBytes()); + sm4Cipher.update(text.getBytes()); + byte[] ciphertext = sm4Cipher.doFinal(); + System.out.println("Ciphertext: " + byteToHex(ciphertext)); + // decryption + sm4Cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "SM4"), new IvParameterSpec(iv)); + sm4Cipher.update(ciphertext); + byte[] plaintext1=sm4Cipher.doFinal(); + System.out.println("plaintext: " + new String(plaintext1)); + } + + @Test + public void SM4_GCM_test1() throws Exception { + String text="Hello, GmSSL!"; + SecureRandom secureRandom = SecureRandom.getInstance("Random", "GmSSL"); + Cipher sm4Cipher = Cipher.getInstance("SM4/GCM/NoPadding", "GmSSL"); + byte[] key = secureRandom.generateSeed(SM4GCM.KEY_SIZE); + byte[] iv = secureRandom.generateSeed(SM4GCM.DEFAULT_IV_SIZE); + byte[] aad = "Hello: ".getBytes(); + // encryption + sm4Cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "SM4"), new GCMParameterSpec(SM4GCM.MAX_TAG_SIZE,iv)); + sm4Cipher.updateAAD(aad); + byte[] ciphertext = new byte[100]; + int cipherlen = sm4Cipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, 0); + cipherlen += sm4Cipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, cipherlen); + cipherlen += sm4Cipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, cipherlen); + cipherlen += sm4Cipher.doFinal(text.getBytes(), 0, text.getBytes().length, ciphertext, cipherlen); + byte[] ciphertext1 = Arrays.copyOfRange(ciphertext,0,cipherlen); + System.out.println("Ciphertext: " + byteToHex(ciphertext1)); + // decryption + byte[] plaintext = new byte[ciphertext1.length+SM4GCM.MAX_TAG_SIZE]; + sm4Cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "SM4"), new GCMParameterSpec(SM4GCM.MAX_TAG_SIZE,iv)); + sm4Cipher.updateAAD(aad); + int plainlen =sm4Cipher.doFinal(ciphertext1, 0, ciphertext1.length, plaintext, 0); + byte[] plaintext1 = Arrays.copyOfRange(plaintext,0,plainlen); + System.out.println("plaintext: " + new String(plaintext1)); + } + + @Test + public void SM4_GCM_test2() throws Exception { + String text="Hello,GmSSL!"; + SecureRandom secureRandom = SecureRandom.getInstance("Random", "GmSSL"); + Cipher sm4Cipher = Cipher.getInstance("SM4/GCM/NoPadding", "GmSSL"); + byte[] key = secureRandom.generateSeed(SM4GCM.KEY_SIZE); + byte[] iv = secureRandom.generateSeed(SM4GCM.DEFAULT_IV_SIZE); + byte[] aad = "Hello: ".getBytes(); + // encryption + sm4Cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "SM4"), new GCMParameterSpec(SM4GCM.MAX_TAG_SIZE,iv)); + sm4Cipher.updateAAD(aad); + sm4Cipher.updateAAD(aad); + sm4Cipher.update(text.getBytes()); + sm4Cipher.update(text.getBytes()); + sm4Cipher.update(text.getBytes()); + byte[] ciphertext = sm4Cipher.doFinal(); + System.out.println("Ciphertext: " + byteToHex(ciphertext)); + // decryption + sm4Cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "SM4"), new GCMParameterSpec(SM4GCM.MAX_TAG_SIZE,iv)); + sm4Cipher.updateAAD(aad); + sm4Cipher.updateAAD(aad); + sm4Cipher.update(ciphertext); + byte[] plaintext1=sm4Cipher.doFinal(); + System.out.println("plaintext: " + new String(plaintext1)); + } + + @Test + public void SM9_cipher_test() throws Exception{ + String text="Hello, GmSSL"; + SM9EncMasterKeyGenParameterSpec sm9EncMasterKeyGenParameterSpec = new SM9EncMasterKeyGenParameterSpec("bob"); + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("SM9", "GmSSL"); + keyPairGen.initialize(sm9EncMasterKeyGenParameterSpec); + keyPairGen.generateKeyPair(); + // encryption + PublicKey publicKey = keyPairGen.genKeyPair().getPublic(); + // export public key + SM9PublicKey SM9PublicKey = (SM9PublicKey)publicKey; + SM9PublicKey.exportPublicKeyPem("SM9Public.enc.mpk"); + Cipher sm9Cipher = Cipher.getInstance("SM9", "GmSSL"); + sm9Cipher.init(Cipher.ENCRYPT_MODE, publicKey,sm9EncMasterKeyGenParameterSpec); + byte[] ciphertext = sm9Cipher.doFinal(text.getBytes()); + System.out.println("Ciphertext: " + byteToHex(ciphertext)); + // decryption + SM9PrivateKey privateKey= (SM9PrivateKey) keyPairGen.genKeyPair().getPrivate(); + // export private key + privateKey.exportEncryptedPrivateKeyInfoPem("123456", "SM9Private.enc.mpk"); + SM9MasterKey masterKey = (SM9MasterKey)privateKey.getSecretKey(); + SM9UserKey userKey= masterKey.extractKey(sm9EncMasterKeyGenParameterSpec.getId()); + sm9Cipher.init(Cipher.DECRYPT_MODE, userKey.getPrivateKey()); + byte[] plaintext = sm9Cipher.doFinal(ciphertext); + System.out.println("plaintext: " + new String(plaintext)); + } + + @Test + public void SM9_sign_test() throws Exception{ + String text="Hello, GmSSL"; + SM9SignMasterKeyGenParameterSpec sm9SignMasterKeyGenParameterSpec = new SM9SignMasterKeyGenParameterSpec("alice"); + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("SM9", "GmSSL"); + keyPairGen.initialize(sm9SignMasterKeyGenParameterSpec); + keyPairGen.generateKeyPair(); + + Signature signature = Signature.getInstance("SM9", "GmSSL"); + // Signature + SM9PrivateKey privateKey= (SM9PrivateKey) keyPairGen.genKeyPair().getPrivate(); + // export private key + privateKey.exportEncryptedPrivateKeyInfoPem("123456", "SM9Private.sign.mpk"); + SM9MasterKey masterKey = (SM9MasterKey)privateKey.getSecretKey(); + SM9UserKey userKey= masterKey.extractKey(sm9SignMasterKeyGenParameterSpec.getId()); + signature.initSign(userKey.getPrivateKey()); + byte[] signatureText = text.getBytes(); + signature.update(signatureText); + byte[] signatureByte = signature.sign(); + System.out.println("Signature:"+byteToHex(signatureByte)); + // Verify + signature.setParameter(sm9SignMasterKeyGenParameterSpec); + PublicKey publicKey= keyPairGen.genKeyPair().getPublic(); + // export public key + SM9PublicKey SM9PublicKey = (SM9PublicKey)publicKey; + SM9PublicKey.exportPublicKeyPem("SM9Public.sign.mpk"); + signature.initVerify(publicKey); + signature.update(signatureText); + boolean signatureResult = signature.verify(signatureByte); + System.out.println("SignatureResult:"+signatureResult); + } + + @Test + public void ZUC_test() throws Exception{ + String text="Hello,GmSSL!"; + SecureRandom secureRandom = SecureRandom.getInstance("Random", "GmSSL"); + Cipher cipher = Cipher.getInstance("ZUC","GmSSL"); + SecretKey key = new ZucKey(secureRandom.generateSeed(ZucKey.KEY_SIZE)); + IvParameterSpec ivParameterSpec = new IvParameterSpec(secureRandom.generateSeed(ZucCipher.IV_SIZE)); + // encryption + cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec); + byte[] ciphertext = new byte[100]; + int cipherlen = cipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, 0); + cipherlen += cipher.update(text.getBytes(), 0, text.getBytes().length, ciphertext, cipherlen); + cipherlen += cipher.doFinal(text.getBytes(), 0, text.getBytes().length, ciphertext, cipherlen); + byte[] ciphertext1 = Arrays.copyOfRange(ciphertext,0,cipherlen); + System.out.println("Ciphertext: " + byteToHex(ciphertext1)); + // decryption + cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec); + cipher.update(ciphertext1); + byte[] plaintext1 = cipher.doFinal(); + System.out.println("plaintext: " + new String(plaintext1)); + } + + /** + * convert byte array to hex string + * @param btArr + * @return String + */ + private String byteToHex(byte[] btArr) { + BigInteger bigInteger = new BigInteger(1, btArr); + return bigInteger.toString(16); + } + + /** + * convert hex string to byte array + * @param hexString + * @return byte[] + */ + private byte[] hexToByte(String hexString) { + byte[] byteArray = new BigInteger(hexString, 16) + .toByteArray(); + if (byteArray[0] == 0) { + byte[] output = new byte[byteArray.length - 1]; + System.arraycopy( + byteArray, 1, output, + 0, output.length); + return output; + } + return byteArray; + } +} diff --git a/src/test/java/org/gmssl/Sm4EcbTest.java b/src/test/java/org/gmssl/Sm4EcbTest.java index b218c09..3f5c324 100644 --- a/src/test/java/org/gmssl/Sm4EcbTest.java +++ b/src/test/java/org/gmssl/Sm4EcbTest.java @@ -32,7 +32,7 @@ public void beforeTest(){ @Test public void encryptTest(){ String test_plaintext="gmssl"; - byte[] paddingPlaintext=pkcs5padding(test_plaintext.getBytes(),Sm4.BLOCK_SIZE); + byte[] paddingPlaintext=pkcs7padding(test_plaintext.getBytes(),Sm4.BLOCK_SIZE); byte[] encrypted = encrypt(paddingPlaintext,key); //System.out.println("encrypted data:"+HexUtil.byteToHex(encrypted)); Assert.assertNotNull("data is empty exception!",encrypted); @@ -44,7 +44,7 @@ public void decryptTest(){ String test_plaintext="gmssl"; byte[] encrypted =HexUtil.hexToByte(test_hex_chipertext); byte[] plaintextArray = decrypt(encrypted,key); - byte[] unpaddingPlaintextArray = pkcs5Unpadding(plaintextArray); + byte[] unpaddingPlaintextArray = pkcs7UnPadding(plaintextArray); String plaintext=new String(unpaddingPlaintextArray); //System.out.println("chipertext:"+plaintext); Assert.assertEquals("original value is not equal to the expected value after decryption!",plaintext,test_plaintext); @@ -52,40 +52,40 @@ public void decryptTest(){ /** - * The purpose of PKCS5Padding is to pad the data to the block size required by the encryption algorithm, ensuring that the data length meets the requirements of the encryption algorithm. - * In special cases where the data length is already a multiple of the block size, according to the PKCS5 rule, padding is still added at the end. + * The purpose of PKCS7Padding is to pad the data to the block size required by the encryption algorithm, ensuring that the data length meets the requirements of the encryption algorithm. + * In special cases where the data length is already a multiple of the block size, according to the PKCS7 rule, padding is still added at the end. * This is done to ensure consistent handling of padding during encryption and decryption processes. - * @param ciphertextArray + * @param byteArray * @param blockSize - * @return byte[] ciphertext + * @return byte[] padding array */ - private static byte[] pkcs5padding(byte[] ciphertextArray, int blockSize) { - int paddingLength = blockSize - (ciphertextArray.length % blockSize); + private static byte[] pkcs7padding(byte[] byteArray, int blockSize) { + int paddingLength = blockSize - (byteArray.length % blockSize); byte[] padding = new byte[paddingLength]; Arrays.fill(padding, (byte) paddingLength); - byte[] result = new byte[ciphertextArray.length + padding.length]; - System.arraycopy(ciphertextArray, 0, result, 0, ciphertextArray.length); - System.arraycopy(padding, 0, result, ciphertextArray.length, padding.length); + byte[] result = new byte[byteArray.length + padding.length]; + System.arraycopy(byteArray, 0, result, 0, byteArray.length); + System.arraycopy(padding, 0, result, byteArray.length, padding.length); return result; } /** - * unpadding the plaintext - * @param plaintextArray - * @return byte[] plaintext + * unPadding the byteArray + * @param byteArray + * @return byte[] unPadding byteArray * @throws IllegalArgumentException */ - private static byte[] pkcs5Unpadding(byte[] plaintextArray) throws IllegalArgumentException { - int paddingSize = plaintextArray[plaintextArray.length - 1]; - if (paddingSize <= 0 || paddingSize > plaintextArray.length) { - throw new IllegalArgumentException("Invalid pkcs#5 padding!"); + private static byte[] pkcs7UnPadding(byte[] byteArray) throws IllegalArgumentException { + int paddingSize = byteArray[byteArray.length - 1]; + if (paddingSize <= 0 || paddingSize > byteArray.length) { + throw new IllegalArgumentException("Invalid pkcs#7 padding!"); } - for (int i = plaintextArray.length - paddingSize; i < plaintextArray.length; i++) { - if (plaintextArray[i] != paddingSize) { - throw new IllegalArgumentException("Invalid pkcs#5 padding!"); + for (int i = byteArray.length - paddingSize; i < byteArray.length; i++) { + if (byteArray[i] != paddingSize) { + throw new IllegalArgumentException("Invalid pkcs#7 padding!"); } } - return Arrays.copyOfRange(plaintextArray, 0, plaintextArray.length - paddingSize); + return Arrays.copyOfRange(byteArray, 0, byteArray.length - paddingSize); } @@ -113,7 +113,7 @@ private static byte[] encrypt(byte[] data, byte[] key) { private static byte[] decrypt(byte[] data, byte[] key) { byte[] plaintext=new byte[data.length]; Sm4 sm4 = new Sm4(key, false); - for (int i = 0; i < data.length; i += 16) { + for (int i = 0; i < data.length; i += Sm4.BLOCK_SIZE) { sm4.encrypt(data, i, plaintext, i); } return plaintext;