Guide to the Cipher Class – 密码类指南

最后修改: 2017年 12月 26日

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

1. Overview

1.概述

Simply put, encryption is the process of encoding a message such that only authorized users can understand or access it.

简单地说,加密是对信息进行编码的过程,以便只有授权用户能够理解或访问它。

The message, referred to as plaintext, is encrypted using an encryption algorithm – a cipher – generating ciphertext that can only be read by authorized users via decryption.

被称为plaintext的信息使用加密算法–cipher进行加密,生成的ciphertext只能由授权用户通过解密读取。

In this article, we describe in detail the core Cipher class, which provides cryptographic encryption and decryption functionality in Java.

在这篇文章中,我们详细描述了核心Cipher类,它在Java中提供了加密和解密功能

2. Cipher Class

2.密码班

Java Cryptography Extension (JCE) is the part of the Java Cryptography Architecture (JCA) that provides an application with cryptographic ciphers for data encryption and decryption as well as hashing of private data.

Java Cryptography Extension(JCE)是Java Cryptography Architecture(JCA)的部分,它为应用程序提供了用于数据加密和解密的加密密码以及私人数据的散列。

The Cipher class — located in the javax.crypto package — forms the core of the JCE framework, providing the functionality for encryption and decryption.

Cipher类–位于javax.crypto包中–构成JCE框架的核心,提供加密和解密的功能。

2.1. Cipher Instantiation

2.1.密码实例化

To instantiate a Cipher object, we call the static getInstance method, passing the name of the requested transformation. Optionally, the name of a provider may be specified.

要实例化一个Cipher对象,我们调用静态的getInstance方法,并传递所请求的转换名称。可以选择指定一个提供者的名字。

Let’s write an example class illustrating the instantiation of a Cipher:

让我们写一个实例类,说明Cipher的实例化。

public class Encryptor {

    public byte[] encryptMessage(byte[] message, byte[] keyBytes) 
      throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        //...
    }
}

The transformation AES/ECB/PKCS5Padding tells the getInstance method to instantiate the Cipher object as an AES cipher with ECB mode of operation and PKCS5 padding scheme.

转换AES/ECB/PKCS5Padding告诉getInstance方法将Cipher对象实例化为AES密码与ECB操作模式和PKCS5 padding方案

We can also instantiate the Cipher object by specifying only the algorithm in the transformation:

我们也可以通过在转换中只指定算法来实例化Cipher对象。

Cipher cipher = Cipher.getInstance("AES");

In this case, Java will use provider-specific default values for the mode and padding scheme.

在这种情况下,Java将使用供应商特定的模式和填充方案的默认值。

Note that getInstance will throw a NoSuchAlgorithmException if the transformation is null, empty, or in an invalid format, or if the provider doesn’t support it.

请注意,如果转换是null、空的或无效的格式,或者如果提供者不支持它,getInstance将抛出NoSuchAlgorithmException

It will throw a NoSuchPaddingException if the transformation contains an unsupported padding scheme.

如果转换包含一个不支持的填充方案,它将抛出一个NoSuchPaddingException

2.2. Thread-Safety

2.2.线程安全

The Cipher class is a stateful one without any form of internal synchronization. As a matter of fact, methods like init() or update() will change the internal state of a particular Cipher instance.

Cipher类是一个有状态的类,没有任何形式的内部同步。事实上,像init()update()等方法将改变特定Cipher实例的内部状态。

Therefore, the Cipher class is not thread-safe. So we should create one Cipher instance per encryption/decryption need.

因此,Cipher 类不是线程安全的。所以我们应该为每个加密/解密需求创建一个Cipher实例。

2.3. Keys

2.3键

The Key interface represents keys for cryptographic operations. Keys are opaque containers that hold an encoded key, the key’s encoding format, and its cryptographic algorithm.

密钥接口表示用于加密操作的密钥。密钥是不透明的容器,它持有一个编码的密钥、密钥的编码格式以及它的加密算法。

Keys are generally obtained through key generators, certificates, or key specifications using a key factory.

密钥通常是通过密钥生成器、证书或使用密钥规范获得。

Let’s create a symmetric Key from the supplied key bytes:

让我们从提供的密钥字节中创建一个对称的密钥

SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");

2.4. Cipher Initialization

2.4.密码初始化

We call the init() method to initialize the Cipher object with a Key or Certificate and an opmode indicating the operation mode of the cipher.

我们调用init()方法来初始化Cipher对象,使用密钥Certificate和一个表示密码操作模式的opmode

Optionally, we can pass in a source of randomness. By default, a SecureRandom implementation of the highest-priority installed provider is used. Otherwise, it’ll use a system-provided source.

作为选择,我们可以传入一个随机性的来源。默认情况下,会使用一个SecureRandom实现的最高优先级的安装提供者。否则,它将使用系统提供的源。

We can specify a set of algorithm-specific parameters optionally. For example, we can pass an IvParameterSpec to specify an initialization vector.

我们可以选择性地指定一组特定的算法参数。例如,我们可以通过一个IvParameterSpec指定一个初始化向量

Here are the available cipher operation modes:

以下是可用的密码操作模式。

  • ENCRYPT_MODE: initialize cipher object to encryption mode
  • DECRYPT_MODE: initialize cipher object to decryption mode
  • WRAP_MODE: initialize cipher object to key-wrapping mode
  • UNWRAP_MODE: initialize cipher object to key-unwrapping mode

Let’s initialize the Cipher object:

我们来初始化Cipher对象。

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// ...

Now, the init method throws an InvalidKeyException if the supplied key is inappropriate for initializing the cipher, like when a key length/encoding is invalid.

