001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.crypto.impl;
019
020
021import com.nimbusds.jose.JOSEException;
022import com.nimbusds.jose.JWEAlgorithm;
023import com.nimbusds.jose.util.ByteUtils;
024import com.nimbusds.jose.util.IntegerUtils;
025import com.nimbusds.jose.util.StandardCharset;
026
027import javax.crypto.Mac;
028import javax.crypto.SecretKey;
029import javax.crypto.SecretKeyFactory;
030import javax.crypto.spec.PBEKeySpec;
031import javax.crypto.spec.SecretKeySpec;
032import java.io.ByteArrayOutputStream;
033import java.io.IOException;
034import java.nio.charset.StandardCharsets;
035import java.security.NoSuchAlgorithmException;
036import java.security.Provider;
037import java.security.spec.InvalidKeySpecException;
038
039
040/**
041 * Password-Based Key Derivation Function 2 (PBKDF2) utilities.
042 *
043 * @author Brian Campbell
044 * @author Pere Bueno Yerbes
045 * @author Vladimir Dzhuvinov
046 * @version 2024-09-10
047 */
048public class PBKDF2 {
049        
050        
051        /**
052         * The minimum salt length (8 bytes).
053         */
054        public static final int MIN_SALT_LENGTH = 8;
055
056
057        /**
058         * Zero byte array of length one.
059         */
060        static final byte[] ZERO_BYTE = { 0 };// value of (long) Math.pow(2, 32) - 1;
061        
062        
063        /**
064         * Value of {@code (long) Math.pow(2, 32) - 1;}
065         */
066        static final long MAX_DERIVED_KEY_LENGTH = 4294967295L;
067        
068        
069        /**
070         * Formats the specified cryptographic salt for use in PBKDF2.
071         *
072         * <pre>
073         * UTF8(JWE-alg) || 0x00 || Salt Input
074         * </pre>
075         *
076         * @param alg  The JWE algorithm. Must not be {@code null}.
077         * @param salt The cryptographic salt. Must be at least 8 bytes long.
078         *
079         * @return The formatted salt for use in PBKDF2.
080         *
081         * @throws JOSEException If formatting failed.
082         */
083        public static byte[] formatSalt(final JWEAlgorithm alg, final byte[] salt)
084                throws JOSEException {
085
086                byte[] algBytes = alg.toString().getBytes(StandardCharset.UTF_8);
087                
088                if (salt == null) {
089                        throw new JOSEException("The salt must not be null");
090                }
091                
092                if (salt.length < MIN_SALT_LENGTH) {
093                        throw new JOSEException("The salt must be at least " + MIN_SALT_LENGTH + " bytes long");
094                }
095
096                ByteArrayOutputStream out = new ByteArrayOutputStream();
097                try {
098                        out.write(algBytes);
099                        out.write(ZERO_BYTE);
100                        out.write(salt);
101                } catch (IOException e) {
102                        throw new JOSEException(e.getMessage(), e);
103                }
104
105                return out.toByteArray();
106        }
107
108
109        /**
110         * Derives a PBKDF2 key from the specified password and parameters.
111         *
112         * @param password       The password. Must not be {@code null}.
113         * @param formattedSalt  The formatted cryptographic salt. Must not be
114         *                       {@code null}.
115         * @param iterationCount The iteration count. Must be a positive
116         *                       integer.
117         * @param prfParams      The Pseudo-Random Function (PRF) parameters.
118         *                       Must not be {@code null}.
119         * @param jcaProvider    The JCA provider, {@code null} if not
120         *                       specified.
121         *
122         * @return The derived secret key (with "AES" algorithm).
123         *
124         * @throws JOSEException If the key derivation failed.
125         */
126        public static SecretKey deriveKey(final byte[] password,
127                                          final byte[] formattedSalt,
128                                          final int iterationCount,
129                                          final PRFParams prfParams,
130                                          final Provider jcaProvider)
131                throws JOSEException {
132                
133                if (formattedSalt == null) {
134                        throw new JOSEException("The formatted salt must not be null");
135                }
136                
137                if (iterationCount < 1) {
138                        throw new JOSEException("The iteration count must be greater than 0");
139                }
140                int keyLengthInBits =  ByteUtils.bitLength(prfParams.getDerivedKeyByteLength());
141                PBEKeySpec spec = new PBEKeySpec(new String(password, StandardCharsets.UTF_8).toCharArray(), formattedSalt, iterationCount, keyLengthInBits);
142                try {
143                        final SecretKeyFactory skf;
144                        if (jcaProvider != null) {
145                                skf = SecretKeyFactory.getInstance("PBKDF2With" + prfParams.getMACAlgorithm(), jcaProvider);
146                        } else {
147                                skf = SecretKeyFactory.getInstance("PBKDF2With" + prfParams.getMACAlgorithm());
148                        }
149                        return new SecretKeySpec(skf.generateSecret(spec).getEncoded(), "AES");
150                } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
151                        throw new JOSEException(ex.getLocalizedMessage(), ex);
152                }
153        }
154
155
156        /**
157         * Block extraction iteration.
158         *
159         * @param formattedSalt  The formatted salt. Must not be {@code null}.
160         * @param iterationCount The iteration count. Must be a positive
161         *                       integer.
162         * @param blockIndex     The block index.
163         * @param prf            The pseudo-random function (HMAC). Must not be
164         *                       {@code null}.
165         *
166         * @return The block.
167         *
168         * @throws JOSEException If the block extraction failed.
169         */
170        static byte[] extractBlock(final byte[] formattedSalt, final int iterationCount, final int blockIndex, final Mac prf)
171                throws JOSEException {
172                
173                if (formattedSalt == null) {
174                        throw new JOSEException("The formatted salt must not be null");
175                }
176                
177                if (iterationCount < 1) {
178                        throw new JOSEException("The iteration count must be greater than 0");
179                }
180
181                byte[] currentU;
182                byte[] lastU = null;
183                byte[] xorU = null;
184
185                for (int i = 1; i <= iterationCount; i++)
186                {
187                        byte[] inputBytes;
188                        if (i == 1)
189                        {
190                                inputBytes = ByteUtils.concat(formattedSalt, IntegerUtils.toBytes(blockIndex));
191                                currentU = prf.doFinal(inputBytes);
192                                xorU = currentU;
193                        }
194                        else
195                        {
196                                currentU = prf.doFinal(lastU);
197                                for (int j = 0; j < currentU.length; j++)
198                                {
199                                        xorU[j] = (byte) (currentU[j] ^ xorU[j]);
200                                }
201                        }
202
203                        lastU = currentU;
204                }
205                return xorU;
206        }
207
208
209        /**
210         * Prevents public instantiation.
211         */
212        private PBKDF2() {}
213}