使用java进行SSL证书的签名与签验

时间:2025-03-23 07:42:48

一、签名与签验的含义

签名:客户端使用私钥对字符串加密,得到一个加密后的字符串

签验(签名验证):服务端使用公钥对字符串加密,验证加密后的字符串是否和客户端签名后一样

二、创建私钥、证书等文件

在linux中执行一下命令,生成所需的各个文件

1.手动生成私钥
openssl genrsa -des3 -passout pass:123456 -out  2048

2.生成pkcs8
openssl pkcs8 -topk8 -in  -out  -nocrypt -passin pass:123456

3.生成pkcs10申请书 //C国家,ST省,L市,O组织,OU部门,CN域名或ip
openssl req -new -key  -passin pass:123456 -out  -subj "/C=CN/ST=GUANGZHOU/L=GUANGZHOU/O=A/OU=B/CN=xxx"

4.生成公钥
openssl rsa -in  -passin pass:123456  -pubout -out 

5.生成证书
openssl x509 -req -days 365 -in  -signkey  -passin pass:123456  -out 

6.生成pk12文件
openssl pkcs12 -export -in  -inkey  -passin pass:123456 -passout pass:123456 -out tomcat.p12

7.生成keystore文件
keytool -importkeystore -v -srckeystore tomcat.p12 -srcstoretype pkcs12 -srcstorepass 123456 -destkeystore  -deststoretype jks -deststorepass 123456

以上生成的文件,我们需要用到pkcs8和

 生成后的pkcs8内容如下

-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7Uv7cOJ0oCsKV
Ous6SL9GcYUlfuuZUJfJVggCYU6peL53ZKv/DzP6FSQMO8W+VRFT+nBDpLntsMBd
mnH2hP0Jz/06d8RCJhRQxn2l8/4lqAZwFhyWvd+r4bkp6nfnY4sk0/ymtC2tp31l
WgTjXKIVtMfO9Km0bNxPIV206YdC9G9IIR65NSx8AyOCmwOp3mc028+9HE/OnT5s
mw2mJB5txVUUeVZYcOq0jaNs5Z/cwzOLuaZEcTPxLHoUAbJ0cDncO6j55/8r+zZg
hkois20k4kGVDUSYKeTH2MGKc62PJVyWRYgaaE+MuU+3tGgGW+/QWR/qShpOj/VA
QDSCVM1BAgMBAAECggEBAI8T6Hg6gccY2OD03MBq2jYq9QDbvYYf6Z+tp3Zx7oxB
HnHBIiIx8YhdZ2g0q2giP5b+HYt9IUpsi7GzCsK5dzBsfcWPvwarYS8FPOlpwL/w
Y+Ju8S0uH86AHVbnsOe6v4fEpyCJVK5j1MJ6DGvA0Eh2CXuIoqqdz3RdTt6k1FyN
Wmr5xYwVq/fnG7HECWpLT1RiZZjqqU7YHlJhdRfIHLEFxsuTrBeEcl2iAt6OTYgw
4QIVJ7DQ5n7cGd2nBgodM081dA8zNrJ5833alPI04pLiu7zNM4fewZuS1ThlnG2G
rJl07IeLjZSwz9dX6Ior/xihOkOqrOqf2rQgnXJQaLECgYEA38k6ijgdGizsXWhU
qeo1TY9XzTF7kh6FWFGNsBtlPC+UmOotkF590ApitZbVnVMg1h4ruruM5Z96K/w2
jL2WahwKjTH2pT1VmsPwXra5RgWPQTEH86VD8DQvYGoIxrqaD8zlgCZuCaj6BCNZ
k6jtsV+zsK2lUUNvoVDHzm7yymUCgYEA1kobLTlu469rSE4P8KP768TWE74mQrRw
LxUTKuUnI4jr3QmMLYMDATS6pRbeUKi/KryKuZB3zqZ0qQYuUAnWVOvwjQ0qsm87
+Y9ZivGtggSGP1fpJUnuA0svvWu+u+qjB3AUO+kdXaTKMNjRySYwk2yRjmiGCTRN
N+eeUT79+60CgYEA2j9bGy/MmhmTzykP4MJsh56zh4eptwCTFWY747NkEMVqi5Nf
KnwtATcPu39GB2/qB0hXZ36/07WrpbgMz0eaqRN96uP+YnqlwRE0nHphyHtlkbOq
yKC12E5cog/nTUDxPVWbwVY5XKxyYJTFKK/IWIM4MKe5Ib694Lpyks47dM0CgYAI
89uizg118bQ3txsvYIp71SNke8M5cjRloynD1wMFlwjTmTyEAj8z47IQh54OHJ6v
+GSLUB/RQF8LWLjCm0abEahhHDW2crM8v+JrzaEEF5BMQS36YtZQyOlbro6vZyQc
gXA2+IxDh0jzNXu+PZw17IlAO9mttMOtHD4fAtS1mQKBgQCg3fuw4KyZcw4XaCCN
KlAYJhnQ5Xk79XL6xAjRyKsSG7GfE1V4tvgkHnoxnAAIPeIqxqACi/VhGXSyxH2/
cfHuXtbdcPJ5XfBnloHOZX7PLEB70SIjxy2/L1lPJUh7e7onc9QVj+PB5Wf7j3J+
bqBhY+Rk1pJ9/9Z5zU7eIN/YcA==
-----END PRIVATE KEY-----