现在,init方法抛出一个InvalidKeyException,如果提供的密钥不适合初始化密码,比如密钥长度/编码无效时。

It’s also thrown when the cipher requires certain algorithm parameters that cannot be determined from the key, or if the key has a key size that exceeds the maximum allowable key size (determined from the configured JCE jurisdiction policy files).

当密码需要某些算法参数而无法从密钥中确定时,或者如果密钥的大小超过了允许的最大密钥大小(由配置的JCE管辖权策略文件确定),也会被抛出。

Let’s look at an example using a Certificate:

让我们看一个使用Certificate的例子。

public byte[] encryptMessage(byte[] message, Certificate certificate) 
  throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
 
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.ENCRYPT_MODE, certificate);
    // ...
}

The Cipher object gets the public key for data encryption from the certificate by calling the getPublicKey method.

Cipher对象通过调用getPublicKey方法从证书中获取用于数据加密的公开密钥。

2.5. Encryption and Decryption

2.5.加密和解密

After initializing the Cipher object, we call the doFinal() method to perform the encryption or decryption operation. This method returns a byte array containing the encrypted or decrypted message.

在初始化Cipher对象后,我们调用doFinal()方法来执行加密或解密操作。该方法返回一个包含加密或解密信息的字节数组。

The doFinal() method also resets the Cipher object to the state it was in when previously initialized via a call to init() method, making the Cipher object available to encrypt or decrypt additional messages.

doFinal()方法还将Cipher对象重置为之前通过调用init()方法进行初始化时的状态,使Cipher对象可用于加密或解密其他消息。

Let’s call doFinal in our encryptMessage method:

让我们在我们的encryptMessage方法中调用doFinal

public byte[] encryptMessage(byte[] message, byte[] keyBytes)
  throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, 
    BadPaddingException, IllegalBlockSizeException {
 
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    return cipher.doFinal(message);
}

To perform a decrypt operation, we change the opmode to DECRYPT_MODE:

为了执行解密操作,我们将opmode改为DECRYPT_MODE

public byte[] decryptMessage(byte[] encryptedMessage, byte[] keyBytes) 
  throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, 
    BadPaddingException, IllegalBlockSizeException {
 
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    return cipher.doFinal(encryptedMessage);
}

2.6. Providers

2.6.提供者

Designed to use a provider-based architecture, the JCE allows for qualified cryptography libraries such as BouncyCastle to be plugged in as security providers and new algorithms to be added seamlessly.

设计为使用基于提供商的架构JCE 允许合格的密码学库(如BouncyCastle)作为安全提供商插入,并可无缝添加新算法

Now let’s add BouncyCastle as a security provider. We can add a security provider either statically or dynamically.

现在让我们把BouncyCastle作为一个安全提供者加入。我们可以静态或动态地添加一个安全提供者。

To add BouncyCastle statically, we modify the java.security file located in <JAVA_HOME>/jre/lib/security folder.

为了静态添加 BouncyCastle,我们要修改位于 <JAVA_HOME>/jre/lib/security 文件夹中的 java.security 文件

We add the line at the end of the list:

我们在列表的末尾添加这一行。

...
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider
security.provider.7=org.bouncycastle.jce.provider.BouncyCastleProvider

When adding the provider property, the property key is in the format security.provider.N where the number N is one more than the last one on the list.

在添加提供者属性时,属性键的格式为security.provider.N,其中数字N是比列表中的最后一个多一个。

We can also add the BouncyCastle security provider dynamically without having to modify the security file:

我们还可以动态地添加BouncyCastle安全提供者,而不必修改安全文件。

Security.addProvider(new BouncyCastleProvider());

We can now specify the provider during cipher initialization:

现在我们可以在密码初始化时指定提供者。

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "BC");

BC specifies BouncyCastle as the provider. We can get the list of registered providers via the Security.getProviders() method.

BC指定BouncyCastle为提供者。我们可以通过Security.getProviders()方法获得已注册提供商的列表。

3. Testing Encryption and Decryption

3.测试加密和解密

Let’s write an example test to illustrate message encryption and decryption.

让我们写一个例子测试来说明消息的加密和解密。

In this test, we use AES encryption algorithm with a 128-bit key and assert that the decrypted result is equal to the original message text:

在这个测试中,我们使用128位密钥的AES加密算法,并断言解密的结果与原始信息文本相等。

@Test
public void whenIsEncryptedAndDecrypted_thenDecryptedEqualsOriginal() 
  throws Exception {
 
    String encryptionKeyString =  "thisisa128bitkey";
    String originalMessage = "This is a secret message";
    byte[] encryptionKeyBytes = encryptionKeyString.getBytes();

    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKey secretKey = new SecretKeySpec(encryptionKeyBytes, "AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    byte[] encryptedMessageBytes = cipher.doFinal(message.getBytes());

    cipher.init(Cipher.DECRYPT_MODE, secretKey);

    byte[] decryptedMessageBytes = cipher.doFinal(encryptedMessageBytes);
    assertThat(originalMessage).isEqualTo(new String(decryptedMessageBytes));
}

4. Conclusion

4.结论

In this article, we discussed the Cipher class and presented usage examples. More details on the Cipher class and the JCE Framework can be found in the class documentation and the Java Cryptography Architecture (JCA) Reference Guide.

在这篇文章中,我们讨论了Cipher类并介绍了使用实例。关于Cipher类和JCE框架的更多细节可以在类文档Java Cryptography Architecture(JCA)参考指南中找到。

Implementation of all these examples and code snippets can be found over on GitHub. This is a Maven-based project, so it should be easy to import and run as it is.

所有这些例子和代码片段的实现都可以在GitHub上找到over。这是一个基于Maven的项目,所以应该很容易导入并按原样运行。