系列中的上一篇
当前教程
Java 中的数字签名和证书基础

Java 中的数字签名和证书基础

几乎可以访问任何在线内容并能够彼此互动已成为我们日常生活的一部分。但是,生活在在线世界意味着您必须质疑数据的合法性:每封电子邮件是否值得信赖?当您登录到您最喜欢的网站时,您如何知道它可靠?您要安装的软件更新是真实的,还是伪装成病毒,等待感染您的设备?

在本文中,我们将更深入地了解如何在 Java 代码中验证数字签名和检查证书。  

使用 Java 实现数字签名

如果您需要一种验证机制,则应使用数字签名

  • 真实性:消息作者的身份是准确的。
  • 完整性:消息在传输过程中未被更改。
  • 不可否认性:已发送和接收传输的消息,并且发送和接收方声称已发送和接收了该消息。

要使用数字签名发送消息,您首先需要使用算法生成非对称密钥对,例如 Ed25519

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Ed25519);
KeyPair keypair = keyPairGenerator.generateKeyPair();

接下来,您应该将消息输入签名对象并调用 sign 方法

public static byte[] generateDigitalSignature(byte[] plainText, PrivateKey privateKey) throws Exception  {
    Signature signature = Signature.getInstance("Ed25519");
    signature.initSign(privateKey);
    signature.update(plainText);
    return signature.sign();
}

generateDigitalSignature 方法中,您通过传递签名算法 Ed25519(一种使用 EdDSACurve25519 的椭圆曲线签名算法)来获取 Signature 对象的实例。然后,您使用私钥初始化签名,传递消息,并通过将其存储为字节数组来完成签名操作。

要验证签名,您再次创建一个 Signature 实例,这次传递文本消息和公钥,就像在 verifyDigitalSignature 方法中一样

public static boolean verifyDigitalSignature(byte[] plainText, byte[] digitalSignature, PublicKey publicKey)
        throws Exception {
    Signature signature = Signature.getInstance("Ed25519");
    signature.initVerify(publicKey);
    signature.update(plainText);
    return signature.verify(digitalSignature);
}

最后,您可以通过调用其上的 verify 方法来检查签名。

您可以通过在 jshell 中运行以下代码片段来尝试以上方法

import java.security.*;
import java.util.HexFormat;
import java.util.Scanner;

public class DigitalSignatureExample {
    public static final String Ed25519 = "Ed25519";

    public static byte[] generateDigitalSignature(byte[] plainText, PrivateKey privateKey) throws Exception  {
        Signature signature = Signature.getInstance(Ed25519);
        signature.initSign(privateKey);
        signature.update(plainText);
        return signature.sign();

    }
    
    public static boolean verifyDigitalSignature(byte[] plainText, byte[] digitalSignature, PublicKey publicKey)
            throws Exception {
        Signature signature = Signature.getInstance(Ed25519);
        signature.initVerify(publicKey);
        signature.update(plainText);
        return signature.verify(digitalSignature);
    }

    public static void main(String[] args) throws Exception {

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("Ed25519");
        KeyPair keypair = keyPairGenerator.generateKeyPair();

        Scanner message = new Scanner(System.in);
        System.out.print("Enter the message you want to encrypt using Ed25519: ");
        String plainText = message.nextLine();
        byte[] bytes = plainText.getBytes();
        message.close();

        byte[] digitalSignature = generateDigitalSignature(bytes, keypair.getPrivate());

        System.out.println("Signature Value:\n " + HexFormat.of().formatHex(digitalSignature));
        System.out.println("Verification: " + verifyDigitalSignature(bytes, digitalSignature, keypair.getPublic()));

    }
}

在下一节中,让我们进一步研究数字证书以及它们如何帮助您将公钥所有权与拥有该公钥的实体关联起来。  

Java 中的数字证书基础

在密码学中,证书代表将某些信息绑定在一起的电子文档,例如用户的身份和公钥。可信第三方 - 证书颁发机构 (CA) - 颁发数字证书以验证证书持有者的身份。

Portion of a CA certificate obtained from a remote SSL server

图 1:来自 keytool -printcert -sslserver oracle.com/java 输出的证书的一部分

