1 /*
2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import static java.lang.System.out;
25 
26 import java.security.InvalidAlgorithmParameterException;
27 import java.security.InvalidKeyException;
28 import java.security.NoSuchAlgorithmException;
29 import java.security.NoSuchProviderException;
30 import java.security.spec.AlgorithmParameterSpec;
31 
32 import javax.crypto.BadPaddingException;
33 import javax.crypto.Cipher;
34 import javax.crypto.IllegalBlockSizeException;
35 import javax.crypto.KeyGenerator;
36 import javax.crypto.NoSuchPaddingException;
37 import javax.crypto.SecretKey;
38 import javax.crypto.ShortBufferException;
39 import javax.crypto.spec.IvParameterSpec;
40 import javax.crypto.spec.SecretKeySpec;
41 
42 /**
43  * This is a abstract class used to test various ciphers
44  */
45 public abstract class TestCipher {
46 
47     private final String SUNJCE = "SunJCE";
48     private final String ALGORITHM;
49     private final String[] MODES;
50     private final String[] PADDINGS;
51 
52     /* Used to test variable-key-length ciphers:
53        Key size tested is increment of KEYCUTTER from minKeySize
54        to min(maxKeySize, Cipher.getMaxAllowedKeyLength(algo)).
55     */
56     private final int KEYCUTTER = 8;
57     private final int minKeySize;
58     private final int maxKeySize;
59 
60     // Used to assert that Encryption/Decryption works with same buffer
61     // TEXT_LEN is multiple of blocks in order to work against ciphers w/ NoPadding
62     private final int TEXT_LEN = 800;
63     private final int ENC_OFFSET = 6;
64     private final int STORAGE_OFFSET = 3;
65     private final int PAD_BYTES = 16;
66 
67     private final byte[] IV;
68     private final byte[] INPUT_TEXT;
69 
70     // for variable-key-length ciphers
TestCipher(String algo, String[] modes, String[] paddings, int minKeySize, int maxKeySize)71     TestCipher(String algo, String[] modes, String[] paddings,
72             int minKeySize, int maxKeySize) throws NoSuchAlgorithmException {
73         ALGORITHM = algo;
74         MODES = modes;
75         PADDINGS = paddings;
76         this.minKeySize = minKeySize;
77         int maxAllowedKeySize = Cipher.getMaxAllowedKeyLength(ALGORITHM);
78         if (maxKeySize > maxAllowedKeySize) {
79             maxKeySize = maxAllowedKeySize;
80         }
81         this.maxKeySize = maxKeySize;
82         IV = generateBytes(8);
83         INPUT_TEXT = generateBytes(TEXT_LEN + PAD_BYTES + ENC_OFFSET);
84     }
85 
86     // for fixed-key-length ciphers
TestCipher(String algo, String[] modes, String[] paddings)87     TestCipher(String algo, String[] modes, String[] paddings) {
88         ALGORITHM = algo;
89         MODES = modes;
90         PADDINGS = paddings;
91         this.minKeySize = this.maxKeySize = 0;
92 
93         IV = generateBytes(8);
94         INPUT_TEXT = generateBytes(TEXT_LEN + PAD_BYTES + ENC_OFFSET);
95     }
96 
generateBytes(int length)97     private static byte[] generateBytes(int length) {
98         byte[] bytes = new byte[length];
99         for (int i = 0; i < length; i++) {
100             bytes[i] = (byte) (i & 0xff);
101         }
102         return bytes;
103     }
104 
isMultipleKeyLengthSupported()105     private boolean isMultipleKeyLengthSupported() {
106         return (maxKeySize != minKeySize);
107     }
108 
runAll()109     public void runAll() throws InvalidKeyException,
110             NoSuchPaddingException, InvalidAlgorithmParameterException,
111             ShortBufferException, IllegalBlockSizeException,
112             BadPaddingException, NoSuchAlgorithmException,
113             NoSuchProviderException {
114 
115         for (String mode : MODES) {
116             for (String padding : PADDINGS) {
117                 if (!isMultipleKeyLengthSupported()) {
118                     runTest(mode, padding, minKeySize);
119                 } else {
120                     int keySize = maxKeySize;
121                     while (keySize >= minKeySize) {
122                         out.println("With Key Strength: " + keySize);
123                         runTest(mode, padding, keySize);
124                         keySize -= KEYCUTTER;
125                     }
126                 }
127             }
128         }
129     }
130 
runTest(String mo, String pad, int keySize)131     private void runTest(String mo, String pad, int keySize)
132             throws NoSuchPaddingException, BadPaddingException,
133             ShortBufferException, IllegalBlockSizeException,
134             InvalidAlgorithmParameterException, InvalidKeyException,
135             NoSuchAlgorithmException, NoSuchProviderException {
136 
137         String TRANSFORMATION = ALGORITHM + "/" + mo + "/" + pad;
138         out.println("Testing: " + TRANSFORMATION);
139 
140         // Initialization
141         Cipher ci = Cipher.getInstance(TRANSFORMATION, SUNJCE);
142         KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM, SUNJCE);
143         if (keySize != 0) {
144             kg.init(keySize);
145         }
146 
147         SecretKey key = kg.generateKey();
148         SecretKeySpec skeySpec = new SecretKeySpec(key.getEncoded(), ALGORITHM);
149 
150         AlgorithmParameterSpec aps = new IvParameterSpec(IV);
151         if (mo.equalsIgnoreCase("ECB")) {
152             ci.init(Cipher.ENCRYPT_MODE, key);
153         } else {
154             ci.init(Cipher.ENCRYPT_MODE, key, aps);
155         }
156 
157         // Encryption
158         byte[] plainText = INPUT_TEXT.clone();
159 
160         // Generate cipher and save to separate buffer
161         byte[] cipherText = ci.doFinal(INPUT_TEXT, ENC_OFFSET, TEXT_LEN);
162 
163         // Generate cipher and save to same buffer
164         int enc_bytes = ci.update(
165                 INPUT_TEXT, ENC_OFFSET, TEXT_LEN, INPUT_TEXT, STORAGE_OFFSET);
166         enc_bytes += ci.doFinal(INPUT_TEXT, enc_bytes + STORAGE_OFFSET);
167 
168         if (!equalsBlock(
169                 INPUT_TEXT, STORAGE_OFFSET, enc_bytes,
170                 cipherText, 0, cipherText.length)) {
171             throw new RuntimeException(
172                     "Different ciphers generated with same buffer");
173         }
174 
175         // Decryption
176         if (mo.equalsIgnoreCase("ECB")) {
177             ci.init(Cipher.DECRYPT_MODE, skeySpec);
178         } else {
179             ci.init(Cipher.DECRYPT_MODE, skeySpec, aps);
180         }
181 
182         // Recover text from cipher and save to separate buffer
183         byte[] recoveredText = ci.doFinal(cipherText, 0, cipherText.length);
184 
185         if (!equalsBlock(
186                 plainText, ENC_OFFSET, TEXT_LEN,
187                 recoveredText, 0, recoveredText.length)) {
188             throw new RuntimeException(
189                     "Recovered text not same as plain text");
190         } else {
191             out.println("Recovered and plain text are same");
192         }
193 
194         // Recover text from cipher and save to same buffer
195         int dec_bytes = ci.update(
196                 INPUT_TEXT, STORAGE_OFFSET, enc_bytes, INPUT_TEXT, ENC_OFFSET);
197         dec_bytes += ci.doFinal(INPUT_TEXT, dec_bytes + ENC_OFFSET);
198 
199         if (!equalsBlock(
200                 plainText, ENC_OFFSET, TEXT_LEN,
201                 INPUT_TEXT, ENC_OFFSET, dec_bytes)) {
202             throw new RuntimeException(
203                     "Recovered text not same as plain text with same buffer");
204         } else {
205             out.println("Recovered and plain text are same with same buffer");
206         }
207 
208         out.println("Test Passed.");
209     }
210 
equalsBlock(byte[] b1, int off1, int len1, byte[] b2, int off2, int len2)211     private static boolean equalsBlock(byte[] b1, int off1, int len1,
212             byte[] b2, int off2, int len2) {
213         if (len1 != len2) {
214             return false;
215         }
216         for (int i = off1, j = off2, k = 0; k < len1; i++, j++, k++) {
217             if (b1[i] != b2[j]) {
218                 return false;
219             }
220         }
221         return true;
222     }
223 }
224