Tandatangan Digital di Jawa

Java Teratas

Saya baru sahaja mengumumkan kursus Learn Spring yang baru , yang berfokus pada asas-asas Spring 5 dan Spring Boot 2:

>> SEMAK KURSUS

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan belajar tentang mekanisme Digital Signature dan bagaimana kita dapat menerapkannya menggunakan Java Cryptography Architecture (JCA) . Kami akan meneroka API KeyPair, MessageDigest, Cipher, KeyStore, Certificate dan JCA Signature .

Kita akan mulakan dengan memahami apa itu Digital Signature, bagaimana menghasilkan pasangan kunci, dan bagaimana mengesahkan kunci awam dari pihak berkuasa perakuan (CA). Selepas itu, kita akan melihat cara melaksanakan Digital Signature menggunakan JCA API tahap rendah dan tinggi.

2. Apakah Tandatangan Digital?

2.1. Definisi Tandatangan Digital

Digital Signature adalah teknik untuk memastikan:

  • Integriti: mesej belum diubah dalam perjalanan
  • Ketulenan: pengarang mesej itu benar-benar menjadi orang yang mereka dakwa
  • Bukan penolakan: pengarang mesej kemudiannya tidak dapat menafikan bahawa mereka adalah sumbernya

2.2. Menghantar Mesej dengan Tandatangan Digital

Secara teknikal, yang tandatangan digital adalah hash yang disulitkan (dihadam, checksum) daripada mesej . Ini bermaksud kita menghasilkan hash dari mesej dan menyulitkannya dengan kunci peribadi mengikut algoritma yang dipilih.

Mesej, hash yang disulitkan, kunci awam yang sesuai, dan algoritma semuanya kemudian dihantar. Ini dikelaskan sebagai mesej dengan tandatangan digitalnya.

2.3. Menerima dan Memeriksa Tandatangan Digital

Untuk memeriksa tandatangan digital, penerima mesej menghasilkan hash baru dari mesej yang diterima, menyahsulitkan hash yang disulitkan yang diterima menggunakan kunci awam, dan membandingkannya. Sekiranya sesuai, Tanda Tangan Digital dikatakan telah disahkan.

Kita harus perhatikan bahawa kita hanya mengenkripsi hash mesej, dan bukan mesej itu sendiri. Dengan kata lain, Digital Signature tidak cuba merahsiakan mesej tersebut. Tanda tangan digital kami hanya membuktikan bahawa mesej itu tidak diubah dalam perjalanan.

Apabila tandatangan disahkan, kami pasti bahawa hanya pemilik kunci peribadi yang boleh menjadi pengarang mesej tersebut .

3. Sijil Digital dan Identiti Kunci Awam

Sijil adalah dokumen yang mengaitkan identiti dengan kunci awam tertentu. Sijil-sijil ditandatangani oleh entiti pihak ketiga yang dinamakan Certificate Authority (CA).

Kami tahu bahawa jika hash yang kami nyahsulitkan dengan kunci awam yang diterbitkan sepadan dengan hash sebenar, maka mesej itu ditandatangani. Namun, bagaimana kita tahu bahawa kunci awam benar-benar berasal dari entiti yang betul? Ini diselesaikan dengan penggunaan sijil digital.

Sijil Digital mengandungi kunci awam dan ditandatangani oleh entiti lain. Tandatangan entiti itu sendiri dapat disahkan oleh entiti lain dan sebagainya. Kami akhirnya mempunyai apa yang kami panggil rantai sijil. Setiap entiti teratas mengesahkan kunci awam entiti seterusnya. Entiti peringkat paling atas ditandatangani sendiri, yang bermaksud bahawa kunci awamnya ditandatangani oleh kunci peribadinya sendiri.

X.509 adalah format sijil yang paling banyak digunakan, dan dihantar sama ada sebagai format binari (DER) atau format teks (PEM). JCA sudah menyediakan pelaksanaan untuk ini melalui kelas X509Certificate .

4. Pengurusan KeyPair

