1/* 2 * Copyright (c) 2012, 2014, 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 24import java.security.InvalidKeyException; 25import java.security.NoSuchAlgorithmException; 26import java.security.SecureRandom; 27import java.security.spec.InvalidKeySpecException; 28import javax.crypto.Mac; 29import javax.crypto.SecretKey; 30import javax.crypto.SecretKeyFactory; 31import javax.crypto.spec.PBEKeySpec; 32 33/** 34 * @test 35 * @bug 8041787 36 * @summary Check if doFinal and update operation result in same PBMac 37 * @author Alexander Fomin 38 * @run main PBMacDoFinalVsUpdate 39 */ 40public class PBMacDoFinalVsUpdate { 41 42 public static void main(String[] args) { 43 String[] PBMAC1Algorithms = { 44 "HmacPBESHA1", 45 "PBEWithHmacSHA1", 46 "PBEWithHmacSHA224", 47 "PBEWithHmacSHA256", 48 "PBEWithHmacSHA384", 49 "PBEWithHmacSHA512" 50 }; 51 52 String[] PBKDF2Algorithms = { 53 "PBKDF2WithHmacSHA1", 54 "PBKDF2WithHmacSHA224", 55 "PBKDF2WithHmacSHA256", 56 "PBKDF2WithHmacSHA384", 57 "PBKDF2WithHmacSHA512" 58 }; 59 60 PBMacDoFinalVsUpdate testRunner = new PBMacDoFinalVsUpdate(); 61 boolean failed = false; 62 63 for (String thePBMacAlgo : PBMAC1Algorithms) { 64 65 for (String thePBKDF2Algo : PBKDF2Algorithms) { 66 67 System.out.println("Running test with " + thePBMacAlgo 68 + " and " + thePBKDF2Algo + ":"); 69 try { 70 if (!testRunner.doTest(thePBMacAlgo, thePBKDF2Algo)) { 71 failed = true; 72 } 73 } catch (NoSuchAlgorithmException | InvalidKeyException | 74 InvalidKeySpecException e) { 75 failed = true; 76 e.printStackTrace(System.out); 77 System.out.println("Test FAILED."); 78 } 79 } 80 } 81 82 if (failed) { 83 throw new RuntimeException("One or more tests failed...."); 84 } 85 } 86 87 /** 88 * Uses a random generator to initialize a message, instantiate a Mac object 89 * according to the given PBMAC1 algorithm, initialize the object with a 90 * SecretKey derived using PBKDF2 algorithm (see PKCS #5 v21, chapter 7.1), 91 * feed the message into the Mac object all at once and get the output MAC 92 * as result1. Reset the Mac object, chop the message into three pieces, 93 * feed into the Mac object sequentially, and get the output MAC as result2. 94 * Finally, compare result1 and result2 and see if they are the same. 95 * 96 * @param theMacAlgo PBMAC algorithm to test 97 * @param thePBKDF2Algo PBKDF2 algorithm to test 98 * @return true - the test is passed; false - otherwise. 99 * @throws NoSuchAlgorithmException 100 * @throws InvalidKeyException 101 * @throws InvalidKeySpecException 102 */ 103 protected boolean doTest(String theMacAlgo, String thePBKDF2Algo) 104 throws NoSuchAlgorithmException, InvalidKeyException, 105 InvalidKeySpecException { 106 int OFFSET = 5; 107 108 // Some message for which a MAC result will be calculated 109 byte[] plain = new byte[25]; 110 new SecureRandom().nextBytes(plain); 111 112 // Form tail - is one of the three pieces 113 byte[] tail = new byte[plain.length - OFFSET]; 114 System.arraycopy(plain, OFFSET, tail, 0, tail.length); 115 116 // Obtain a SecretKey using PBKDF2 117 SecretKey key = getSecretKey(thePBKDF2Algo); 118 119 // Instantiate Mac object and init it with a SecretKey and calc result1 120 Mac theMac = Mac.getInstance(theMacAlgo); 121 theMac.init(key); 122 byte[] result1 = theMac.doFinal(plain); 123 124 if (!isMacLengthExpected(theMacAlgo, result1.length)) { 125 return false; 126 } 127 128 // Reset Mac and calculate result2 129 theMac.reset(); 130 theMac.update(plain[0]); 131 theMac.update(plain, 1, OFFSET - 1); 132 byte[] result2 = theMac.doFinal(tail); 133 134 // Return result 135 if (!java.util.Arrays.equals(result1, result2)) { 136 System.out.println("result1 and result2 are not the same:"); 137 System.out.println("result1: " + dumpByteArray(result1)); 138 System.out.println("result2: " + dumpByteArray(result2)); 139 return false; 140 } else { 141 System.out.println("Resulted MAC with update and doFinal is same"); 142 } 143 144 return true; 145 } 146 147 /** 148 * Get SecretKey for the given PBKDF2 algorithm. 149 * 150 * @param thePBKDF2Algorithm - PBKDF2 algorithm 151 * @return SecretKey according to thePBKDF2Algorithm 152 * @throws NoSuchAlgorithmException 153 * @throws InvalidKeySpecException 154 */ 155 protected SecretKey getSecretKey(String thePBKDF2Algorithm) 156 throws NoSuchAlgorithmException, InvalidKeySpecException { 157 // Prepare salt 158 byte[] salt = new byte[64]; // PKCS #5 v2.1 recommendation 159 new SecureRandom().nextBytes(salt); 160 161 // Generate secret key 162 PBEKeySpec pbeKeySpec = new PBEKeySpec( 163 "A #pwd# implied to be hidden!".toCharArray(), 164 salt, 1000, 128); 165 SecretKeyFactory keyFactory 166 = SecretKeyFactory.getInstance(thePBKDF2Algorithm); 167 return keyFactory.generateSecret(pbeKeySpec); 168 } 169 170 /** 171 * Check if the lengthToCheck is expected length for the given MACAlgo. 172 * 173 * @param MACAlgo PBMAC algorithm 174 * @param lengthToCheck the length of MAC need to check 175 * @return true - lengthToCheck is expected length for the MACAlgo; false - 176 * otherwise. 177 */ 178 protected boolean isMacLengthExpected(String MACAlgo, int lengthToCheck) { 179 java.util.regex.Pattern p = java.util.regex.Pattern.compile("(\\d+)", 180 java.util.regex.Pattern.CASE_INSENSITIVE); 181 java.util.regex.Matcher m = p.matcher(MACAlgo); 182 int val = 0; 183 184 if (m.find()) { 185 val = Integer.parseInt(m.group(1)); 186 } 187 188 // HmacPBESHA1 should return MAC 20 byte length 189 if ((val == 1) && (lengthToCheck == 20)) { 190 return true; 191 } 192 193 return (val / 8) == lengthToCheck; 194 } 195 196 /** 197 * An utility method to dump a byte array for debug output. 198 * 199 * @param theByteArray the byte array to dump 200 * @return string representation of the theByteArray in Hex. 201 */ 202 protected String dumpByteArray(byte[] theByteArray) { 203 StringBuilder buf = new StringBuilder(); 204 205 for (byte b : theByteArray) { 206 buf.append(Integer.toHexString(b)); 207 } 208 209 return buf.toString(); 210 } 211 212} 213