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}