内容如下

-----BEGIN CERTIFICATE-----
MIIDMjCCAhoCCQCs0EkSIt7T/jANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJD
TjESMBAGA1UECAwJR1VBTkdaSE9VMRIwEAYDVQQHDAlHVUFOR1pIT1UxCjAIBgNV
BAoMAUExCjAIBgNVBAsMAUIxDDAKBgNVBAMMA3h4eDAeFw0yMjAxMDcwMzI0MzNa
Fw0yMzAxMDcwMzI0MzNaMFsxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlHVUFOR1pI
T1UxEjAQBgNVBAcMCUdVQU5HWkhPVTEKMAgGA1UECgwBQTEKMAgGA1UECwwBQjEM
MAoGA1UEAwwDeHh4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1L+
3DidKArClTrrOki/RnGFJX7rmVCXyVYIAmFOqXi+d2Sr/w8z+hUkDDvFvlURU/pw
Q6S57bDAXZpx9oT9Cc/9OnfEQiYUUMZ9pfP+JagGcBYclr3fq+G5Kep352OLJNP8
prQtrad9ZVoE41yiFbTHzvSptGzcTyFdtOmHQvRvSCEeuTUsfAMjgpsDqd5nNNvP
vRxPzp0+bJsNpiQebcVVFHlWWHDqtI2jbOWf3MMzi7mmRHEz8Sx6FAGydHA53Duo
+ef/K/s2YIZKIrNtJOJBlQ1EmCnkx9jBinOtjyVclkWIGmhPjLlPt7RoBlvv0Fkf
6koaTo/1QEA0glTNQQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBk3o9BIggMhKdX
+wEIJ3piMnwXudLC2rUs2gP4EUaW/JuOotxVB+fRGNEBeHWyx8EupKEna5ho0IuN
WoqlFKyd7luqjUItbNyQ6IWoG8+bemhXWC6zg2jrPpHvwbhb9625Oo28XgNA5lsl
Lf+ybF7wpW7gMzLVK3z+vzOnvaunbjtASde7bLmL25qfm1K08iBQUOk0+EUCXY4q
efkRrVTh1aWwVSN51QhM3lMn0wGkPl2ww34zik5dA5RzXMlrOmHoGxzmd8oAJf7g
lJIQPrFyMv+mSo54TECkWSJeRAZNa+KZ0DKskcBPgE4SDZTlkCrZa3kEganTkE5F
/SO9kc0D
-----END CERTIFICATE-----

三、在Java代码中读取这些文件,然后调用对应类进行签名和签验

public class SslUtil {

    /**
     * 签验,返回签验是否通过
     * @param certificate   证书
     * @param signStr       签名的字符串
     * @param unSignStr     原字符串
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public static boolean verifySign(X509Certificate certificate, String signStr, String unSignStr)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        byte[] data = ().decode(signStr);
        Signature signature = (());
        (());
        ((StandardCharsets.UTF_8));
        return (data);
    }

    /**
     * 使用私钥对字符串进行签名,返回Base64编码后的字符串
     * @param str   原始字符串
     * @param privateKey    私钥
     * @param certificate   公钥
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public static String signString(String str, PrivateKey privateKey, X509Certificate certificate)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        Signature signature = (());
        (privateKey);
        ((StandardCharsets.UTF_8));
        byte[] data = ();
        return ().encodeToString(data);
    }

    /**
     * 读取证书文件
     * @param certFile
     * @return
     * @throws CertificateException
     */
    public static X509Certificate getCertificate(String certFile) throws CertificateException, IOException {
        byte[] cert = readCertFile(certFile);
        CertificateFactory factory = ("X.509");
        InputStream inputStream = new ByteArrayInputStream(cert);
        X509Certificate certificate = (X509Certificate) (inputStream);
        return certificate;
    }

