001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020package org.apache.shiro.crypto.support.hashes.argon2;
021
022import org.apache.shiro.crypto.hash.HashRequest;
023import org.apache.shiro.crypto.hash.HashSpi;
024import org.apache.shiro.lang.util.ByteSource;
025import org.apache.shiro.lang.util.SimpleByteSource;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029import java.security.SecureRandom;
030import java.util.Base64;
031import java.util.Locale;
032import java.util.Optional;
033import java.util.Random;
034import java.util.Set;
035
036/**
037 * A HashProvider for the Argon2 hash algorithm.
038 *
039 * <p>This class is intended to be used by the {@code HashProvider} class from Shiro. However,
040 * this class can also be used to created instances of the Argon2 hash manually.</p>
041 *
042 * <p>Furthermore, there is a nested {@link Parameters} class which provides names for the
043 * keys used in the parameters map of the {@link HashRequest} class.</p>
044 *
045 * @since 2.0
046 */
047public class Argon2HashProvider implements HashSpi {
048
049    private static final Logger LOG = LoggerFactory.getLogger(Argon2HashProvider.class);
050
051    @Override
052    public Set<String> getImplementedAlgorithms() {
053        return Argon2Hash.getAlgorithmsArgon2();
054    }
055
056    @Override
057    public Argon2Hash fromString(String format) {
058        return Argon2Hash.fromString(format);
059    }
060
061    @Override
062    public HashFactory newHashFactory(Random random) {
063        return new Argon2HashFactory(random);
064    }
065
066    static class Argon2HashFactory implements HashSpi.HashFactory {
067
068        private final SecureRandom random;
069
070        Argon2HashFactory(Random random) {
071            if (!(random instanceof SecureRandom)) {
072                throw new IllegalArgumentException("Only SecureRandom instances are supported at the moment!");
073            }
074
075            this.random = (SecureRandom) random;
076        }
077
078        @Override
079        public Argon2Hash generate(HashRequest hashRequest) {
080            final String algorithmName = Optional.ofNullable(hashRequest.getParameters().get(Parameters.PARAMETER_ALGORITHM_NAME))
081                    .map(algo -> (String) algo)
082                    .orElse(Parameters.DEFAULT_ALGORITHM_NAME);
083
084            final int version = Optional.ofNullable(hashRequest.getParameters().get(Parameters.PARAMETER_ALGORITHM_VERSION))
085                    .flatMap(algoV -> intOrEmpty(algoV, Parameters.PARAMETER_ALGORITHM_VERSION))
086                    .orElse(Parameters.DEFAULT_ALGORITHM_VERSION);
087
088            final ByteSource salt = parseSalt(hashRequest);
089
090            final int iterations = Optional.ofNullable(hashRequest.getParameters().get(Parameters.PARAMETER_ITERATIONS))
091                    .flatMap(algoV -> intOrEmpty(algoV, Parameters.PARAMETER_ITERATIONS))
092                    .orElse(Parameters.DEFAULT_ITERATIONS);
093
094            final int memoryKib = Optional.ofNullable(hashRequest.getParameters().get(Parameters.PARAMETER_MEMORY_KIB))
095                    .flatMap(algoV -> intOrEmpty(algoV, Parameters.PARAMETER_MEMORY_KIB))
096                    .orElse(Parameters.DEFAULT_MEMORY_KIB);
097
098            final int parallelism = Optional.ofNullable(hashRequest.getParameters().get(Parameters.PARAMETER_PARALLELISM))
099                    .flatMap(algoV -> intOrEmpty(algoV, Parameters.PARAMETER_PARALLELISM))
100                    .orElse(Parameters.DEFAULT_PARALLELISM);
101
102            final int outputLengthBits = Optional.ofNullable(hashRequest.getParameters()
103                            .get(Parameters.PARAMETER_OUTPUT_LENGTH_BITS))
104                    .flatMap(algoV -> intOrEmpty(algoV, Parameters.PARAMETER_OUTPUT_LENGTH_BITS))
105                    .orElse(Parameters.DEFAULT_OUTPUT_LENGTH_BITS);
106
107            return Argon2Hash.generate(
108                    algorithmName,
109                    version,
110                    hashRequest.getSource(),
111                    salt,
112                    iterations,
113                    memoryKib,
114                    parallelism,
115                    outputLengthBits
116            );
117        }
118
119        private ByteSource parseSalt(HashRequest hashRequest) {
120            return Optional.ofNullable(hashRequest.getParameters().get(Parameters.PARAMETER_SALT))
121                    .map(saltParm -> Base64.getDecoder().decode((String) saltParm))
122                    .map(SimpleByteSource::new)
123                    .flatMap(this::lengthValidOrEmpty)
124                    .orElseGet(() -> Argon2Hash.createSalt(random));
125        }
126
127        @SuppressWarnings("checkstyle:MagicNumber")
128        private Optional<ByteSource> lengthValidOrEmpty(ByteSource bytes) {
129            if (bytes.getBytes().length != 16) {
130                return Optional.empty();
131            }
132
133            return Optional.of(bytes);
134        }
135
136        @SuppressWarnings("checkstyle:MagicNumber")
137        private Optional<Integer> intOrEmpty(Object maybeInt, String parameterName) {
138            try {
139                return Optional.of(Integer.parseInt((String) maybeInt, 10));
140            } catch (NumberFormatException numberFormatException) {
141                String message = String.format(
142                        Locale.ENGLISH,
143                        "Expected Integer for parameter %s, but %s is not parsable.",
144                        parameterName, maybeInt
145                );
146                LOG.warn(message, numberFormatException);
147                return Optional.empty();
148            }
149        }
150    }
151
152    /**
153     * Parameters for the {@link Argon2Hash} class.
154     *
155     * <p>This class contains public constants only. The constants starting with {@code PARAMETER_} are
156     * the parameter names recognized by the
157     * {@link org.apache.shiro.crypto.hash.HashSpi.HashFactory#generate(HashRequest)} method.</p>
158     *
159     * <p>The constants starting with {@code DEFAULT_} are their respective default values.</p>
160     */
161    public static final class Parameters {
162
163        /**
164         * Default algorithm name.
165         */
166        public static final String DEFAULT_ALGORITHM_NAME = Argon2Hash.DEFAULT_ALGORITHM_NAME;
167
168        /**
169         * Default algorithm version.
170         */
171        public static final int DEFAULT_ALGORITHM_VERSION = Argon2Hash.DEFAULT_ALGORITHM_VERSION;
172
173        /**
174         * Default iterations.
175         */
176        public static final int DEFAULT_ITERATIONS = Argon2Hash.DEFAULT_ITERATIONS;
177
178        /**
179         * Default memory kib.
180         */
181        public static final int DEFAULT_MEMORY_KIB = Argon2Hash.DEFAULT_MEMORY_KIB;
182
183        /**
184         * Default parallelism number.
185         */
186        public static final int DEFAULT_PARALLELISM = Argon2Hash.DEFAULT_PARALLELISM;
187
188        /**
189         * Default output length bits.
190         */
191        public static final int DEFAULT_OUTPUT_LENGTH_BITS = Argon2Hash.DEFAULT_OUTPUT_LENGTH_BITS;
192
193        /**
194         * Parameter for modifying the internal algorithm used by Argon2.
195         *
196         * <p>Valid values are {@code argon2i} (optimized to resist side-channel attacks),
197         * {@code argon2d} (maximizes resistance to GPU cracking attacks)
198         * and {@code argon2id} (a hybrid version).</p>
199         *
200         * <p>The default value is {@value DEFAULT_ALGORITHM_NAME} when this parameter is not specified.</p>
201         */
202        public static final String PARAMETER_ALGORITHM_NAME = "Argon2.algorithmName";
203
204        /**
205         * Argon2 algorithm version.
206         */
207        public static final String PARAMETER_ALGORITHM_VERSION = "Argon2.version";
208
209        /**
210         * The salt to use.
211         *
212         * <p>The value for this parameter accepts a Base64-encoded 16byte (128bit) salt.</p>
213         *
214         * <p>As for any KDF, do not use a static salt value for multiple passwords.</p>
215         *
216         * <p>The default value is a new random 128bit-salt, if this parameter is not specified.</p>
217         */
218        public static final String PARAMETER_SALT = "Argon2.salt";
219
220        /**
221         * Argon2 parameter iterations.
222         */
223        public static final String PARAMETER_ITERATIONS = "Argon2.iterations";
224
225        /**
226         * Argon2 parameter memory kib.
227         */
228        public static final String PARAMETER_MEMORY_KIB = "Argon2.memoryKib";
229
230        /**
231         * Argon2 parameter parallelism.
232         */
233        public static final String PARAMETER_PARALLELISM = "Argon2.parallelism";
234
235        /**
236         * The output length (in bits) of the resulting data section.
237         *
238         * <p>Argon2 allows to modify the length of the generated output.</p>
239         *
240         * <p>The default value is {@value DEFAULT_OUTPUT_LENGTH_BITS} when this parameter is not specified.</p>
241         */
242        public static final String PARAMETER_OUTPUT_LENGTH_BITS = "Argon2.outputLength";
243
244        private Parameters() {
245            // utility class
246        }
247    }
248}