如果 Cipher 编码失败,重试也会失败
If Cipher fails to encode, retries will also fail
假设您有一些字符串要用 public 密钥加密,然后用 dectrypt 加密,它们彼此不相关。但是其中一个没有达到标准(它比密钥位长可以处理的长),你想抛出一个错误并继续解码其他人。
嗯,问题是如果抛出 Data must not be longer than x bytes
异常,那么 下一个也会失败 。
为什么会发生这种情况,我该如何防止它发生?
我制作了一个可用于重现错误的不言自明的代码:
Cipher encrypter;
Cipher decrypter;
Key pubKey;
Key privKey;
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
pubKey = kp.getPublic();
privKey = kp.getPrivate();
encrypter = Cipher.getInstance("RSA");
encrypter.init(Cipher.ENCRYPT_MODE, pubKey);
decrypter = Cipher.getInstance("RSA");
decrypter.init(Cipher.DECRYPT_MODE, privKey);
byte[] encryptedData;
byte[] decryptedData;
System.out.println("Starting short test 1");
encryptedData = encrypter.doFinal("SHORT TEST 1".getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); //SHORT TEST
System.out.println("Starting short test 2");
encryptedData = encrypter.doFinal("SHORT TEST 2".getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); //SHORT TEST 2
System.out.println("Starting short test 3");
encryptedData = encrypter.doFinal("SHORT TEST 3".getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); //SHORT TEST 3
try {
encryptedData = encrypter.doFinal(("LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST" +
" LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST" +
" LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST ").getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); // IT DOESN'T REACH HERE, WHICH IS OK
} catch (IllegalBlockSizeException e){
System.out.println(e.toString()); // Data must not be longer than 117 bytes (OK, fair enough, my bad)
}
System.out.println("Starting short test 4");
encryptedData = encrypter.doFinal("SHORT TEST 4".getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); // THROWS THE SAME EXCEPTION THAN IN PREVIOUS TEST
控制台打印:
Starting short test 1
SHORT TEST 1
Starting short test 2
SHORT TEST 2
Starting short test 3
SHORT TEST 3
javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
Starting short test 4
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
at java.base/com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:347)
at java.base/com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:392)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
at main.main(main.java:66)
doFinal() documentation states that:
Note: if any exception is thrown, this cipher object may need to be
reset before it can be used again.
由于没有 reset
方法,我假设您必须再次调用 init
。
当然可以,但有几点需要考虑。
应尽可能避免异常(在发布的代码中)。重新加密相同的消息将不起作用,因为您会得到相同的异常,因此对于该特定消息,您将无法选择。如果您使用混合密码术(下一点),那么您可以轻松避免此问题。
RSA 不适用于传输大量数据。了解如何结合 AES/GCM 和 RSA/OAEP 来创建一个 混合密码系统,可以有效地 encrypt/decrypt 任意数量的数据。
Cipher
class 的实例通常 轻量级 。它们的实例化和初始化相对便宜,并且不携带太多 state。 因此,简单地生成一个新实例比重新使用旧实例更不容易出错。
例如,这意味着您应该将 键 存储在 class 的字段中, 而不是 Cipher
实例本身。由于密钥是不可变的,这意味着 class 的状态仍然有效,无论 Cipher
实例发生什么。
关于代码/密码学:
kpg.initialize(1024);
这不再被认为是足够好的密钥大小。尝试 3072 或更高版本。或者使用椭圆曲线密码术 (ECC)。
encrypter = Cipher.getInstance("RSA");
永远不要忘记指定 完整 算法并且不要使用默认值。以上使用 "RSA/ECB/PKCS1Padding"
但 OAEP 应该是首选(但开销更大)。
String.getBytes()
和 new String(byte[])
:总是从 StandardCharsets
指定一个字符集,除非你被绑定到平台默认编码(以及随后在另一个平台上的解码错误)。
假设您有一些字符串要用 public 密钥加密,然后用 dectrypt 加密,它们彼此不相关。但是其中一个没有达到标准(它比密钥位长可以处理的长),你想抛出一个错误并继续解码其他人。
嗯,问题是如果抛出 Data must not be longer than x bytes
异常,那么 下一个也会失败 。
为什么会发生这种情况,我该如何防止它发生?
我制作了一个可用于重现错误的不言自明的代码:
Cipher encrypter;
Cipher decrypter;
Key pubKey;
Key privKey;
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
pubKey = kp.getPublic();
privKey = kp.getPrivate();
encrypter = Cipher.getInstance("RSA");
encrypter.init(Cipher.ENCRYPT_MODE, pubKey);
decrypter = Cipher.getInstance("RSA");
decrypter.init(Cipher.DECRYPT_MODE, privKey);
byte[] encryptedData;
byte[] decryptedData;
System.out.println("Starting short test 1");
encryptedData = encrypter.doFinal("SHORT TEST 1".getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); //SHORT TEST
System.out.println("Starting short test 2");
encryptedData = encrypter.doFinal("SHORT TEST 2".getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); //SHORT TEST 2
System.out.println("Starting short test 3");
encryptedData = encrypter.doFinal("SHORT TEST 3".getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); //SHORT TEST 3
try {
encryptedData = encrypter.doFinal(("LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST" +
" LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST" +
" LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST LONG TEST ").getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); // IT DOESN'T REACH HERE, WHICH IS OK
} catch (IllegalBlockSizeException e){
System.out.println(e.toString()); // Data must not be longer than 117 bytes (OK, fair enough, my bad)
}
System.out.println("Starting short test 4");
encryptedData = encrypter.doFinal("SHORT TEST 4".getBytes());
decryptedData = decrypter.doFinal(encryptedData);
System.out.println(new String(decryptedData)); // THROWS THE SAME EXCEPTION THAN IN PREVIOUS TEST
控制台打印:
Starting short test 1
SHORT TEST 1
Starting short test 2
SHORT TEST 2
Starting short test 3
SHORT TEST 3
javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
Starting short test 4
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
at java.base/com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:347)
at java.base/com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:392)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
at main.main(main.java:66)
doFinal() documentation states that:
Note: if any exception is thrown, this cipher object may need to be reset before it can be used again.
由于没有 reset
方法,我假设您必须再次调用 init
。
应尽可能避免异常(在发布的代码中)。重新加密相同的消息将不起作用,因为您会得到相同的异常,因此对于该特定消息,您将无法选择。如果您使用混合密码术(下一点),那么您可以轻松避免此问题。
RSA 不适用于传输大量数据。了解如何结合 AES/GCM 和 RSA/OAEP 来创建一个 混合密码系统,可以有效地 encrypt/decrypt 任意数量的数据。
Cipher
class 的实例通常 轻量级 。它们的实例化和初始化相对便宜,并且不携带太多 state。 因此,简单地生成一个新实例比重新使用旧实例更不容易出错。
例如,这意味着您应该将 键 存储在 class 的字段中, 而不是 Cipher
实例本身。由于密钥是不可变的,这意味着 class 的状态仍然有效,无论 Cipher
实例发生什么。
关于代码/密码学:
kpg.initialize(1024);
这不再被认为是足够好的密钥大小。尝试 3072 或更高版本。或者使用椭圆曲线密码术 (ECC)。
encrypter = Cipher.getInstance("RSA");
永远不要忘记指定 完整 算法并且不要使用默认值。以上使用 "RSA/ECB/PKCS1Padding"
但 OAEP 应该是首选(但开销更大)。
String.getBytes()
和 new String(byte[])
:总是从 StandardCharsets
指定一个字符集,除非你被绑定到平台默认编码(以及随后在另一个平台上的解码错误)。