数字证书包含

  • 序列号用于唯一标识证书,由证书确定的个人或实体。
  • 到期日期(不早于和不晚于)
  • 证书持有者姓名(主体)。
  • 证书持有者公钥的副本。您需要此密钥来解密消息和数字签名。
  • 证书颁发机构的数字签名。

为了更好地理解数字签名和证书,请查看下面的表格

特征 数字签名 数字证书
目的 验证真实性、完整性和不可否认性。 验证发送方和接收方的身份。
过程 使用私钥对消息应用密码算法以生成唯一的数字签名。 认证机构 (CA) 生成它:密钥生成、注册、验证、创建。
标准 数字签名标准 (DSS) X.509 标准格式

不是由已知证书颁发机构颁发,而是由托管证书的服务器颁发的证书称为自签名证书。您可以使用 keytool 生成一个有效的自签名证书,有效期为 365 天,方法是在终端窗口中运行以下命令

$ keytool -genkeypair -keyalg ed25519 -alias mykey -keystore mykeystore.jks -storepass jkspass \
    -dname "CN=server.mycompany.com,OU=My Company Dev Team,O=My Company,c=NL" -validity 365 

> Generating 255 bit Ed25519 key pair and self-signed certificate (Ed25519) 
> with a validity of 365 days for: CN=server.mycompany.com, OU=My Company Dev Team, O=My Company, C=NL 

前面的命令执行以下操作

  • 创建名为 mykeystore 的密钥库并为其分配密码 jkspass
  • 为“区分名称”的通用名为“server.company.com”、组织单位为“My Company”、组织为“My Company”和两位字母国家代码为“NL”的实体生成公钥/私钥对。
  • 使用默认的 Ed25519 密钥生成算法创建密钥。
  • 创建一个自签名证书,其中包含公钥和区分名称信息。此证书的有效期为 365 天,并且与密钥库条目中的私钥相关联,该条目由别名 mykey 引用。

您可以从密钥库中读取与别名关联的证书,并将其以可打印编码格式存储在文件 mycert.pem 中,方法是

$ keytool -exportcert -alias mykey \
    -keystore mykeystore.jks -storepass jkspass -rfc -file mycert.pem

> Certificate stored in file <mycert.pem>

要解析和管理此证书,处理证书路径和证书吊销列表 (CRL),您可以使用 keytooljava.security.cert 包中的类。例如,您可以生成一个 java.security.cert.Certificate 并将其转换为 java.security.cert.X509Certificate,使用之前创建的自签名证书

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; 
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

public class CertificateManagementExample {
    
    public static void main(String[] args) throws Exception {
        //generate a certificate object
        X509Certificate cert = extractCertificate("mycert.pem");
    }


    private static X509Certificate extractCertificate(String filePath) throws IOException, CertificateException {
        try (InputStream is = new FileInputStream(filePath)) {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            return (X509Certificate) certificateFactory.generateCertificate(is);
        }
    }

}

虽然 java.security.cert.Certificate 是对具有不同格式的证书的抽象,但类 java.security.cert.X509Certificate 提供了一种标准方法来访问 X.509 证书的所有属性。如果您需要验证 X.509 证书路径,则应使用 java.security.cert.TrustAnchorjava.security.cert.CertPathValidator 用于证书链。

在处理数字证书时,您可以通过使用证书指纹来快速找到系统凭据存储中特定证书的位置。证书的指纹是证书的唯一标识符,可以帮助您比较证书。您可以使用 java.security.MessageDigestjava.security.cert.X509Certificate 对象轻松计算证书指纹

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.HexFormat;

public class CertificateManagementExample {
    
    public static void main(String[] args) throws Exception {
        //generate a certificate object
        X509Certificate cert = extractCertificate("mycert.pem");
        //compute thumbprint
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(cert.getEncoded());
        System.out.println(HexFormat.ofDelimiter(":").formatHex(md.digest()));
    }

    private static X509Certificate extractCertificate(String filePath) throws IOException, CertificateException {
        try (InputStream is = new FileInputStream(filePath)) {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            return (X509Certificate) certificateFactory.generateCertificate(is);
        }
    }
}

恭喜,您已经了解了数字签名和数字证书之间的区别,以及如何使用 Java 安全 API 在 Java 中与它们进行交互。

 

更多学习


上次更新: Invalid DateTime


系列中的上一篇
当前教程
Java 中的数字签名和证书基础