1. Overview
1.概述
In this tutorial, we’re going to learn about the Digital Signature mechanism and how we can implement it using the Java Cryptography Architecture (JCA). We’ll explore the KeyPair, MessageDigest, Cipher, KeyStore, Certificate, and Signature JCA APIs.
在本教程中,我们将学习数字签名机制以及如何使用Java Cryptography Architecture (JCA)实现它。我们将探讨KeyPair、MessageDigest、Cipher、KeyStore、Certificate,和Signature JCA API。
We’ll start by understanding what is Digital Signature, how to generate a key pair, and how to certify the public key from a certificate authority (CA). After that, we’ll see how to implement Digital Signature using the low-level and high-level JCA APIs.
我们将首先了解什么是数字签名,如何生成一个密钥对,以及如何从证书颁发机构(CA)认证公钥。之后,我们将看到如何使用低级和高级的JCA APIs来实现数字签名。
2. What Is Digital Signature?
2.什么是数字签名?
2.1. Digital Signature Definition
2.1 数字签名的定义
Digital Signature is a technique for ensuring:
数字签名是一种用于确保的技术。
- Integrity: the message hasn’t been altered in transit
- Authenticity: the author of the message is really who they claim to be
- Non-repudiation: the author of the message can’t later deny that they were the source
2.2. Sending a Message with a Digital Signature
2.2.发送带有数字签名的信息
Technically speaking, a digital signature is the encrypted hash (digest, checksum) of a message. That means we generate a hash from a message and encrypt it with a private key according to a chosen algorithm.
从技术上讲,一个数字签名是一个消息的加密散列(摘要,校验)。这意味着我们从一个信息中生成一个哈希值,并根据选定的算法用私人密钥对其进行加密。
The message, the encrypted hash, the corresponding public key, and the algorithm are all then sent. This is classified as a message with its digital signature.
然后,信息、加密的哈希值、相应的公钥和算法都被发送。这被归类为一个带有数字签名的信息。
2.3. Receiving and Checking a Digital Signature
2.3.接收和检查数字签名
To check the digital signature, the message receiver generates a new hash from the received message, decrypts the received encrypted hash using the public key, and compares them. If they match, the Digital Signature is said to be verified.
为了检查数字签名,信息接收者从收到的信息中生成一个新的哈希值,用公钥对收到的加密哈希值进行解密,并对它们进行比较。如果它们匹配,则表示数字签名得到了验证。
We should note that we only encrypt the message hash, and not the message itself. In other words, Digital Signature doesn’t try to keep the message secret. Our digital signature only proves that the message was not altered in transit.
我们应该注意,我们只对信息哈希进行加密,而不是对信息本身进行加密。换句话说,数字签名并不试图对信息进行保密。我们的数字签名只证明了信息在传输过程中没有被改变。
When the signature is verified, we’re sure that only the owner of the private key could be the author of the message.
当签名被验证时,我们确信只有私钥的所有者才可能是信息的作者。
3. Digital Certificate and Public Key Identity
3.数字证书和公钥身份
A certificate is a document that associates an identity to a given public key. Certificates are signed by a third-party entity called a Certificate Authority (CA).
证书是将一个身份与一个给定的公共密钥联系起来的文件。证书由一个称为证书颁发机构(CA)的第三方实体签署。
We know that if the hash we decrypt with the published public key matches the actual hash, then the message is signed. However, how do we know that the public key really came from the right entity? This is solved by the use of digital certificates.
我们知道,如果我们用公布的公钥解密的哈希值与实际的哈希值一致,那么该信息就被签署了。然而,我们如何知道公钥真的来自正确的实体?这可以通过使用数字证书来解决。
A Digital Certificate contains a public key and is itself signed by another entity. The signature of that entity can itself be verified by another entity and so on. We end up having what we call a certificate chain. Each top entity certifies the public key of the next entity. The most top-level entity is self-signed, which means that his public key is signed by his own private key.
数字证书包含一个公共密钥,其本身由另一个实体签署。该实体的签名本身可以由另一个实体验证,依此类推。我们最终有一个我们称之为证书链的东西。每个顶级实体都对下一个实体的公钥进行验证。最顶层的实体是自签的,这意味着他的公钥是由他自己的私钥签署的。
The X.509 is the most used certificate format, and it is shipped either as binary format (DER) or text format (PEM). JCA already provides an implementation for this via the X509Certificate class.
X.509是最常用的证书格式,它以二进制格式(DER)或文本格式(PEM)运送。JCA已经通过X509Certificate类为其提供了一个实现。
4. KeyPair Management
4.密钥对管理
Since Digital Signature uses a private and public key, we’ll use the JCA classes PrivateKey and PublicKey for signing and checking a message, respectively.
由于数字签名使用私钥和公钥,我们将使用JCA类PrivateKey和PublicKey来分别签署和检查一个消息。
4.1. Getting a KeyPair
4.1.获得一个密钥对
To create a key pair of a private and public key, we’ll use the Java keytool.
为了创建一个私钥和公钥的配对,我们将使用Java keytool。
Let’s generate a key pair using the genkeypair command:
让我们使用genkeypair命令生成一个密钥对。
keytool -genkeypair -alias senderKeyPair -keyalg RSA -keysize 2048 \
-dname "CN=Baeldung" -validity 365 -storetype PKCS12 \
-keystore sender_keystore.p12 -storepass changeit
This creates a private key and its corresponding public key for us. The public key is wrapped into an X.509 self-signed certificate which is wrapped in turn into a single-element certificate chain. We store the certificate chain and the private key in the Keystore file sender_keystore.p12, which we can process using the KeyStore API.
这将为我们创建一个私钥和其相应的公钥。公钥被包装成一个X.509自签名证书,而该证书又被包装成一个单元素的证书链。我们将证书链和私钥存储在Keystore文件sender_keystore.p12中,我们可以使用KeyStore API来处理它。
Here, we’ve used the PKCS12 key store format, as it is the standard and recommended over the Java-proprietary JKS format. Also, we should remember the password and alias, as we’ll use them in the next subsection when loading the Keystore file.
在这里,我们使用了PKCS12密钥存储格式,因为它是标准的,比Java专有的JKS格式更被推荐。另外,我们应该记住密码和别名,因为我们将在下一小节加载Keystore文件时使用它们。
4.2. Loading the Private Key for Signing
4.2.加载用于签名的私钥
In order to sign a message, we need an instance of the PrivateKey.
为了签署一份信息,我们需要一个PrivateKey.的实例。
Using the KeyStore API, and the previous Keystore file, sender_keystore.p12, we can get a PrivateKey object:
使用KeyStore API,以及之前的Keystore文件sender_keystore.p12,我们可以得到一个PrivateKey对象。
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("sender_keystore.p12"), "changeit");
PrivateKey privateKey =
(PrivateKey) keyStore.getKey("senderKeyPair", "changeit");
4.3. Publishing the Public Key
4.3.发布公钥
Before we can publish the public key, we must first decide whether we’re going to use a self-signed certificate or a CA-signed certificate.
在我们发布公钥之前,我们必须首先决定是使用自签名的证书还是CA签名的证书。
When using a self-signed certificate, we need only to export it from the Keystore file. We can do this with the exportcert command:
当使用自签名证书时,我们只需要从Keystore文件中导出它。我们可以用exportcert命令来做。
keytool -exportcert -alias senderKeyPair -storetype PKCS12 \
-keystore sender_keystore.p12 -file \
sender_certificate.cer -rfc -storepass changeit
Otherwise, if we’re going to work with a CA-signed certificate, then we need to create a certificate signing request (CSR). We do this with the certreq command:
否则,如果我们要使用 CA 签署的证书,那么我们需要创建一个证书签署请求(CSR)。我们用certreq命令来做这个。
keytool -certreq -alias senderKeyPair -storetype PKCS12 \
-keystore sender_keystore.p12 -file -rfc \
-storepass changeit > sender_certificate.csr
The CSR file, sender_certificate.csr, is then sent to a Certificate Authority for the purpose of signing. When this is done, we’ll receive a signed public key wrapped in an X.509 certificate, either in binary (DER) or text (PEM) format. Here, we’ve used the rfc option for a PEM format.
然后,CSR文件,sender_certificate.csr,被发送到一个证书颁发机构进行签署。完成后,我们将收到一个经过签名的公钥,它被包裹在X.509证书中,可以是二进制(DER)或文本(PEM)格式。在这里,我们使用rfc选项来获得PEM格式。
The public key we received from the CA, sender_certificate.cer, has now been signed by a CA and can be made available for clients.
我们从CA收到的公钥,sender_certificate.cer,现在已经被CA签署,可以提供给客户使用。
4.4. Loading a Public Key for Verification
4.4.加载公钥进行验证
Having access to the public key, a receiver can load it into their Keystore using the importcert command:
在获得公钥后,接收方可以使用importcert命令将其加载到他们的钥匙库中。
keytool -importcert -alias receiverKeyPair -storetype PKCS12 \
-keystore receiver_keystore.p12 -file \
sender_certificate.cer -rfc -storepass changeit
And using the KeyStore API as before, we can get a PublicKey instance:
和之前一样使用KeyStore API,我们可以得到一个PublicKey实例。
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("receiver_keytore.p12"), "changeit");
Certificate certificate = keyStore.getCertificate("receiverKeyPair");
PublicKey publicKey = certificate.getPublicKey();
Now that we have a PrivateKey instance on the sender side, and an instance of the PublicKey on the receiver side, we can start the process of signing and verification.
现在我们在发送方有一个PrivateKey实例,在接收方有一个PublicKey实例,我们可以开始签名和验证过程。
5. Digital Signature With MessageDigest and Cipher Classes
5.使用MessageDigest和Cipher类的数字签名
As we have seen, the digital signature is based on hashing and encryption.
正如我们所看到的,数字签名是基于散列和加密的。
Usually, we use the MessageDigest class with SHA or MD5 for hashing and the Cipher class for encryption.
通常,我们使用MessageDigest类与SHA或MD5进行散列,使用Cipher类进行加密。
Now, let’s start implementing the digital signature mechanisms.
现在,让我们开始实施数字签名机制。
5.1. Generating a Message Hash
5.1.生成一个信息哈希值
A message can be a string, a file, or any other data. So let’s take the content of a simple file:
消息可以是一个字符串,一个文件,或任何其他数据。因此,让我们以一个简单的文件的内容为例。
byte[] messageBytes = Files.readAllBytes(Paths.get("message.txt"));
Now, using MessageDigest, let’s use the digest method to generate a hash:
现在,使用MessageDigest,让我们使用digest方法来生成一个哈希值。
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] messageHash = md.digest(messageBytes);
Here, we’ve used the SHA-256 algorithm, which is the one most commonly used. Other alternatives are MD5, SHA-384, and SHA-512.
这里,我们使用了SHA-256算法,这是最常用的算法。其他的选择是MD5、SHA-384和SHA-512。
5.2. Encrypting the Generated Hash
5.2.对生成的哈希值进行加密
To encrypt a message, we need an algorithm and a private key. Here we’ll use the RSA algorithm. The DSA algorithm is another option.
为了加密一个信息,我们需要一种算法和一个私钥。这里我们将使用RSA算法。DSA算法是另一种选择。
Let’s create a Cipher instance and initialize it for encryption. Then we’ll call the doFinal() method to encrypt the previously hashed message:
让我们创建一个Cipher实例并初始化它进行加密。然后我们将调用doFinal()方法来加密之前的哈希消息。
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] digitalSignature = cipher.doFinal(messageHash);
The signature can be saved into a file for sending it later:
签名可以被保存到一个文件中,以便以后发送。
Files.write(Paths.get("digital_signature_1"), digitalSignature);
At this point, the message, the digital signature, the public key, and the algorithm are all sent, and the receiver can use these pieces of information to verify the integrity of the message.
在这一点上,信息、数字签名、公钥和算法都已发送,接收方可以使用这些信息来验证信息的完整性。
5.3. Verifying Signature
5.3.验证签名
When we receive a message, we must verify its signature. To do so, we decrypt the received encrypted hash and compare it with a hash we make of the received message.
当我们收到一个信息时,我们必须验证其签名。要做到这一点,我们要解密收到的加密哈希值,并将其与我们对收到的信息所做的哈希值进行比较。
Let’s read the received digital signature:
让我们来读读收到的数字签名。
byte[] encryptedMessageHash =
Files.readAllBytes(Paths.get("digital_signature_1"));
For decryption, we create a Cipher instance. Then we call the doFinal method:
对于解密,我们创建一个Cipher实例。然后我们调用doFinal方法。
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] decryptedMessageHash = cipher.doFinal(encryptedMessageHash);
Next, we generate a new message hash from the received message:
接下来,我们从收到的信息中生成一个新的信息哈希值。
byte[] messageBytes = Files.readAllBytes(Paths.get("message.txt"));
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] newMessageHash = md.digest(messageBytes);
And finally, we check if the newly generated message hash matches the decrypted one:
最后,我们检查新生成的信息哈希值是否与解密的信息相匹配。
boolean isCorrect = Arrays.equals(decryptedMessageHash, newMessageHash);
In this example, we’ve used the text file message.txt to simulate a message we want to send, or the location of the body of a message we’ve received. Normally, we’d expect to receive our message alongside the signature.
在这个例子中,我们用文本文件message.txt来模拟我们要发送的信息,或者我们收到的信息正文的位置。通常情况下,我们会期望在签名旁边收到我们的消息。
6. Digital Signature Using the Signature Class
6.使用签名类的数字签名
So far, we’ve used the low-level APIs to build our own digital signature verification process. This helps us understand how it works and allows us to customize it.
到目前为止,我们已经使用低级别的API来建立我们自己的数字签名验证过程。这有助于我们了解它是如何工作的,并允许我们对它进行定制。
However, JCA already offers a dedicated API in the form of the Signature class.
然而,JCA已经以Signature类的形式提供了一个专门的API。
6.1. Signing a Message
6.1.签署信息
To start the process of signing, we first create an instance of the Signature class. To do that, we need a signing algorithm. We then initialize the Signature with our private key:
为了开始签名的过程,我们首先创建一个Signature类的实例。要做到这一点,我们需要一个签名算法。然后我们用我们的私钥初始化Signature。
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
The signing algorithm we chose, SHA256withRSA in this example, is a combination of a hashing algorithm and an encryption algorithm. Other alternatives include SHA1withRSA, SHA1withDSA, and MD5withRSA, among others.
我们选择的签名算法,SHA256withRSA在这个例子中,是一种散列算法和加密算法的组合。其他选择包括SHA1withRSA、SHA1withDSA和MD5withRSA,等等。
Next, we proceed to sign the byte array of the message:
接下来,我们继续对信息的字节数组进行签名。
byte[] messageBytes = Files.readAllBytes(Paths.get("message.txt"));
signature.update(messageBytes);
byte[] digitalSignature = signature.sign();
We can save the signature into a file for later transmission:
我们可以将签名保存到一个文件中,以便以后传输。
Files.write(Paths.get("digital_signature_2"), digitalSignature);
6.2. Verifying the Signature
6.2.核实签名
To verify the received signature, we again create a Signature instance:
为了验证收到的签名,我们再次创建一个Signature实例。
Signature signature = Signature.getInstance("SHA256withRSA");
Next, we initialize the Signature object for verification by calling the initVerify method, which takes a public key:
接下来,我们通过调用initVerify方法来初始化Signature对象进行验证,该方法需要一个公钥。
signature.initVerify(publicKey);
Then, we need to add the received message bytes to the signature object by invoking the update method:
然后,我们需要通过调用update方法将收到的消息字节添加到签名对象。
byte[] messageBytes = Files.readAllBytes(Paths.get("message.txt"));
signature.update(messageBytes);
And finally, we can check the signature by calling the verify method:
最后,我们可以通过调用verify方法检查签名。
boolean isCorrect = signature.verify(receivedSignature);
7. Conclusion
7.结论
In this article, we first looked at how digital signature works and how to establish trust for a digital certificate. Then we implemented a digital signature using the MessageDigest, Cipher, and Signature classes from the Java Cryptography Architecture.
在这篇文章中,我们首先看了数字签名的工作原理以及如何为数字证书建立信任。然后我们使用Java Cryptography Architecture中的MessageDigest,Cipher,和Signature类实现了一个数字签名。
We saw in detail how to sign data using the private key and how to verify the signature using a public key.
我们详细看到了如何使用私钥签署数据,以及如何使用公钥验证签名。
As always, the code from this article is available over on GitHub.
一如既往,本文中的代码可在GitHub上获得。