blob: 74961564b346fef3a44171b5d41826672c973bb7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.crypto.support.hashes.bcrypt;
import org.apache.shiro.crypto.hash.HashRequest;
import org.apache.shiro.crypto.hash.HashSpi;
import org.apache.shiro.lang.util.ByteSource;
import org.apache.shiro.lang.util.SimpleByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
/**
* @since 2.0
*/
public class BCryptProvider implements HashSpi {
private static final Logger LOG = LoggerFactory.getLogger(BCryptProvider.class);
@Override
public Set<String> getImplementedAlgorithms() {
return BCryptHash.getAlgorithmsBcrypt();
}
@Override
public BCryptHash fromString(String format) {
return BCryptHash.fromString(format);
}
@Override
public HashFactory newHashFactory(Random random) {
return new BCryptHashFactory(random);
}
static class BCryptHashFactory implements HashSpi.HashFactory {
private final SecureRandom random;
public BCryptHashFactory(Random random) {
if (!(random instanceof SecureRandom)) {
throw new IllegalArgumentException("Only SecureRandom instances are supported at the moment!");
}
this.random = (SecureRandom) random;
}
@Override
public BCryptHash generate(HashRequest hashRequest) {
final String algorithmName = hashRequest.getAlgorithmName().orElse(Parameters.DEFAULT_ALGORITHM_NAME);
final ByteSource salt = getSalt(hashRequest);
final int cost = getCost(hashRequest);
return BCryptHash.generate(
algorithmName,
hashRequest.getSource(),
salt,
cost
);
}
private int getCost(HashRequest hashRequest) {
final Map<String, Object> parameters = hashRequest.getParameters();
final Optional<String> optCostStr = Optional.ofNullable(parameters.get(Parameters.PARAMETER_COST))
.map(obj -> (String) obj);
if (!optCostStr.isPresent()) {
return BCryptHash.DEFAULT_COST;
}
String costStr = optCostStr.orElseThrow(NoSuchElementException::new);
try {
int cost = Integer.parseInt(costStr, 10);
BCryptHash.checkValidCost(cost);
return cost;
} catch (IllegalArgumentException costEx) {
String message = String.format(
Locale.ENGLISH,
"Expected Integer for parameter %s, but %s is not parsable or valid.",
Parameters.PARAMETER_COST, costStr
);
LOG.warn(message, costEx);
return BCryptHash.DEFAULT_COST;
}
}
private ByteSource getSalt(HashRequest hashRequest) {
final Map<String, Object> parameters = hashRequest.getParameters();
final Optional<String> optSaltBase64 = Optional.ofNullable(parameters.get(Parameters.PARAMETER_SALT))
.map(obj -> (String) obj);
if (!optSaltBase64.isPresent()) {
return BCryptHash.createSalt(random);
}
final String saltBase64 = optSaltBase64.orElseThrow(NoSuchElementException::new);
final byte[] saltBytes = Base64.getDecoder().decode(saltBase64);
if (saltBytes.length != BCryptHash.SALT_LENGTH) {
return BCryptHash.createSalt(random);
}
return new SimpleByteSource(saltBytes);
}
}
public static final class Parameters {
public static final String DEFAULT_ALGORITHM_NAME = BCryptHash.DEFAULT_ALGORITHM_NAME;
public static final String PARAMETER_SALT = "BCrypt.salt";
public static final String PARAMETER_COST = "BCrypt.cost";
private Parameters() {
// utility class
}
}
}