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