Oleh kerana Digital Signature menggunakan kunci peribadi dan awam, kami akan menggunakan kelas JCA PrivateKey dan PublicKey masing-masing untuk menandatangani dan memeriksa mesej.

4.1. Mendapatkan KeyPair

Untuk membuat sepasang kunci kunci peribadi dan awam , kami akan menggunakan Java keytool .

Mari buat pasangan kunci menggunakan arahan genkeypair :

keytool -genkeypair -alias senderKeyPair -keyalg RSA -keysize 2048 \ -dname "CN=Baeldung" -validity 365 -storetype PKCS12 \ -keystore sender_keystore.p12 -storepass changeit

Ini mewujudkan kunci peribadi dan kunci awam yang sesuai untuk kita. Kunci awam dibungkus ke dalam sijil yang ditandatangani sendiri X.509 yang kemudian dibungkus menjadi rantai sijil elemen tunggal. Kami menyimpan rantai sijil dan kunci peribadi dalam fail Keystore sender_keystore.p12 , yang dapat kami proses menggunakan KeyStore API.

Di sini, kami telah menggunakan format penyimpanan kunci PKCS12, kerana ini adalah standard dan disyorkan berbanding format JKS proprietari Java. Juga, kita harus ingat kata laluan dan alias, kerana kita akan menggunakannya pada subseksyen seterusnya semasa memuatkan fail Keystore.

4.2. Memuatkan Kunci Peribadi untuk Menandatangani

Untuk menandatangani mesej, kami memerlukan contoh PrivateKey.

Dengan menggunakan KeyStore API, dan fail Keystore sebelumnya, sender_keystore.p12, kita dapat memperoleh objek PrivateKey :

KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(new FileInputStream("sender_keystore.p12"), "changeit"); PrivateKey privateKey = (PrivateKey) keyStore.getKey("senderKeyPair", "changeit");

4.3. Penerbitan Kunci Awam

Sebelum kita dapat menerbitkan kunci awam, kita mesti memutuskan terlebih dahulu sama ada kita akan menggunakan sijil yang ditandatangani sendiri atau sijil yang ditandatangani CA.

Semasa menggunakan sijil yang ditandatangani sendiri, kami hanya perlu mengeksportnya dari fail Keystore. Kita boleh melakukan ini dengan perintah exportcert :

keytool -exportcert -alias senderKeyPair -storetype PKCS12 \ -keystore sender_keystore.p12 -file \ sender_certificate.cer -rfc -storepass changeit

Jika tidak, jika kita akan bekerja dengan sijil bertanda CA, maka kita perlu membuat permintaan penandatanganan sijil (CSR) . Kami melakukan ini dengan perintah 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.

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.

4.4. Loading a Public Key for Verification

Having access to the public key, a receiver can load it into their Keystore using the importcert command:

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 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.

5. Digital Signature With MessageDigest and Cipher Classes

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.

Now, let's start implementing the digital signature mechanisms.

5.1. Generating a Message Hash

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 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.

5.2. Encrypting the Generated Hash

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.

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 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

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 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.

6. Digital Signature Using the Signature Class

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.

However, JCA already offers a dedicated API in the form of the Signature class.

6.1. Signing a Message

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.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.

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

To verify the received signature, we again create a Signature instance:

Signature signature = Signature.getInstance("SHA256withRSA");

Next, we initialize the Signature object for verification by calling the initVerify method, which takes a public key:

signature.initVerify(publicKey);

Then, we need to add the received message bytes to the signature object by invoking the update method:

byte[] messageBytes = Files.readAllBytes(Paths.get("message.txt")); signature.update(messageBytes);

And finally, we can check the signature by calling the verify method:

boolean isCorrect = signature.verify(receivedSignature);

7. Conclusion

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.

We saw in detail how to sign data using the private key and how to verify the signature using a public key.

Seperti biasa, kod dari artikel ini terdapat di GitHub.

Bahagian bawah Java

Saya baru sahaja mengumumkan kursus Learn Spring yang baru , yang berfokus pada asas-asas Spring 5 dan Spring Boot 2:

>> SEMAK KURSUS