View Javadoc
1    /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.commons.crypto.cipher;
19  
20  
21  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.fail;
26  
27  import java.nio.ByteBuffer;
28  import java.security.InvalidAlgorithmParameterException;
29  import java.security.InvalidKeyException;
30  import java.security.SecureRandom;
31  import java.util.Properties;
32  import java.util.Random;
33  
34  import javax.crypto.Cipher;
35  import javax.crypto.spec.GCMParameterSpec;
36  import javax.crypto.spec.IvParameterSpec;
37  import javax.crypto.spec.SecretKeySpec;
38  import javax.xml.bind.DatatypeConverter;
39  
40  import org.apache.commons.crypto.utils.AES;
41  import org.apache.commons.crypto.utils.ReflectionUtils;
42  import org.junit.jupiter.api.BeforeEach;
43  import org.junit.jupiter.api.Test;
44  
45  public abstract class AbstractCipherTest {
46  
47  	public static final String OPENSSL_CIPHER_CLASSNAME = OpenSslCipher.class.getName();
48  
49  	public static final String JCE_CIPHER_CLASSNAME = JceCipher.class.getName();
50  
51  	// data
52  	public static final int BYTEBUFFER_SIZE = 1000;
53  
54  	// cipher
55  	static final byte[] KEY = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14,
56  			0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24 };
57  	static final byte[] IV = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
58  			0x08 };
59  	public String[] cipherTests;
60  	private Properties props;
61  
62  	protected String cipherClass;
63  	protected String[] transformations;
64  	private CryptoCipher enc, dec;
65  
66  	/** test byte array whose data is randomly generated */
67  	private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv) throws Exception {
68  		final int blockSize = enc.getBlockSize();
69  
70  		// AES_CBC_NOPADDING only accepts data whose size is the multiple of
71  		// block size
72  		final int[] dataLenList = transformation.equals(AES.CBC_NO_PADDING) ? new int[] { 10 * 1024 }
73  				: new int[] { 10 * 1024, 10 * 1024 - 3 };
74  		for (final int dataLen : dataLenList) {
75  			final byte[] plainText = new byte[dataLen];
76  			final Random random = new SecureRandom();
77  			random.nextBytes(plainText);
78  			final byte[] cipherText = new byte[dataLen + blockSize];
79  
80  			// check update method with inputs whose sizes are the multiple of
81  			// block size or not
82  			final int[] bufferLenList = { 2 * 1024 - 128, 2 * 1024 - 125 };
83  			for (final int bufferLen : bufferLenList) {
84  				resetCipher(transformation, key, iv);
85  
86  				int offset = 0;
87  				// encrypt (update + doFinal) the data
88  				int cipherPos = 0;
89  				for (int i = 0; i < dataLen / bufferLen; i++) {
90  					cipherPos += enc.update(plainText, offset, bufferLen, cipherText, cipherPos);
91  					offset += bufferLen;
92  				}
93  				cipherPos += enc.doFinal(plainText, offset, dataLen % bufferLen, cipherText, cipherPos);
94  
95  				offset = 0;
96  				// decrypt (update + doFinal) the data
97  				final byte[] realPlainText = new byte[cipherPos + blockSize];
98  				int plainPos = 0;
99  				for (int i = 0; i < cipherPos / bufferLen; i++) {
100 					plainPos += dec.update(cipherText, offset, bufferLen, realPlainText, plainPos);
101 					offset += bufferLen;
102 				}
103 				plainPos += dec.doFinal(cipherText, offset, cipherPos % bufferLen, realPlainText, plainPos);
104 
105 				// verify
106 				assertEquals(dataLen, plainPos, "random byte array length changes after transformation");
107 
108 				final byte[] shrinkPlainText = new byte[plainPos];
109 				System.arraycopy(realPlainText, 0, shrinkPlainText, 0, plainPos);
110 				assertArrayEquals(plainText, shrinkPlainText, "random byte array contents changes after transformation");
111 			}
112 		}
113 	}
114 
115 	/** test byte array whose data is planned in {@link TestData} */
116 	private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv, final byte[] input,
117 			final byte[] output) throws Exception {
118 		resetCipher(transformation, key, iv);
119 		final int blockSize = enc.getBlockSize();
120 
121 		byte[] temp = new byte[input.length + blockSize];
122 		final int n = enc.doFinal(input, 0, input.length, temp, 0);
123 		final byte[] cipherText = new byte[n];
124 		System.arraycopy(temp, 0, cipherText, 0, n);
125 		assertArrayEquals(output, cipherText, "byte array encryption error.");
126 
127 		temp = new byte[cipherText.length + blockSize];
128 		final int m = dec.doFinal(cipherText, 0, cipherText.length, temp, 0);
129 		final byte[] plainText = new byte[m];
130 		System.arraycopy(temp, 0, plainText, 0, m);
131 		assertArrayEquals(input, plainText, "byte array decryption error.");
132 	}
133 
134 	private void byteBufferTest(final String transformation, final byte[] key, final byte[] iv, final ByteBuffer input,
135 			final ByteBuffer output) throws Exception {
136 		final ByteBuffer decResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE);
137 		final ByteBuffer encResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE);
138 
139 		try (final CryptoCipher enc = getCipher(transformation); final CryptoCipher dec = getCipher(transformation)) {
140 
141 			enc.init(Cipher.ENCRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
142 			dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
143 
144 			//
145 			// encryption pass
146 			//
147 			enc.doFinal(input, encResult);
148 			input.flip();
149 			encResult.flip();
150 			if (!output.equals(encResult)) {
151 				final byte[] b = new byte[output.remaining()];
152 				output.get(b);
153 				final byte[] c = new byte[encResult.remaining()];
154 				encResult.get(c);
155 				fail("AES failed encryption - expected " + DatatypeConverter.printHexBinary(b)
156 						+ " got " + DatatypeConverter.printHexBinary(c));
157 			}
158 
159 			//
160 			// decryption pass
161 			//
162 			dec.doFinal(encResult, decResult);
163 			decResult.flip();
164 
165 			if (!input.equals(decResult)) {
166 				final byte[] inArray = new byte[input.remaining()];
167 				final byte[] decResultArray = new byte[decResult.remaining()];
168 				input.get(inArray);
169 				decResult.get(decResultArray);
170 				fail();
171 			}
172 		}
173 	}
174 
175 	@Test
176 	public void closeTestAfterInit() throws Exception {
177 		// This test deliberately does not use try with resources in order to control
178 		// the sequence of operations exactly
179         try (final CryptoCipher enc = getCipher(transformations[0])) {
180             enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
181         }
182 	}
183 
184     @Test
185 	public void closeTestNoInit() throws Exception {
186 		// This test deliberately does not use try with resources in order to control
187 		// the sequence of operations exactly
188 		try (final CryptoCipher enc = getCipher(transformations[0])) {
189 		    // empty
190 		}
191 	}
192 
193 	@Test
194 	public void closeTestRepeat() throws Exception {
195 		// This test deliberately does not use try with resources in order to control
196 		// the sequence of operations exactly
197         try (final CryptoCipher enc = getCipher(transformations[0])) {
198             enc.close();
199             enc.close(); // repeat the close
200         }
201 	}
202 
203 	@Test
204 	public void cryptoTest() throws Exception {
205 		for (final String tran : transformations) {
206 			/** uses the small data set in {@link TestData} */
207 			cipherTests = TestData.getTestData(tran);
208 			assertNotNull(cipherTests, tran);
209 			for (int i = 0; i != cipherTests.length; i += 5) {
210 				final byte[] key = DatatypeConverter.parseHexBinary(cipherTests[i + 1]);
211 				final byte[] iv = DatatypeConverter.parseHexBinary(cipherTests[i + 2]);
212 
213 				final byte[] inputBytes = DatatypeConverter.parseHexBinary(cipherTests[i + 3]);
214 				final byte[] outputBytes = DatatypeConverter.parseHexBinary(cipherTests[i + 4]);
215 
216 				final ByteBuffer inputBuffer = ByteBuffer.allocateDirect(inputBytes.length);
217 				final ByteBuffer outputBuffer = ByteBuffer.allocateDirect(outputBytes.length);
218 				inputBuffer.put(inputBytes);
219 				inputBuffer.flip();
220 				outputBuffer.put(outputBytes);
221 				outputBuffer.flip();
222 
223 				byteBufferTest(tran, key, iv, inputBuffer, outputBuffer);
224 				byteArrayTest(tran, key, iv, inputBytes, outputBytes);
225 			}
226 
227 			/** uses randomly generated big data set */
228 			byteArrayTest(tran, KEY, IV);
229 		}
230 	}
231 
232 	private CryptoCipher getCipher(final String transformation) throws ClassNotFoundException {
233 		return (CryptoCipher) ReflectionUtils.newInstance(ReflectionUtils.getClassByName(cipherClass), props,
234 				transformation);
235 	}
236 
237 	protected abstract void init();
238 
239 	SecretKeySpec newSecretKeySpec() {
240         return AES.newSecretKeySpec(KEY);
241     }
242 
243 	@Test
244     public void reInitAfterClose() throws Exception {
245         // This test deliberately does not use try with resources in order to control
246         // the sequence of operations exactly
247         try (final CryptoCipher enc = getCipher(transformations[0])) {
248             enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
249             enc.close();
250             enc.init(Cipher.DECRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
251         }
252     }
253 
254 	@Test
255 	public void reInitTest() throws Exception {
256 		// This test deliberately does not use try with resources in order to control
257 		// the sequence of operations exactly
258         try (final CryptoCipher enc = getCipher(transformations[0])) {
259             enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
260             enc.init(Cipher.DECRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
261             enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
262         }
263 	}
264 
265 	private void resetCipher(final String transformation, final byte[] key, final byte[] iv)
266 			throws ClassNotFoundException, InvalidKeyException, InvalidAlgorithmParameterException {
267 		enc = getCipher(transformation);
268 		dec = getCipher(transformation);
269 
270 		enc.init(Cipher.ENCRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
271 
272 		dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
273 	}
274 
275 	@BeforeEach
276 	public void setup() {
277 		init();
278 		assertNotNull(cipherClass, "cipherClass");
279 		assertNotNull(transformations, "transformations");
280 		props = new Properties();
281 		props.setProperty(CryptoCipherFactory.CLASSES_KEY, cipherClass);
282 	}
283 
284 	@Test
285 	public void testInvalidIV() throws Exception {
286 		for (final String transform : transformations) {
287 			try (final CryptoCipher cipher = getCipher(transform)) {
288 				assertNotNull(cipher);
289 				final byte[] invalidIV = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
290 						0x0d, 0x0e, 0x0f, 0x11 };
291 				assertThrows(InvalidAlgorithmParameterException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(invalidIV)));
292 			}
293 		}
294 	}
295 
296 	@Test
297 	public void testInvalidIVClass() throws Exception {
298 		for (final String transform : transformations) {
299 			try (final CryptoCipher cipher = getCipher(transform)) {
300 				assertNotNull(cipher);
301 				assertThrows(InvalidAlgorithmParameterException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, newSecretKeySpec(), new GCMParameterSpec(IV.length, IV)));
302 			}
303 		}
304 	}
305 
306 	@Test
307 	public void testInvalidKey() throws Exception {
308 		for (final String transform : transformations) {
309 			try (final CryptoCipher cipher = getCipher(transform)) {
310 				assertNotNull(cipher);
311 
312 				final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
313 						0x0c, 0x0d, 0x0e, 0x0f, 0x11 };
314 				assertThrows(InvalidKeyException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(invalidKey), new IvParameterSpec(IV)));
315 			}
316 		}
317 	}
318 
319 	@Test
320 	public void testInvalidTransform() {
321 		assertThrows(IllegalArgumentException.class,
322 				() -> getCipher("AES/CBR/NoPadding/garbage/garbage").close());
323 	}
324 
325 	@Test
326 	public void testNullTransform() {
327 		assertThrows(IllegalArgumentException.class,
328 				() -> getCipher(null).close());
329 	}
330 }