    /**
     * 读取私钥文件
     * @param pkcs8File
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PrivateKey getPrivateKey(String pkcs8File)
            throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        byte[] prikey = readPkcs8File(pkcs8File);
        KeyFactory keyFactory = ("RSA");
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(prikey);
        PrivateKey privateKey = (keySpec);
        return privateKey;
    }

    /**
     * 读取私钥文件,返回字节数组
     * 去掉开头结尾注释行,保留中间的数据
     * @param pkcs8File
     * @return
     * @throws IOException
     */
    private static byte[] readPkcs8File(String pkcs8File) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        try(BufferedReader br = new BufferedReader(new FileReader(pkcs8File))){
            String line;
            while((line=())!=null){
                if(("-")){
                    continue;
                }
                (line);
            }
            return ().decode(());
        }
    }

    /**
     * 读取Cert证书文件,返回字节数组
     * 把命令生成的证书文件全部读取到内存
     * @param certFile
     * @return
     * @throws IOException
     */
    private static  byte[] readCertFile(String certFile) throws IOException{
        try(InputStream is = new FileInputStream(certFile)){
            return ();
        }
    }
}

调用方法验证,最后会返还true

public class Main {
    public static void main(String[] args) {
        String pkcs8File = "";
        String certFile = "";
        String str = "这是原文字符串";
        try{
            PrivateKey privateKey = (pkcs8File);
            X509Certificate certificate = (certFile);
            String signStr = (str, privateKey, certificate);
            ("字符串 ["+str+"] ---签名--->["+signStr+"]");

            boolean result = (certificate, signStr, str);
            ("签验结果:"+result);
        }catch (Exception e){
            ();
        }
    }
}

四、不使用java来签名,而是使用openssl命令来生成签名,然后使用java来签验

openssl签名的命令

1.创建需要签名的文件
echo "这是原文字符串" > 

2.对文件进行签名
openssl dgst -sha256 -passin pass:123456 -sign   > 

3.对签名后的文件进行base64编码
base64  > 

/**
上面需要注意的是,不能使用openssl rsautl来生成签名
因为rsault生成的签名结果,只包含字符串body,而使用dgst生成的签名结果,是一个结构体,包含了结构体的信息(类型、长度、body)
java的Signature类解析会按照结构体进行解析,如果去解析rsault生成签名就会因为结构不对
报错: Decryption error
*/

命令生成的内容如下

BTG52NfjVx6v7yHq4OzCbuzeA4bvlWSPHI4N4yHSDzErmB8nWsQsJ97fKOTbeH7pdLzTuO2RS832
3wbEBo+FFTyOgUENa0J+I22SVVmCAzuPGzATRdvk6rM7kTbeMJZSIiBq/v4hX6btvAOuJ0f0/ZG5
m1bbbr3SZe26DiNl7TKgWpMhFW2OyRmaTq6fPfhOx+1/zWHFiMWrVl5WO3vJpRt2We3/wRwNzgBj
e6M+BlcQvND+gSS//ix/6avvAaNjudXU8r8/T0MKMAsuRk0zPbOjqbNudZrN4aDRf3GUkYiFzp86
5F9kkyqhapISVnpNWDEpSQz14cyoDAFUcd0yDw==

然后Java中直接拿到这个字符串进行签验

public class Main {
    public static void main(String[] args) {
        String pkcs8File = "";
        String certFile = "";
        //注意,文件方式加密的字符串,后面多了一个回车符号
        String str = "这是原文字符串\n";
        try{
            X509Certificate certificate = (certFile);

            String signStr = "BTG52NfjVx6v7yHq4OzCbuzeA4bvlWSPHI4N4yHSDzErmB8nWsQsJ97fKOTbeH7pdLzTuO2RS8323wbEBo+FFTyOgUENa0J+I22SVVmCAzuPGzATRdvk6rM7kTbeMJZSIiBq/v4hX6btvAOuJ0f0/ZG5m1bbbr3SZe26DiNl7TKgWpMhFW2OyRmaTq6fPfhOx+1/zWHFiMWrVl5WO3vJpRt2We3/wRwNzgBje6M+BlcQvND+gSS//ix/6avvAaNjudXU8r8/T0MKMAsuRk0zPbOjqbNudZrN4aDRf3GUkYiFzp865F9kkyqhapISVnpNWDEpSQz14cyoDAFUcd0yDw==";

            boolean result = (certificate, signStr, str);
            ("签验结果:"+result);
        }catch (Exception e){
            ();
        }
    }
}