Java密码
Java Cipher(javax.crypto.Cipher
)类表示一种加密算法。密码这一术语是密码学领域中加密算法的标准术语。这就是为什么Java类称为Cipher
而不是例如Encrypter
/Decrypter
或者其他。
我们可以使用Cipher
实例来加密和解密Java中的数据。该Java Cipher教程将说明Java Cryptography API的Cipher
类如何工作。
创建密码
在使用JavaCipher
之前,我们只需创建Cipher
类的实例。通过调用带有参数的getInstance()方法来创建一个Cipher实例,该参数会告诉我们要使用哪种加密算法。这是创建JavaCipher
实例的示例:
Cipher cipher = Cipher.getInstance("AES");
本示例使用称为AES的加密算法创建了一个" Cipher"实例。
密码模式
某些加密算法可以在不同的模式下工作。加密模式指定有关算法应如何加密数据的详细信息。因此,加密模式会影响部分加密算法。
加密模式有时可以与多种不同的加密算法一起使用,例如添加到核心加密算法的技术。这就是为什么将模式视为与加密算法本身不同,而是将其"添加"到加密算法中的原因。以下是一些最著名的密码模式:
- ECB-电子密码本
- CBC-密码块链接
- CFB-密码反馈
- OFB-输出反馈
- 点击率-计数器
实例化密码时,可以将其模式添加到加密算法的名称中。例如,要使用密码块链接(CBC)创建AESCipher
实例,请使用以下代码:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
由于密码块链接也需要"填充方案",因此填充方案会添加在加密算法名称字符串的末尾。
请记住,默认的Java SDK加密提供程序不支持所有加密算法和模式。我们可能需要安装像Bouncy Castle这样的外部提供程序,以使用所需的模式和填充方案创建所需的Cipher
实例。
初始化密码
在使用Cipher
实例之前,必须对其进行初始化。初始化"密码"是通过调用其" init()"方法来完成的。 init()
方法有两个参数:
- 加密/解密密码操作模式。
- 加密/解密密钥。
这是在加密模式下初始化Cipher
实例的示例:
Key key = ... // get / create symmetric encryption key cipher.init(Cipher.ENCRYPT_MODE, key);
这是在解密模式下初始化Cipher
实例的示例:
Key key = ... // get / create symmetric encryption key cipher.init(Cipher.DECRYPT_MODE, key);
加密和解密数据
为了使用" Cipher"实例加密或者解密数据,我们可以调用以下两种方法之一:
update()
doFinal()
update()
和doFinal()
都有多个覆盖的版本,它们具有不同的参数。我将在这里介绍最常用的版本。
如果我们必须加密或者解密单个数据块,则只需对带有数据的doFinal()
进行加密或者解密即可。这是一个加密示例:
byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); byte[] cipherText = cipher.doFinal(plainText);
在解密数据的情况下,代码实际上看起来几乎相同。请记住,必须将Cipher
实例初始化为解密模式。这是解密单个密文块的样子:
byte[] plainText = cipher.doFinal(cipherText);
如果我们必须加密或者解密多个数据块,例如在一个大文件中包含多个块时,我们需要为每个数据块调用一次update(),最后对最后一个数据块调用doFinal()。这是加密多个数据块的示例:
byte[] data1 = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); byte[] data2 = "zyxwvutsrqponmlkjihgfedcba".getBytes("UTF-8"); byte[] data3 = "01234567890123456789012345".getBytes("UTF-8"); byte[] cipherText1 = cipher.update(data1); byte[] cipherText2 = cipher.update(data2); byte[] cipherText3 = cipher.doFinal(data3);
最后一个数据块需要调用doFinal()
的原因是,某些加密算法需要填充数据以适合特定的密码块大小(例如8字节边界)。但是我们不想填充加密的中间数据块。因此,对中间数据块调用" update()",对最后一个数据块调用" doFinal()"。
解密多个数据块时,我们还可以对中间数据块调用Cipher``update()
方法,对最后一个块调用doFinal()
方法。这是一个使用JavaCipher
实例解密多个数据块的示例:
byte[] plainText1 = cipher.update(cipherText1); byte[] plainText2 = cipher.update(cipherText2); byte[] plainText3 = cipher.doFinal(cipherText3);
同样,必须将Cipher
实例初始化为解密模式,此示例才能正常工作。
加密/解密字节数组的一部分
Java" Cipher"类的加密和解密方法可以对存储在" byte"数组中的部分数据进行加密或者解密。我们只需将偏移量和长度传递给update()
和/或者doFinal()
方法。这是一个例子:
int offset = 10; int length = 24; byte[] cipherText = cipher.doFinal(data, offset, length);
该示例将从具有索引8和24字节的字节开始加密(或者解密,具体取决于" Cipher"的初始化)。
加密/解密为现有的字节数组
到目前为止,本教程中显示的所有加密和解密示例都已将加密或者解密的数据返回到新的字节数组中。但是,也可以将数据加密或者解密为现有的字节数组。这对于减少创建的字节数组的数量很有用。
我们可以通过将目标字节数组作为参数传递给update()和/或者doFinal()方法来将数据加密或者解密为现有的字节数组。这是一个例子:
int offset = 10; int length = 24; byte[] dest = new byte[1024]; cipher.doFinal(data, offset, length, dest);
此示例将索引为10和24字节的字节中的数据加密,从偏移量0转发到"目标"字节数组。如果要为"目标"字节数组设置不同的偏移量,则有一个版本的update()
和doFinal()
额外需要一个偏移量参数。这是一个调用doFinal()
方法并将其偏移到dest
数组中的示例:
int offset = 10; int length = 24; byte[] dest = new byte[1024]; int destOffset = 12 cipher.doFinal(data, offset, length, dest, destOffset);
重用密码实例
初始化JavaCipher
实例是一项昂贵的操作。因此,重用Cipher
实例是一个好主意。幸运的是,Cipher
类的设计考虑了重用。
当我们在Cipher
实例上调用doFinal()
方法时,Cipher
实例将返回到初始化后的状态。然后,可以使用"密码"实例再次加密或者解密更多数据。
这是一个重用JavaCipher
实例的例子:
Cipher cipher = Cipher.getInstance("AES"); Key key = ... // get / create symmetric encryption key cipher.init(Cipher.ENCRYPT_MODE, key); byte[] data1 = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); byte[] data2 = "zyxwvutsrqponmlkjihgfedcba".getBytes("UTF-8"); byte[] cipherText1 = cipher.update(data1); byte[] cipherText2 = cipher.doFinal(data2); byte[] data3 = "01234567890123456789012345".getBytes("UTF-8"); byte[] cipherText3 = cipher.doFinal(data3);
首先,创建并初始化Cipher
实例,然后将其用于加密两个连贯的数据块。注意,对这两个数据块的调用分别是update()和doFinal()。现在," Cipher"实例可以再次用于加密更多数据。这是通过对第三个数据块的" doFinal()"调用来完成的。调用完" doFinal()"之后,我们可以使用相同的Java" Cipher"实例加密另一个数据块。