1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package libcore.java.security; 18 19import java.lang.reflect.Method; 20import java.security.MessageDigest; 21import java.security.MessageDigestSpi; 22import java.security.NoSuchAlgorithmException; 23import java.security.Provider; 24import java.security.Security; 25import java.util.ArrayList; 26import java.util.Arrays; 27import java.util.Collections; 28import java.util.HashMap; 29import java.util.List; 30import java.util.Map; 31import java.util.Set; 32import java.util.concurrent.Callable; 33import java.util.concurrent.CountDownLatch; 34import java.util.concurrent.ExecutorService; 35import java.util.concurrent.Executors; 36import java.util.concurrent.TimeUnit; 37import junit.framework.TestCase; 38 39public final class MessageDigestTest extends TestCase { 40 41 private final byte[] sha_456 = { 42 -24, 9, -59, -47, -50, -92, 123, 69, -29, 71, 43 1, -46, 63, 96, -118, -102, 88, 3, 77, -55 44 }; 45 46 public void testShaReset() throws NoSuchAlgorithmException { 47 MessageDigest sha = MessageDigest.getInstance("SHA"); 48 sha.update(new byte[] { 1, 2, 3 }); 49 sha.reset(); 50 sha.update(new byte[] { 4, 5, 6 }); 51 assertEquals(Arrays.toString(sha_456), Arrays.toString(sha.digest())); 52 } 53 54 public void test_getInstance() throws Exception { 55 Provider[] providers = Security.getProviders(); 56 for (Provider provider : providers) { 57 Set<Provider.Service> services = provider.getServices(); 58 for (Provider.Service service : services) { 59 String type = service.getType(); 60 if (!type.equals("MessageDigest")) { 61 continue; 62 } 63 String algorithm = service.getAlgorithm(); 64 try { 65 // MessageDigest.getInstance(String) 66 MessageDigest md1 = MessageDigest.getInstance(algorithm); 67 assertEquals(algorithm, md1.getAlgorithm()); 68 test_MessageDigest(md1); 69 70 // MessageDigest.getInstance(String, Provider) 71 MessageDigest md2 = MessageDigest.getInstance(algorithm, provider); 72 assertEquals(algorithm, md2.getAlgorithm()); 73 assertEquals(provider, md2.getProvider()); 74 test_MessageDigest(md2); 75 76 // MessageDigest.getInstance(String, String) 77 MessageDigest md3 = MessageDigest.getInstance(algorithm, provider.getName()); 78 assertEquals(algorithm, md3.getAlgorithm()); 79 assertEquals(provider, md3.getProvider()); 80 test_MessageDigest(md3); 81 } catch (Exception e) { 82 throw new Exception("Problem testing MessageDigest." + algorithm, e); 83 } 84 } 85 } 86 } 87 88 private static final Map<String, Map<String, byte[]>> EXPECTATIONS 89 = new HashMap<String, Map<String, byte[]>>(); 90 private static void putExpectation(String algorithm, String inputName, byte[] expected) { 91 algorithm = algorithm.toUpperCase(); 92 Map<String, byte[]> expectations = EXPECTATIONS.get(algorithm); 93 if (expectations == null) { 94 expectations = new HashMap<String, byte[]>(); 95 EXPECTATIONS.put(algorithm, expectations); 96 } 97 expectations.put(inputName, expected); 98 } 99 private static Map<String, byte[]> getExpectations(String algorithm) throws Exception { 100 algorithm = algorithm.toUpperCase(); 101 Map<String, byte[]> expectations = EXPECTATIONS.get(algorithm); 102 if (expectations == null) { 103 throw new Exception("No expectations for MessageDigest." + algorithm); 104 } 105 return expectations; 106 } 107 private static final String INPUT_EMPTY = "empty"; 108 private static final String INPUT_256MB = "256mb"; 109 static { 110 // INPUT_EMPTY 111 putExpectation("MD2", 112 INPUT_EMPTY, 113 new byte[] { -125, 80, -27, -93, -30, 76, 21, 61, 114 -14, 39, 92, -97, -128, 105, 39, 115 }); 115 putExpectation("MD5", 116 INPUT_EMPTY, 117 new byte[] { -44, 29, -116, -39, -113, 0, -78, 4, 118 -23, -128, 9, -104, -20, -8, 66, 126 }); 119 putExpectation("SHA", 120 INPUT_EMPTY, 121 new byte[] { -38, 57, -93, -18, 94, 107, 75, 13, 122 50, 85, -65, -17, -107, 96, 24, -112, 123 -81, -40, 7, 9}); 124 putExpectation("SHA-1", 125 INPUT_EMPTY, 126 new byte[] { -38, 57, -93, -18, 94, 107, 75, 13, 127 50, 85, -65, -17, -107, 96, 24, -112, 128 -81, -40, 7, 9}); 129 putExpectation("SHA-224", 130 INPUT_EMPTY, 131 new byte[] { -47, 74, 2, -116, 42, 58, 43, -55, 71, 132 97, 2, -69, 40, -126, 52, -60, 21, 133 -94, -80, 31, -126, -114, -90, 42, 134 -59, -77, -28, 47}); 135 putExpectation("SHA-256", 136 INPUT_EMPTY, 137 new byte[] { -29, -80, -60, 66, -104, -4, 28, 20, 138 -102, -5, -12, -56, -103, 111, -71, 36, 139 39, -82, 65, -28, 100, -101, -109, 76, 140 -92, -107, -103, 27, 120, 82, -72, 85 }); 141 putExpectation("SHA-384", 142 INPUT_EMPTY, 143 new byte[] { 56, -80, 96, -89, 81, -84, -106, 56, 144 76, -39, 50, 126, -79, -79, -29, 106, 145 33, -3, -73, 17, 20, -66, 7, 67, 146 76, 12, -57, -65, 99, -10, -31, -38, 147 39, 78, -34, -65, -25, 111, 101, -5, 148 -43, 26, -46, -15, 72, -104, -71, 91 }); 149 putExpectation("SHA-512", 150 INPUT_EMPTY, 151 new byte[] { -49, -125, -31, 53, 126, -17, -72, -67, 152 -15, 84, 40, 80, -42, 109, -128, 7, 153 -42, 32, -28, 5, 11, 87, 21, -36, 154 -125, -12, -87, 33, -45, 108, -23, -50, 155 71, -48, -47, 60, 93, -123, -14, -80, 156 -1, -125, 24, -46, -121, 126, -20, 47, 157 99, -71, 49, -67, 71, 65, 122, -127, 158 -91, 56, 50, 122, -7, 39, -38, 62 }); 159 160 // Regression test for a SHA-1 problem with inputs larger than 256 MiB. http://b/4501620 161 // In mid-2013 this takes 3 minutes even on the host, so let's not run it on devices. 162 if (System.getenv("ANDROID_BUILD_TOP") != null) { 163 // INPUT_256MB 164 putExpectation("MD2", 165 INPUT_256MB, 166 new byte[] { -63, -120, 6, 67, 12, -87, -39, -11, 167 -67, -3, -31, -41, -91, 16, -35, 91 }); 168 putExpectation("MD5", 169 INPUT_256MB, 170 new byte[] { 31, 80, 57, -27, 11, -42, 107, 41, 171 12, 86, 104, 77, -123, 80, -58, -62 }); 172 putExpectation("SHA", 173 INPUT_256MB, 174 new byte[] { 123, -111, -37, -36, 86, -59, 120, 30, 175 -33, 108, -120, 71, -76, -86, 105, 101, 176 86, 108, 92, 117 }); 177 putExpectation("SHA-1", 178 INPUT_256MB, 179 new byte[] { 123, -111, -37, -36, 86, -59, 120, 30, 180 -33, 108, -120, 71, -76, -86, 105, 101, 181 86, 108, 92, 117 }); 182 putExpectation("SHA-224", 183 INPUT_256MB, 184 new byte[] { -78, 82, 5, -71, 57, 119, 77, -32, 185 -62, -74, -40, 64, -57, 79, 40, 116, 186 -18, 48, -69, 45, 18, -94, 111, 114, 187 -45, -93, 43, -11 }); 188 putExpectation("SHA-256", 189 INPUT_256MB, 190 new byte[] { -90, -41, 42, -57, 105, 15, 83, -66, 191 106, -28, 107, -88, -123, 6, -67, -105, 192 48, 42, 9, 63, 113, 8, 71, 43, 193 -39, -17, -61, -50, -3, -96, 100, -124 }); 194 putExpectation("SHA-384", 195 INPUT_256MB, 196 new byte[] { 71, 72, 77, -83, -110, 22, -118, -18, 197 -58, 119, 115, 74, -67, -36, 84, 122, 198 -105, -67, -75, 15, -33, 37, 78, -95, 199 4, 118, -53, 106, 65, -115, -19, 121, 200 -59, -94, -45, -111, -124, 35, 35, 60, 201 67, -34, 62, 106, -16, 122, -110, -14 }); 202 putExpectation("SHA-512", 203 INPUT_256MB, 204 new byte[] { 36, 7, -120, 39, -87, -87, 84, -40, 205 -66, 114, 62, -73, 107, 101, -117, -12, 206 -124, 20, 109, 103, -92, 125, 111, 102, 207 12, 114, -68, 100, 30, 25, -88, 62, 208 108, 56, 9, -107, 89, -25, -50, 118, 209 -87, 100, 13, 37, -14, 66, -40, -97, 210 105, -27, 79, -62, 53, -31, 83, 40, 211 4, 57, 90, -81, 63, -77, -42, 113 }); 212 } 213 } 214 215 private void test_MessageDigest(MessageDigest md) throws Exception { 216 String algorithm = md.getAlgorithm(); 217 for (Map.Entry<String, byte[]> expectation : getExpectations(algorithm).entrySet()) { 218 String inputName = expectation.getKey(); 219 byte[] expected = expectation.getValue(); 220 byte[] actual; 221 if (inputName.equals(INPUT_EMPTY)) { 222 actual = md.digest(); 223 } else if (inputName.equals(INPUT_256MB)) { 224 byte[] mb = new byte[1 * 1024 * 1024]; 225 for (int i = 0; i < 256; i++) { 226 md.update(mb); 227 } 228 actual = md.digest(); 229 } else { 230 throw new AssertionError(inputName); 231 } 232 assertDigest(algorithm, expected, actual); 233 assertEquals(algorithm, expected.length, md.getDigestLength()); 234 } 235 } 236 237 private void assertDigest(String algorithm, byte[] actual, byte[] expected) { 238 assertEquals(algorithm, javaBytes(actual), javaBytes(expected)); 239 } 240 241 private String javaBytes(byte[] bytes) { 242 StringBuffer buf = new StringBuffer(); 243 buf.append("new byte[] { "); 244 for (byte b : bytes) { 245 buf.append(b); 246 buf.append(", "); 247 } 248 buf.append(" }"); 249 return buf.toString(); 250 } 251 252 private final int THREAD_COUNT = 10; 253 254 public void testMessageDigest_MultipleThreads_Misuse() throws Exception { 255 ExecutorService es = Executors.newFixedThreadPool(THREAD_COUNT); 256 257 final CountDownLatch latch = new CountDownLatch(THREAD_COUNT); 258 final MessageDigest md = MessageDigest.getInstance("SHA-256"); 259 final byte[] message = new byte[64]; 260 261 for (int i = 0; i < THREAD_COUNT; i++) { 262 es.submit(new Callable<Void>() { 263 @Override 264 public Void call() throws Exception { 265 // Try to make sure all the threads are ready first. 266 latch.countDown(); 267 latch.await(); 268 269 for (int j = 0; j < 100; j++) { 270 md.update(message); 271 md.digest(); 272 } 273 274 return null; 275 } 276 }); 277 } 278 es.shutdown(); 279 assertTrue("Test should not timeout", es.awaitTermination(1, TimeUnit.MINUTES)); 280 } 281 282 /** 283 * When an instance of a MessageDigest is obtained, it's actually wrapped in an implementation 284 * which delegates MessageDigestSpi calls through to the underlying SPI implementation. We 285 * verify that all these MessageDigestSpi methods are indeed overridden -- if they aren't, they 286 * won't be delegated to the SPI implementation. 287 */ 288 public void testMessageDigestDelegateOverridesAllMethods() throws Exception { 289 MessageDigest md = MessageDigest.getInstance("SHA-256"); 290 291 /* 292 * Make sure we're dealing with a delegate and not an actual instance of MessageDigest. 293 */ 294 Class<?> mdClass = md.getClass(); 295 assertFalse(mdClass.equals(MessageDigestSpi.class)); 296 assertFalse(mdClass.equals(MessageDigest.class)); 297 298 List<String> methodsNotOverridden = new ArrayList<String>(); 299 300 for (Method spiMethod : MessageDigestSpi.class.getDeclaredMethods()) { 301 try { 302 mdClass.getDeclaredMethod(spiMethod.getName(), spiMethod.getParameterTypes()); 303 } catch (NoSuchMethodException e) { 304 methodsNotOverridden.add(spiMethod.toString()); 305 } 306 } 307 308 assertEquals(Collections.EMPTY_LIST, methodsNotOverridden); 309 } 310} 311