ShangMi is a set of cryptographic techniques and tools in commercial or business applications, including the encryption of sensitive business data, secure communication channels, digital signatures for document verification, and even more.
Generally, the below ShangMi cryptographic algorithms and secure communication protocols are widely applied.
Tencent Kona JDK extends Java Cryptography Architecture (JCA) and Java Cryptography Extension (JCE) providers, exactly SUN, SunEC and SunJCE, to support ShangMi cryptographic algorithms, and extends Java Secure Socket Extension (JSSE) provider, exactly SunJSSE, to support ShangMi secure communication protocols.
The algorithms are implemented by the different security providers in the JDK. The followings list the security providers and the corresponding supported ShangMi algorithms and protocols.
Since Tencent Kona JDK just uses the existing OpenJDK security providers to accommodate the ShangMi stuff, and these providers are already installed with the highest priorities, so the application developers can use these out-of-the-box features as those JDK built-in algorithms (e.g. EC and AES) and protocols (e.g. TLS 1.3).
The following sections will unveil the details on using the above algorithms and protocols.
Generating SM2 key pair is the same as generating the key pairs on other EC curves. It just needs to invoke the standard JDK APIs. The JDK builtin ECKeyPairGenerator. In the key pair generated by this implementation, the format of private key is PKCS#8 and the format of public key is X.509.
Create KeyPairGenerator implementation for EC algorithm.
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC);
keyPairGenerator.initialize(spec);
spec
can be SM2ParameterSpec
(use SM2ParameterSpec.instance()
to create the instance) or ECGenParameterSpec
(use new ECGenParameterSpec("curveSM2")
to create the instance).
Generate key pair.
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
SM2 key pair is also EC key pair, so the public key and private key are also ECPublicKey and ECPrivateKey respectively.
For more information on key pair generation, please refer to the official KeyPairGenerator docs.
Generally, in the signing and encrypting operations, the key pairs are already generated. They are not generated on the runtime. It just reads the existing public key and private key to create PublicKey and PrivateKey instances respectively.
byte[] encodedPublicKey = <public key>;
byte[] encodedPrivateKey = <private key>;
KeyFactory keyFactory = KeyFactory.getInstance("SM2");
SM2PublicKeySpec publicKeySpec = new SM2PublicKeySpec(encodedPublicKey);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
SM2PrivateKeySpec privateKeySpec = new SM2PrivateKeySpec(encodedPrivateKey);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
Using SM2 signature is the same as using other existing signatures, like ECDSA, though the parameters are different.
Create Signature instance.
Signature signature = Signature.getInstance("SM3withSM2);
Initialize the Signature instance with the private key for signing.
signature.initSign(privateKey);
Behind the above initialization, it uses the default SM2 ID, exactly 1234567812345678
. It also derives the public key from the private key. According to the associated specification, public key must be used for generating SM2 signature data. This is a significant difference between the international algorithms, like ECDSA, and SM2 signature algorithm. Note that calculating the public key leads to some overhead. That is harmful for the performance.
If not using the default ID or expect to eliminate the cost on calculating public key, it would provide an AlgorithmParameterSpec, exactly SM2SignatureParameterSpec, instance.
byte[] altID = <custom ID>;
ECPublicKey publicKey = <public key>;
SM2SignatureParameterSpec paramSpec = new SM2SignatureParameterSpec(altID, publicKey);
signature.setParameter(paramSpec);
signature.initSign(privateKey);
After parameter setup and initialization, it is time to pass the message data in.
byte[] message = <the message>;
signature.update(message);
Generate signature data.
byte[] sign = signature.sign();
Initialize the Signature instance with public key for verifying.
signature.initVerify(publicKey);
Pass the message in.
byte[] message = <the message>;
signature.update(message);
Pass the signature data in.
boolean verified = signature.verify(sign);
If the verification is successful, the result is true, otherwise false.
It has to know that the private key is used for signing, however the public key is used for verifying. For detailed information about Signature APIs, please refer to the official Signature docs.
Because of the performance concern, public key encryption is only used for encrypting short but critical data. The same to SM2 encryption.
Create Cipher instance.
Cipher cipher = Cipher.getInstance("SM2");
Initialize the Cipher instance with public key and set the mode to encryption.
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
Pass the message in and generate the ciphertext.
byte[] message = <the message>;
byte[] ciphertext = cipher.doFinal(message);
Initialize the Cipher instance with private key and set the mode to decryption.
cipher.init(Cipher.DECRYPT_MODE, privateKey);
Pass the ciphertext in and generate the plaintext.
byte[] cleartext = cipher.doFinal(ciphertext);
It has to know that public key is used for encryption, however the private key is used for decryption. For detailed information on Cipher APIs, please refer to the official Cipher docs.
Using SM3 hash algorithm is the same as using other existing hash algorithm, like SHA-256. It only invokes JDK APIs to generate the message digest (hash).
Create MessageDigest instance.
MessageDigest md = MessageDigest.getInstance("SM3");
It can pass all the message in and generate the digest.
byte[] message = <the message>;
byte[] digest = md.digest(message);
It also can pass the message segments, and finally generate the digest.
byte[] messageSegment1 = <the message segment1>;
byte[] messageSegment2 = <the message segment2>;
// Pass the message segments in
md.update(messageSegment1);
md.update(messageSegment2);
// Finally generate the digest
byte[] digest = md.digest();
For detailed information about MessageDigest APIs, please refer to the official MessageDigest docs.
Using HmacSM3 algorithm is the same as using other existing MAC algorithm, like HmacSHA256. It only invokes JDK APIs to generate the message authentication code.
Prepare a 16-bytes key.
byte[] key = <the key>;
SecretKey secretKey = new SecretKeySpec(key, "SM4");
Create Mac instance.
Mac hmac = Mac.getInstance("HmacSM3");
Initialize the Mac instance with the key.
hmac.init(secretKey);
Pass all the message in and generate message authentication code. The code is 32-bytes length.
byte[] message = <the message>;
byte[] mac = hmac.doFinal(message);
It also can pass the message segments, and finally generate the message authentication code.
byte[] messageSegment1 = <the message segment1>;
byte[] messageSegment2 = <the message segment2>;
// Pass the message segments in
hmac.update(messageSegment1);
hmac.update(messageSegment2);
// Finally generate the message authentication code
byte[] mac = hmac.doFinal();
For the detailed information about Mac APIs, please refer to the official Mac docs.
Using SM4 cipher algorithm is the same as using other existing block cipher algorithm, like AES. It only invokes JDK APIs to do encryption and decryption. Tencent Kona JDK supports four operation modes, including CBC, CTR, ECB and GCM. It also supports PKCS#7 padding specification.
Prepare a 16-bytes key.
byte[] key = <the key>;
SecretKey secretKey = new SecretKeySpec(key, "SM4");
Create Cipher instance.
Cipher cipher = Cipher.getInstance(transformation);
The transformation consists of algorithm name, operation mode and padding type. A symbol /
is used as separator between the parts.
The following transformations are supported:
Create the algorithm parameters.
AlgorithmParameterSpec paramSpec = <the parameter parameter instance>;
Different operation modes may need different AlgorithmParameterSpec types,
Initialize the Cipher instance with the key and parameter type and set the mode to encryption.
cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
Pass all the message in and generate the ciphertext.
byte[] ciphertext = cipher.doFinal(message);
Initialize the Cipher instance with the key and parameter type and set the mode to decryption.
cipher.init(Cipher.DECRYPT_MODE, secretKey);
Pass all the ciphertext in and generate the plaintext.
byte[] cleartext = cipher.doFinal(ciphertext);
It also can receive the plaintext/ciphertext segments in, and then generate the ciphertext/plaintext segments.
byte[] input1 = <segment1>;
byte[] input2 = <segment2>;
// Pass the plaintext/ciphertext segments in
byte[] output1 = cipher.update(input1);
byte[] output2 = cipher.update(input2);
// Generate the ciphertext/plaintext segments
byte[] outputFinal = cipher.doFinal();
For the detailed information about Cipher APIs, please refer to the official [Cipher] docs.
The so-called ShangMi certificate also complies to X.509 specification, though the public key algorithm is EC with SM2 curve and the signature algorithm is SM3withSM2.
String certPEM = <certificate in PEM format>;
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) cf.generateCertificate(
new ByteArrayInputStream(certPEM.getBytes(StandardCharsets.UTF_8));
For more information on certificate parsing, please read the official reference on CertificateFactory.
It can parse public key from the ShangMi certificate.
PublicKey publicKey = certificate.getPublicKey();
publicKey can be cast to ECPublicKey.
It can parse private key from PKCS#8 format.
String peyPEM = <private key in PKCS#8 format>;
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
Base64.getMimeDecoder().decode(keyPem));
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
privateKey can be cast to ECPrivateKey.
It can sign data with PKCS#8 private key and verify the signature with X.509 certificate.
Create Signature instance and use SM3withSM2 signature algorithm.
Signature signature = Signature.getInstance("SM3withSM2");
Sign the message with the private key and generate the signature data.
signature.initSign(privateKey);
signature.update(message);
byte[] sign = signature.sign();
Verify the signature data with the certificate and determine whether the verification passes or fails.
signature.initVerify(certificate);
signature.update(message);
boolean verified = signature.verify(sign);
The certificate has to contain digitalSignature key usage for verifying signature data. But it also can verify the signature with the public key directly.
signature.initVerify(publicKey);
signature.update(message);
boolean verified = signature.verify(sign);
In order to take advantage of the implementations on TLCP 1.1 and TLS 1.3/RFC 8998, the most important point is taking SSLSocket or SSLEngine to apply the SSLContext implementation that supports the protocols.
KeyStore trustStore = <a trust store carring the CAs>;
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(trustStore);
KeyStore keyStore = <a key store carring the end entity certificates>;
KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509");
kmf.init(keyStore, keyStorePassword);
SSLContext context = SSLContext.getInstance("TLCPv1.1");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
When create SSLContext instances, it allows to specify the following context protocols:
Please read the official JSSE reference for understanding it deeply.