Merge branch 'main' into develop

This commit is contained in:
siujamo
2025-04-24 15:21:16 +08:00
8 changed files with 258 additions and 69 deletions
+58 -9
View File
@@ -43,22 +43,71 @@ ZyYNcH60ONRWjMqlQXozWMb2i7WphKxf8kopp42nzCflWQod+JQY+hM/EQ==
-----END PUBLIC KEY-----
```
#### Convert private key to EC formats which could be acceptable by Java
## RSA-based algorithm
Java's `PKCS8EncodedKeySpec` requires the private key to be in PKCS#8 format, while OpenSSL by
default generates private keys in traditional PEM format. To convert the private key, run the
following command:
### Generate key pair
#### Generate private key
Generate a private key by `genpkey` command provided by OpenSSL:
```shell
openssl pkcs8 -topk8 -inform PEM -outform PEM -in ec_private_key.pem -out ec_private_key_pkcs8.pem -nocrypt
openssl genpkey -algorithm RSA -out rsa_private_key.pem -pkeyopt rsa_keygen_bits:2048
```
The converted private key will look like this:
The output of this command is a file called `rsa_private_key.pem` and its content looks like the
following:
```text
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7
+PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn
Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD4VIFYJFMAs15j
J3V3IicHd7sI2TIFqTZME40zlOlVAlPKLZmTQvZFLNgaUAAsvPi5i1DR2ywwK6Al
BfnwVnzvmDXC5mKHOz4oxOQVA6Nlp2yVaQMzidmfYNSkMtcv/4HRPsatc7K/M5l6
pCP20DVRjkikBdIy8e9w+x6BrIFp5Q8PZc/X2BGNAUMMYACdeYH5R/A0CxqkND13
esc4gkynMOrvZrZGHCz51usfSCqyDWWwsN+GG6LYWia4GkNlS0erQnP8gS93dfjl
e96BIfy3z7Iv+kUrf5ikNW2P8jMxLAv6LO+dcUAu9k477wIAF7Iq5KMuH/otsDOu
+h+2qXmBAgMBAAECggEAdRqcmC0g+y6arxV3fkObthjPGYAa57KBCWUa7B0n30+m
pavVRS2Jpttb2SSqwG4ouI6rARti/iBEd9EWqTCP4AieKZetFOpqCJ24lPRPRGus
d9S6jr5N4qut+vSCp37NABijZj4uJ540nTH0R7qtuhTnynl4Q0/1wwiYvTvVF1Lg
dn+I/8aRbshwDhdAOWOUe6GL7/eaCYgN8/UmlKIpp8tg0w2iWxbaFiR7gZiM41LA
M6SXXfcCas+ZVXsGbzQ3SNiVurCGuuRNcCScXS3/WoEDIb3cNtp49iOmQS+nmEoo
wh4uiEd+0+BrzxngS4o5+mKnHJnwgY0+veGVYLMR5QKBgQD9WKQmevMDU5c+NPq9
8jaR457Fuxq1gwzeFNJdWfOc/K2LEWh+nFNFCb++EboEj6FdxWaWNMxbrmJps5gs
EoBUYy/Tl7UycDqDfiYLmDdTsf2pVjjh9jaIADiLcJ8S6wwJMZKub7Tp8UVkenAl
535MqShLUC11Y7VxLb3Tsll4XwKBgQD67mm6iCmshr/eszPfNE3ylZ+PiNa7nat7
N7lQzBIiRJflT1kmVidC5gE+jASqH728ChkZZKxbHsjxpmWdAhLOITdXoTB4sDsd
wtV1lxkXxK9FnrpFvO3y1wZ/QsD3Z2KXxHYZqawkUETO9F3nqAXW0b2GDar5Qiyo
J3Tx/43aHwKBgDC0NMJtCoDONhowZy/S+6iqQKC0qprQec3L5PErVMkOTnKYwyTr
+pogGKt6ju9HiXcUdvdTaSIK8UJu00dNuzv94XjlBmGO78DNpJTAC4rcge5m9AKE
qdEVcclkukARzbuKuy8rrHT4/CUn4J141m/4aRWpcUPLCluato6XD9ozAoGBANvf
JhOFFgcPd3YazfvpZ9eE1XA+tfFlYYmxNRcgCU+vjO0oDvSxjutmgHae18N91pG6
w21lskSRf/+GDwl5dKLbphOJsOA/gz07qDDGOf2CoRW+1Hcg6drcINxH0K+4DkLv
qZApBSY4k2JH6zR+HMeztn6M4WBRZLHfCPC3PUN/AoGAA3AoHbLTZvqMIKSDkP4Y
U/tTsSFDY4aYo7LG/jk8af3oPU3KyGh4ZFBd6aMmXbS8f8FjvmrM+/e+y9OOGAlq
iOl0hYrs5cJSMLW6i4KnJYuYbMkgmk3bN2t9apu64xKR94gbPrI6AGnPZp+iIzp0
hXKe4HcuhQ3G0a2hjayiQ84=
-----END PRIVATE KEY-----
```
#### Generate public key by private key
Export public key from private key by OpenSSL:
```shell
openssl pkey -in rsa_private_key.pem -pubout -out rsa_public_key.pem
```
The output of this command is a file called `rsa_public_key.pem` and its content looks like the
following:
```text
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+FSBWCRTALNeYyd1dyIn
B3e7CNkyBak2TBONM5TpVQJTyi2Zk0L2RSzYGlAALLz4uYtQ0dssMCugJQX58FZ8
75g1wuZihzs+KMTkFQOjZadslWkDM4nZn2DUpDLXL/+B0T7GrXOyvzOZeqQj9tA1
UY5IpAXSMvHvcPsegayBaeUPD2XP19gRjQFDDGAAnXmB+UfwNAsapDQ9d3rHOIJM
pzDq72a2Rhws+dbrH0gqsg1lsLDfhhui2FomuBpDZUtHq0Jz/IEvd3X45XvegSH8
t8+yL/pFK3+YpDVtj/IzMSwL+izvnXFALvZOO+8CABeyKuSjLh/6LbAzrvoftql5
gQIDAQAB
-----END PUBLIC KEY-----
```
@@ -49,4 +49,29 @@ public interface KeyLoader {
*/
PublicKey loadPublicKey(String pemKeyText);
/**
* Retrieves the raw content of a PEM formatted key by removing unnecessary headers, footers,
* and new line characters.
*
* <p>
* This method processes the provided PEM key text to return a cleaned string that contains
* only the key content. The method strips away the
* {@code "-----BEGIN (EC )?(PRIVATE|PUBLIC) KEY-----"} and
* {@code "-----END (EC )?(PRIVATE|PUBLIC) KEY-----"} lines, as well as any new line characters,
* resulting in a continuous string representation of the key, which can be used for further
* cryptographic operations.
*
* @param pemKeyText the PEM formatted key as a string, which may include headers, footers and
* line breaks
* @return a string containing the raw key content devoid of any unnecessary formatting
* or whitespace
*/
default String getRawContent(String pemKeyText) {
// remove all unnecessary parts of the pem key text
return pemKeyText
.replaceAll("-----BEGIN (EC )?(PRIVATE|PUBLIC) KEY-----", "")
.replaceAll("-----END (EC )?(PRIVATE|PUBLIC) KEY-----", "")
.replaceAll("\n", "");
}
}
@@ -82,11 +82,7 @@ public class EcKeyLoader implements KeyLoader {
@Override
public ECPrivateKey loadPrivateKey(String pemKeyText) {
try {
// remove all unnecessary parts of the pem key text
pemKeyText = pemKeyText
.replaceAll("-----BEGIN (EC )?PRIVATE KEY-----", "")
.replaceAll("-----END (EC )?PRIVATE KEY-----", "")
.replaceAll("\n", "");
pemKeyText = getRawContent(pemKeyText);
var decodedKeyString = decoder.decode(pemKeyText);
var keySpec = new PKCS8EncodedKeySpec(decodedKeyString);
@@ -112,11 +108,7 @@ public class EcKeyLoader implements KeyLoader {
@Override
public ECPublicKey loadPublicKey(String pemKeyText) {
try {
// remove all unnecessary parts of the pem key text
pemKeyText = pemKeyText
.replaceAll("-----BEGIN (EC )?PUBLIC KEY-----", "")
.replaceAll("-----END (EC )?PUBLIC KEY-----", "")
.replaceAll("\n", "");
pemKeyText = getRawContent(pemKeyText);
var keyBytes = decoder.decode(pemKeyText);
var spec = new X509EncodedKeySpec(keyBytes);
var key = keyFactory.generatePublic(spec);
@@ -0,0 +1,136 @@
/*
* Copyright (C) 2024-2025 OnixByte.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.onixbyte.security.impl;
import com.onixbyte.security.KeyLoader;
import com.onixbyte.security.exception.KeyLoadingException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* A class responsible for loading RSA keys from PEM formatted text.
* <p>
* This class implements the {@link KeyLoader} interface and provides methods to load both private
* and public RSA keys. The keys are expected to be in the standard PEM format, which includes
* Base64-encoded key content surrounded by header and footer lines. The class handles the decoding
* of Base64 content and the generation of keys using the RSA key factory.
* <p>
* Any exceptions encountered during the loading process are encapsulated in a
* {@link KeyLoadingException}, allowing for flexible error handling.
*
* @author siujamo
* @see KeyLoader
* @see KeyLoadingException
*/
public class RsaKeyLoader implements KeyLoader {
private final Base64.Decoder decoder;
private final KeyFactory keyFactory;
/**
* Constructs an instance of {@code RsaKeyLoader}.
* <p>
* This constructor initialises the Base64 decoder and the RSA {@link KeyFactory}. It may throw
* a {@link KeyLoadingException} if the RSA algorithm is not available.
*/
public RsaKeyLoader() {
try {
this.decoder = Base64.getDecoder();
this.keyFactory = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new KeyLoadingException(e);
}
}
/**
* Loads an RSA private key from a given PEM formatted key text.
* <p>
* This method extracts the raw key content from the provided PEM text, decodes the
* Base64-encoded content, and generates an instance of {@link RSAPrivateKey}. If the key cannot
* be loaded due to invalid specifications or types, a {@link KeyLoadingException} is thrown.
*
* @param pemKeyText the PEM formatted private key text
* @return an instance of {@link RSAPrivateKey}
* @throws KeyLoadingException if the key loading process encounters an error
*/
@Override
public RSAPrivateKey loadPrivateKey(String pemKeyText) {
// Extract the raw key content
var rawKeyContent = getRawContent(pemKeyText);
// Decode the Base64-encoded content
var keyBytes = decoder.decode(rawKeyContent);
// Create a PKCS8EncodedKeySpec from the decoded bytes
var keySpec = new PKCS8EncodedKeySpec(keyBytes);
try {
// Get an RSA KeyFactory and generate the private key
var _key = keyFactory.generatePrivate(keySpec);
if (_key instanceof RSAPrivateKey key) {
return key;
} else {
throw new KeyLoadingException("Unable to load private key from pem-formatted key text.");
}
} catch (InvalidKeySpecException e) {
throw new KeyLoadingException("Key spec is invalid.", e);
}
}
/**
* Loads an RSA public key from a given PEM formatted key text.
* <p>
* This method extracts the raw key content from the provided PEM text, decodes the
* Base64-encoded content, and generates an instance of {@link RSAPublicKey}. If the key cannot
* be loaded due to invalid specifications or types, a {@link KeyLoadingException} is thrown.
*
* @param pemKeyText the PEM formatted public key text
* @return an instance of {@link RSAPublicKey}
* @throws KeyLoadingException if the key loading process encounters an error
*/
@Override
public RSAPublicKey loadPublicKey(String pemKeyText) {
// Extract the raw key content
var rawKeyContent = getRawContent(pemKeyText);
// Decode the Base64-encoded content
var keyBytes = decoder.decode(rawKeyContent);
// Create an X509EncodedKeySpec from the decoded bytes
var keySpec = new X509EncodedKeySpec(keyBytes);
// Get an RSA KeyFactory and generate the public key
try {
var _key = keyFactory.generatePublic(keySpec);
if (_key instanceof RSAPublicKey key) {
return key;
} else {
throw new KeyLoadingException("Unable to load public key from pem-formatted key text.");
}
} catch (InvalidKeySpecException e) {
throw new KeyLoadingException("Key spec is invalid.", e);
}
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2024-2024 OnixByte.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.onixbyte.security;
import com.onixbyte.security.impl.EcKeyLoader;
import org.junit.jupiter.api.Test;
public class KeyPairLoaderTest {
@Test
public void test() {
var keyLoader = new EcKeyLoader();
// The following key pair is only used for test only, and is already exposed to public.
// DO NOT USE THEM FOR PRODUCTION!
var privateKey = keyLoader.loadPrivateKey("""
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7
+PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn
Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R
-----END PRIVATE KEY-----
""");
var publicKey = keyLoader.loadPublicKey("""
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZAOxkWNouIIPKsdF1YgF1D/XD8Pa
ZyYNcH60ONRWjMqlQXozWMb2i7WphKxf8kopp42nzCflWQod+JQY+hM/EQ==
-----END PUBLIC KEY-----
""");
}
}
@@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgs79JlARgXEf6EDV7
+PHQCTHEMtqIoHOy1GZ1+ynQJ6yhRANCAARkA7GRY2i4gg8qx0XViAXUP9cPw9pn
Jg1wfrQ41FaMyqVBejNYxvaLtamErF/ySimnjafMJ+VZCh34lBj6Ez8R
-----END PRIVATE KEY-----
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD4VIFYJFMAs15j
J3V3IicHd7sI2TIFqTZME40zlOlVAlPKLZmTQvZFLNgaUAAsvPi5i1DR2ywwK6Al
BfnwVnzvmDXC5mKHOz4oxOQVA6Nlp2yVaQMzidmfYNSkMtcv/4HRPsatc7K/M5l6
pCP20DVRjkikBdIy8e9w+x6BrIFp5Q8PZc/X2BGNAUMMYACdeYH5R/A0CxqkND13
esc4gkynMOrvZrZGHCz51usfSCqyDWWwsN+GG6LYWia4GkNlS0erQnP8gS93dfjl
e96BIfy3z7Iv+kUrf5ikNW2P8jMxLAv6LO+dcUAu9k477wIAF7Iq5KMuH/otsDOu
+h+2qXmBAgMBAAECggEAdRqcmC0g+y6arxV3fkObthjPGYAa57KBCWUa7B0n30+m
pavVRS2Jpttb2SSqwG4ouI6rARti/iBEd9EWqTCP4AieKZetFOpqCJ24lPRPRGus
d9S6jr5N4qut+vSCp37NABijZj4uJ540nTH0R7qtuhTnynl4Q0/1wwiYvTvVF1Lg
dn+I/8aRbshwDhdAOWOUe6GL7/eaCYgN8/UmlKIpp8tg0w2iWxbaFiR7gZiM41LA
M6SXXfcCas+ZVXsGbzQ3SNiVurCGuuRNcCScXS3/WoEDIb3cNtp49iOmQS+nmEoo
wh4uiEd+0+BrzxngS4o5+mKnHJnwgY0+veGVYLMR5QKBgQD9WKQmevMDU5c+NPq9
8jaR457Fuxq1gwzeFNJdWfOc/K2LEWh+nFNFCb++EboEj6FdxWaWNMxbrmJps5gs
EoBUYy/Tl7UycDqDfiYLmDdTsf2pVjjh9jaIADiLcJ8S6wwJMZKub7Tp8UVkenAl
535MqShLUC11Y7VxLb3Tsll4XwKBgQD67mm6iCmshr/eszPfNE3ylZ+PiNa7nat7
N7lQzBIiRJflT1kmVidC5gE+jASqH728ChkZZKxbHsjxpmWdAhLOITdXoTB4sDsd
wtV1lxkXxK9FnrpFvO3y1wZ/QsD3Z2KXxHYZqawkUETO9F3nqAXW0b2GDar5Qiyo
J3Tx/43aHwKBgDC0NMJtCoDONhowZy/S+6iqQKC0qprQec3L5PErVMkOTnKYwyTr
+pogGKt6ju9HiXcUdvdTaSIK8UJu00dNuzv94XjlBmGO78DNpJTAC4rcge5m9AKE
qdEVcclkukARzbuKuy8rrHT4/CUn4J141m/4aRWpcUPLCluato6XD9ozAoGBANvf
JhOFFgcPd3YazfvpZ9eE1XA+tfFlYYmxNRcgCU+vjO0oDvSxjutmgHae18N91pG6
w21lskSRf/+GDwl5dKLbphOJsOA/gz07qDDGOf2CoRW+1Hcg6drcINxH0K+4DkLv
qZApBSY4k2JH6zR+HMeztn6M4WBRZLHfCPC3PUN/AoGAA3AoHbLTZvqMIKSDkP4Y
U/tTsSFDY4aYo7LG/jk8af3oPU3KyGh4ZFBd6aMmXbS8f8FjvmrM+/e+y9OOGAlq
iOl0hYrs5cJSMLW6i4KnJYuYbMkgmk3bN2t9apu64xKR94gbPrI6AGnPZp+iIzp0
hXKe4HcuhQ3G0a2hjayiQ84=
-----END PRIVATE KEY-----
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+FSBWCRTALNeYyd1dyIn
B3e7CNkyBak2TBONM5TpVQJTyi2Zk0L2RSzYGlAALLz4uYtQ0dssMCugJQX58FZ8
75g1wuZihzs+KMTkFQOjZadslWkDM4nZn2DUpDLXL/+B0T7GrXOyvzOZeqQj9tA1
UY5IpAXSMvHvcPsegayBaeUPD2XP19gRjQFDDGAAnXmB+UfwNAsapDQ9d3rHOIJM
pzDq72a2Rhws+dbrH0gqsg1lsLDfhhui2FomuBpDZUtHq0Jz/IEvd3X45XvegSH8
t8+yL/pFK3+YpDVtj/IzMSwL+izvnXFALvZOO+8CABeyKuSjLh/6LbAzrvoftql5
gQIDAQAB
-----END PUBLIC KEY-----