RSA in Java – Java中的RSA

最后修改: 2021年 4月 8日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

RSA, or in other words Rivest–Shamir–Adleman, is an asymmetric cryptographic algorithm. It differs from symmetric algorithms like DES or AES by having two keys. A public key that we can share with anyone is used to encrypt data. And a private one that we keep only for ourselves and it’s used for decrypting the data

RSA,或者换句话说Rivest-Shamir-Adleman,是一种非对称密码算法。它与DESAES等对称算法不同,它有两个密钥。一个我们可以与任何人分享的公钥,用于加密数据。还有一把私人钥匙,我们只为自己保留,它用于解密数据。

In this tutorial, we’ll learn how to generate, store and use the RSA keys in Java.

在本教程中,我们将学习如何在Java中生成、存储和使用RSA密钥。

2. Generate RSA Key Pair

2.生成RSA密钥对

Before we start the actual encryption, we need to generate our RSA key pair. We can easily do it by using the KeyPairGenerator from java.security package:

在我们开始实际加密之前,我们需要生成我们的RSA密钥对。我们可以通过使用java.security包中的KeyPairGenerator轻松做到这一点。

KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair pair = generator.generateKeyPair();

The generated key will have a size of 2048 bits.

生成的密钥将有2048位的大小。

Next, we can extract the private and public key:

接下来,我们可以提取私钥和公钥。

PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();

We’ll use the public key to encrypt the data and the private one for decrypting it.

我们将使用公钥对数据进行加密,使用私钥对数据进行解密。

3. Storing Keys in Files

3.在文件中存储钥匙

Storing the key pair in memory is not always a good option. Mostly, the keys will stay unchanged for a long time. In such cases, it’s more convenient to store them in files.

将密钥对存储在内存中并不总是一个好的选择。大多数情况下,钥匙会在很长一段时间内保持不变。在这种情况下,将它们存储在文件中更为方便。

To save a key in a file, we can use the getEncoded method, which returns the key content in its primary encoding format:

要在文件中保存一个密钥,我们可以使用getEncoded方法,它以主要编码格式返回密钥内容。

try (FileOutputStream fos = new FileOutputStream("public.key")) {
    fos.write(publicKey.getEncoded());
}

To read the key from a file, we’ll first need to load the content as a byte array:

要从一个文件中读取密钥,我们首先需要将内容加载为一个字节数组。

File publicKeyFile = new File("public.key");
byte[] publicKeyBytes = Files.readAllBytes(publicKeyFile.toPath());

and then use the KeyFactory to recreate the actual instance:

然后使用KeyFactory来重新创建实际的实例。

KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
keyFactory.generatePublic(publicKeySpec);

The key byte content needs to be wrapped with an EncodedKeySpec class. Here, we’re using the X509EncodedKeySpec, which represents the default algorithm for Key::getEncoded method we used for saving the file.

密钥的字节内容需要用一个EncodedKeySpec类来包装。在这里,我们使用X509EncodedKeySpec,,它代表了我们用于保存文件的Key::getEncoded方法的默认算法。

In this example, we saved and read only the public key file. The same steps can be used for handling the private key.

在这个例子中,我们只保存和读取了公钥文件。同样的步骤也可以用于处理私钥。

Remember, keep the file with a private key as safe as possible with access as limited as possible. Unauthorized access might bring security issues.

请记住,将带有私钥的文件尽可能的安全,并尽可能的限制访问。未经授权的访问可能会带来安全问题。

4. Working With Strings

4.使用字符串的工作

Now, let’s take a look at how we can encrypt and decrypt simple strings. Firstly, we’ll need some data to work with:

现在,让我们来看看我们如何对简单的字符串进行加密和解密。首先,我们需要一些数据来工作。

String secretMessage = "Baeldung secret message";

Secondly, we’ll need a Cipher object initialized for encryption with the public key that we generated previously:

其次,我们需要一个Cipher对象,用我们之前生成的公开密钥进行初始化加密。

Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);

Having that ready, we can invoke the doFinal method to encrypt our message. Note that it accepts only byte array arguments, so we need to transform our string before:

准备好后,我们可以调用doFinal方法来加密我们的信息。注意,它只接受字节数组参数,所以我们需要在之前转换我们的字符串。

byte[] secretMessageBytes = secretMessage.getBytes(StandardCharsets.UTF_8);)
byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessageBytes);

Now, our message is successfully encoded. If we’d like to store it in a database or send it via REST API, it would be more convenient to encode it with the Base64 Alphabet:

现在,我们的消息已经成功编码。如果我们想把它存储在数据库中或通过REST API发送它,用Base64字母编码会更方便。

String encodedMessage = Base64.getEncoder().encodeToString(encryptedMessageBytes);

This way, the message will be more readable and easier to work with.

这样一来,信息将更易读,更容易操作。

Now, let’s see how we can decrypt the message to its original form. For this, we’ll need another Cipher instance. This time we’ll initialize it with a decryption mode and a private key:

现在,让我们看看如何将信息解密为原始形式。为此,我们将需要另一个Cipher实例。这一次,我们将用一个解密模式和一个私钥来初始化它。

Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);

We’ll invoke the cipher as previously with the doFinal method:

我们将像以前一样用doFinal方法调用密码。

byte[] decryptedMessageBytes = decryptCipher.doFinal(encryptedMessageBytes);
String decryptedMessage = new String(decryptedMessageBytes, StandardCharsets.UTF_8);

Finally, let’s verify if the encryption-decryption process went correctly:

最后,让我们验证一下加密-解密过程是否正确。

assertEquals(secretMessage, decryptedMessage);

5. Working With Files

5.使用文件的工作

It is also possible to encrypt whole files. As an example, let’s create a temp file with some text content:

也可以对整个文件进行加密。作为一个例子,让我们创建一个有一些文本内容的临时文件。

Path tempFile = Files.createTempFile("temp", "txt");
Files.writeString(tempFile, "some secret message");

Before we start the encryption, we need to transform its content into a byte array:

在我们开始加密之前,我们需要将其内容转化为一个字节数组。

byte[] fileBytes = Files.readAllBytes(tempFile);

Now, we can use the encryption cipher:

现在,我们可以使用加密密码了。

Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedFileBytes = encryptCipher.doFinal(fileBytes);

And finally, we can overwrite it with new, encrypted content:

最后,我们可以用新的、加密的内容来覆盖它。

try (FileOutputStream stream = new FileOutputStream(tempFile.toFile())) {
    stream.write(encryptedFileBytes);
}

The decryption process looks very similar. The only difference is a cipher initialized in decryption mode with a private key:

解密过程看起来非常相似。唯一的区别是在解密模式下用私钥初始化了一个密码。

byte[] encryptedFileBytes = Files.readAllBytes(tempFile);
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedFileBytes = decryptCipher.doFinal(encryptedFileBytes);
try (FileOutputStream stream = new FileOutputStream(tempFile.toFile())) {
    stream.write(decryptedFileBytes);
}

As the last step, we can verify if the file content matches the original value:

作为最后一步,我们可以验证文件内容是否与原始值相符。

String fileContent = Files.readString(tempFile);
Assertions.assertEquals("some secret message", fileContent);

6. Summary

6.归纳总结

In this article, we’ve learned how to create RSA keys in Java and how to use them to encrypt and decrypt messages and files. As always, all source code is available over on GitHub.

在这篇文章中,我们已经学会了如何在Java中创建RSA密钥,以及如何使用它们来加密和解密信息和文件。一如既往,所有的源代码都可以在GitHub上找到