PageRenderTime 27ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/prov/src/test/java/org/bouncycastle/jce/provider/test/CipherStreamTest2.java

https://gitlab.com/edgardo001/bc-java
Java | 519 lines | 424 code | 59 blank | 36 comment | 44 complexity | 7123ee3849b88c66828ed9a284a565dc MD5 | raw file
  1. package org.bouncycastle.jce.provider.test;
  2. import java.io.*;
  3. import java.security.Key;
  4. import java.security.Security;
  5. import javax.crypto.*;
  6. import javax.crypto.spec.IvParameterSpec;
  7. import org.bouncycastle.crypto.io.InvalidCipherTextIOException;
  8. import org.bouncycastle.jcajce.io.CipherInputStream;
  9. import org.bouncycastle.jcajce.io.CipherOutputStream;
  10. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  11. import org.bouncycastle.util.Arrays;
  12. import org.bouncycastle.util.test.SimpleTest;
  13. public class CipherStreamTest2
  14. extends SimpleTest
  15. {
  16. private int streamSize;
  17. public String getName()
  18. {
  19. return "CipherStreamTest2";
  20. }
  21. private void testModes(String algo, String[] transforms, boolean authenticated)
  22. throws Exception
  23. {
  24. Key key = generateKey(algo);
  25. for (int i = 0; i != transforms.length; i++)
  26. {
  27. String transform = transforms[i];
  28. String cipherName = algo + transform;
  29. boolean cts = transform.indexOf("CTS") > -1;
  30. if (cts && streamSize < Cipher.getInstance(cipherName, "BC").getBlockSize())
  31. {
  32. continue;
  33. }
  34. testWriteRead(cipherName, key, authenticated, true, false);
  35. testWriteRead(cipherName, key, authenticated, true, true);
  36. testWriteRead(cipherName, key, authenticated, false, false);
  37. testWriteRead(cipherName, key, authenticated, false, true);
  38. testReadWrite(cipherName, key, authenticated, true, false);
  39. testReadWrite(cipherName, key, authenticated, true, true);
  40. testReadWrite(cipherName, key, authenticated, false, false);
  41. testReadWrite(cipherName, key, authenticated, false, true);
  42. if (!cts)
  43. {
  44. testWriteReadEmpty(cipherName, key, authenticated, true, false);
  45. testWriteReadEmpty(cipherName, key, authenticated, true, true);
  46. testWriteReadEmpty(cipherName, key, authenticated, false, false);
  47. testWriteReadEmpty(cipherName, key, authenticated, false, true);
  48. }
  49. if (authenticated)
  50. {
  51. testTamperedRead(cipherName, key, true, true);
  52. testTamperedRead(cipherName, key, true, false);
  53. testTruncatedRead(cipherName, key, true, true);
  54. testTruncatedRead(cipherName, key, true, false);
  55. testTamperedWrite(cipherName, key, true, true);
  56. testTamperedWrite(cipherName, key, true, false);
  57. }
  58. }
  59. }
  60. private InputStream createInputStream(byte[] data, Cipher cipher, boolean useBc)
  61. {
  62. ByteArrayInputStream bytes = new ByteArrayInputStream(data);
  63. // cast required for earlier JDK
  64. return useBc ? (InputStream)new CipherInputStream(bytes, cipher) : (InputStream)new javax.crypto.CipherInputStream(bytes, cipher);
  65. }
  66. private OutputStream createOutputStream(ByteArrayOutputStream bytes, Cipher cipher, boolean useBc)
  67. {
  68. // cast required for earlier JDK
  69. return useBc ? (OutputStream)new CipherOutputStream(bytes, cipher) : (OutputStream)new javax.crypto.CipherOutputStream(bytes, cipher);
  70. }
  71. /**
  72. * Test tampering of ciphertext followed by read from decrypting CipherInputStream
  73. */
  74. private void testTamperedRead(String name, Key key, boolean authenticated, boolean useBc)
  75. throws Exception
  76. {
  77. Cipher encrypt = Cipher.getInstance(name, "BC");
  78. Cipher decrypt = Cipher.getInstance(name, "BC");
  79. encrypt.init(Cipher.ENCRYPT_MODE, key);
  80. if (encrypt.getIV() != null)
  81. {
  82. decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encrypt.getIV()));
  83. }
  84. else
  85. {
  86. decrypt.init(Cipher.DECRYPT_MODE, key);
  87. }
  88. byte[] ciphertext = encrypt.doFinal(new byte[streamSize]);
  89. // Tamper
  90. ciphertext[0] += 1;
  91. InputStream input = createInputStream(ciphertext, decrypt, useBc);
  92. try
  93. {
  94. while (input.read() >= 0)
  95. {
  96. }
  97. fail("Expected invalid ciphertext after tamper and read : " + name, authenticated, useBc);
  98. }
  99. catch (InvalidCipherTextIOException e)
  100. {
  101. // Expected
  102. }
  103. catch (IOException e) // cause will be AEADBadTagException
  104. {
  105. // Expected
  106. }
  107. try
  108. {
  109. input.close();
  110. }
  111. catch (Exception e)
  112. {
  113. fail("Unexpected exception : " + name, e, authenticated, useBc);
  114. }
  115. }
  116. /**
  117. * Test truncation of ciphertext to make tag calculation impossible, followed by read from
  118. * decrypting CipherInputStream
  119. */
  120. private void testTruncatedRead(String name, Key key, boolean authenticated, boolean useBc)
  121. throws Exception
  122. {
  123. Cipher encrypt = Cipher.getInstance(name, "BC");
  124. Cipher decrypt = Cipher.getInstance(name, "BC");
  125. encrypt.init(Cipher.ENCRYPT_MODE, key);
  126. if (encrypt.getIV() != null)
  127. {
  128. decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encrypt.getIV()));
  129. }
  130. else
  131. {
  132. decrypt.init(Cipher.DECRYPT_MODE, key);
  133. }
  134. byte[] ciphertext = encrypt.doFinal(new byte[streamSize]);
  135. // Truncate to just smaller than complete tag
  136. byte[] truncated = new byte[ciphertext.length - streamSize - 1];
  137. System.arraycopy(ciphertext, 0, truncated, 0, truncated.length);
  138. // Tamper
  139. ciphertext[0] += 1;
  140. InputStream input = createInputStream(truncated, decrypt, useBc);
  141. while (true)
  142. {
  143. int read = 0;
  144. try
  145. {
  146. read = input.read();
  147. }
  148. catch (InvalidCipherTextIOException e)
  149. {
  150. // Expected
  151. break;
  152. }
  153. catch (IOException e)
  154. {
  155. // Expected from JDK 1.7 on
  156. break;
  157. }
  158. catch (Exception e)
  159. {
  160. fail("Unexpected exception : " + name, e, authenticated, useBc);
  161. break;
  162. }
  163. if (read < 0)
  164. {
  165. fail("Expected invalid ciphertext after truncate and read : " + name, authenticated, useBc);
  166. break;
  167. }
  168. }
  169. try
  170. {
  171. input.close();
  172. }
  173. catch (Exception e)
  174. {
  175. fail("Unexpected exception : " + name, e, authenticated, useBc);
  176. }
  177. }
  178. /**
  179. * Test tampering of ciphertext followed by write to decrypting CipherOutputStream
  180. */
  181. private void testTamperedWrite(String name, Key key, boolean authenticated, boolean useBc)
  182. throws Exception
  183. {
  184. Cipher encrypt = Cipher.getInstance(name, "BC");
  185. Cipher decrypt = Cipher.getInstance(name, "BC");
  186. encrypt.init(Cipher.ENCRYPT_MODE, key);
  187. if (encrypt.getIV() != null)
  188. {
  189. decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encrypt.getIV()));
  190. }
  191. else
  192. {
  193. decrypt.init(Cipher.DECRYPT_MODE, key);
  194. }
  195. byte[] ciphertext = encrypt.doFinal(new byte[streamSize]);
  196. // Tamper
  197. ciphertext[0] += 1;
  198. ByteArrayOutputStream plaintext = new ByteArrayOutputStream();
  199. OutputStream output = createOutputStream(plaintext, decrypt, useBc);
  200. for (int i = 0; i < ciphertext.length; i++)
  201. {
  202. output.write(ciphertext[i]);
  203. }
  204. try
  205. {
  206. output.close();
  207. fail("Expected invalid ciphertext after tamper and write : " + name, authenticated, useBc);
  208. }
  209. catch (InvalidCipherTextIOException e)
  210. {
  211. // Expected
  212. }
  213. }
  214. /**
  215. * Test CipherOutputStream in ENCRYPT_MODE, CipherInputStream in DECRYPT_MODE
  216. */
  217. private void testWriteRead(String name, Key key, boolean authenticated, boolean useBc, boolean blocks)
  218. throws Exception
  219. {
  220. byte[] data = new byte[streamSize];
  221. for (int i = 0; i < data.length; i++)
  222. {
  223. data[i] = (byte)(i % 255);
  224. }
  225. testWriteRead(name, key, authenticated, useBc, blocks, data);
  226. }
  227. /**
  228. * Test CipherOutputStream in ENCRYPT_MODE, CipherInputStream in DECRYPT_MODE
  229. */
  230. private void testWriteReadEmpty(String name, Key key, boolean authenticated, boolean useBc, boolean blocks)
  231. throws Exception
  232. {
  233. byte[] data = new byte[0];
  234. testWriteRead(name, key, authenticated, useBc, blocks, data);
  235. }
  236. private void testWriteRead(String name, Key key, boolean authenticated, boolean useBc, boolean blocks, byte[] data)
  237. {
  238. ByteArrayOutputStream bOut = new ByteArrayOutputStream();
  239. try
  240. {
  241. Cipher encrypt = Cipher.getInstance(name, "BC");
  242. Cipher decrypt = Cipher.getInstance(name, "BC");
  243. encrypt.init(Cipher.ENCRYPT_MODE, key);
  244. if (encrypt.getIV() != null)
  245. {
  246. decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encrypt.getIV()));
  247. }
  248. else
  249. {
  250. decrypt.init(Cipher.DECRYPT_MODE, key);
  251. }
  252. OutputStream cOut = createOutputStream(bOut, encrypt, useBc);
  253. if (blocks)
  254. {
  255. int chunkSize = Math.max(1, data.length / 8);
  256. for (int i = 0; i < data.length; i += chunkSize)
  257. {
  258. cOut.write(data, i, Math.min(chunkSize, data.length - i));
  259. }
  260. }
  261. else
  262. {
  263. for (int i = 0; i < data.length; i++)
  264. {
  265. cOut.write(data[i]);
  266. }
  267. }
  268. cOut.close();
  269. byte[] cipherText = bOut.toByteArray();
  270. bOut.reset();
  271. InputStream cIn = createInputStream(cipherText, decrypt, useBc);
  272. if (blocks)
  273. {
  274. byte[] block = new byte[encrypt.getBlockSize() + 1];
  275. int c;
  276. while ((c = cIn.read(block)) >= 0)
  277. {
  278. bOut.write(block, 0, c);
  279. }
  280. }
  281. else
  282. {
  283. int c;
  284. while ((c = cIn.read()) >= 0)
  285. {
  286. bOut.write(c);
  287. }
  288. }
  289. cIn.close();
  290. }
  291. catch (Exception e)
  292. {
  293. fail("Unexpected exception " + name, e, authenticated, useBc);
  294. }
  295. byte[] decrypted = bOut.toByteArray();
  296. if (!Arrays.areEqual(data, decrypted))
  297. {
  298. fail("Failed - decrypted data doesn't match: " + name, authenticated, useBc);
  299. }
  300. }
  301. protected void fail(String message, boolean authenticated, boolean bc)
  302. {
  303. if (bc || !authenticated)
  304. {
  305. super.fail(message);
  306. }
  307. else
  308. {
  309. // javax.crypto.CipherInputStream/CipherOutputStream
  310. // are broken wrt handling AEAD failures
  311. // System.err.println("Broken JCE Streams: " + message);
  312. }
  313. }
  314. protected void fail(String message, Throwable throwable, boolean authenticated, boolean bc)
  315. {
  316. if (bc || !authenticated)
  317. {
  318. super.fail(message, throwable);
  319. }
  320. else
  321. {
  322. // javax.crypto.CipherInputStream/CipherOutputStream
  323. // are broken wrt handling AEAD failures
  324. //System.err.println("Broken JCE Streams: " + message + " : " + throwable);
  325. throwable.printStackTrace();
  326. }
  327. }
  328. /**
  329. * Test CipherInputStream in ENCRYPT_MODE, CipherOutputStream in DECRYPT_MODE
  330. */
  331. private void testReadWrite(String name, Key key, boolean authenticated, boolean useBc, boolean blocks)
  332. throws Exception
  333. {
  334. String lCode = "ABCDEFGHIJKLMNOPQRSTU";
  335. ByteArrayOutputStream bOut = new ByteArrayOutputStream();
  336. try
  337. {
  338. Cipher in = Cipher.getInstance(name, "BC");
  339. Cipher out = Cipher.getInstance(name, "BC");
  340. in.init(Cipher.ENCRYPT_MODE, key);
  341. if (in.getIV() != null)
  342. {
  343. out.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(in.getIV()));
  344. }
  345. else
  346. {
  347. out.init(Cipher.DECRYPT_MODE, key);
  348. }
  349. InputStream cIn = createInputStream(lCode.getBytes(), in, useBc);
  350. OutputStream cOut = createOutputStream(bOut, out, useBc);
  351. if (blocks)
  352. {
  353. byte[] block = new byte[in.getBlockSize() + 1];
  354. int c;
  355. while ((c = cIn.read(block)) >= 0)
  356. {
  357. cOut.write(block, 0, c);
  358. }
  359. }
  360. else
  361. {
  362. int c;
  363. while ((c = cIn.read()) >= 0)
  364. {
  365. cOut.write(c);
  366. }
  367. }
  368. cIn.close();
  369. cOut.flush();
  370. cOut.close();
  371. }
  372. catch (Exception e)
  373. {
  374. fail("Unexpected exception " + name, e, authenticated, useBc);
  375. }
  376. String res = new String(bOut.toByteArray());
  377. if (!res.equals(lCode))
  378. {
  379. fail("Failed - decrypted data doesn't match: " + name, authenticated, useBc);
  380. }
  381. }
  382. private static Key generateKey(String name)
  383. throws Exception
  384. {
  385. KeyGenerator kGen;
  386. if (name.indexOf('/') < 0)
  387. {
  388. kGen = KeyGenerator.getInstance(name, "BC");
  389. }
  390. else
  391. {
  392. kGen = KeyGenerator.getInstance(name.substring(0, name.indexOf('/')), "BC");
  393. }
  394. return kGen.generateKey();
  395. }
  396. public void performTest()
  397. throws Exception
  398. {
  399. int[] testSizes = new int[]{0, 1, 7, 8, 9, 15, 16, 17, 1023, 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097};
  400. for (int i = 0; i < testSizes.length; i++)
  401. {
  402. this.streamSize = testSizes[i];
  403. performTests();
  404. }
  405. }
  406. private void performTests()
  407. throws Exception
  408. {
  409. final String[] blockCiphers64 = new String[]{"BLOWFISH", "DES", "DESEDE", "TEA", "CAST5", "RC2", "XTEA"};
  410. for (int i = 0; i != blockCiphers64.length; i++)
  411. {
  412. testModes(blockCiphers64[i], new String[]{
  413. "/ECB/PKCS5Padding",
  414. "/CBC/PKCS5Padding",
  415. "/OFB/NoPadding",
  416. "/CFB/NoPadding",
  417. "/CTS/NoPadding",}, false);
  418. testModes(blockCiphers64[i], new String[]{"/EAX/NoPadding"}, true);
  419. }
  420. final String[] blockCiphers128 = new String[]{
  421. "AES",
  422. "NOEKEON",
  423. "Twofish",
  424. "CAST6",
  425. "SEED",
  426. "Serpent",
  427. "RC6",
  428. "CAMELLIA"};
  429. for (int i = 0; i != blockCiphers128.length; i++)
  430. {
  431. testModes(blockCiphers128[i], new String[]{
  432. "/ECB/PKCS5Padding",
  433. "/CBC/PKCS5Padding",
  434. "/OFB/NoPadding",
  435. "/CFB/NoPadding",
  436. "/CTS/NoPadding",
  437. "/CTR/NoPadding",
  438. "/SIC/NoPadding"}, false);
  439. testModes(blockCiphers128[i], new String[]{"/CCM/NoPadding", "/EAX/NoPadding", "/GCM/NoPadding", "/OCB/NoPadding"}, true);
  440. }
  441. final String[] streamCiphers = new String[]{
  442. "ARC4",
  443. "SALSA20",
  444. "XSalsa20",
  445. "ChaCha",
  446. "Grainv1",
  447. "Grain128",
  448. "HC128",
  449. "HC256"};
  450. for (int i = 0; i != streamCiphers.length; i++)
  451. {
  452. testModes(streamCiphers[i], new String[]{""}, false);
  453. }
  454. }
  455. public static void main(String[] args)
  456. {
  457. Security.addProvider(new BouncyCastleProvider());
  458. runTest(new CipherStreamTest2());
  459. }
  460. }