blob: ddc0a722f263991ff0799926dbbbf1312a252bfc [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.cassandra.tools;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.cassandra.io.util.File;
import org.mindrot.jbcrypt.BCrypt;
public class HashPassword
{
private static final String LOGROUNDS_OPTION = "logrounds";
private static final String HELP_OPTION = "help";
private static final String ENV_VAR = "environment-var";
private static final String PLAIN = "plain";
private static final String INPUT = "input";
private static final int LOGROUNDS_DEFAULT = 10;
private static final int MIN_PASS_LENGTH = 4;
public static void main(String[] args)
{
try
{
Options options = getOptions();
CommandLine cmd = parseCommandLine(args, options);
String password = null;
if (cmd.hasOption(ENV_VAR))
{
password = System.getenv(cmd.getOptionValue(ENV_VAR));
if (password == null)
{
System.err.println(String.format("Environment variable '%s' is undefined.", cmd.getOptionValue(ENV_VAR)));
System.exit(1);
}
}
else if (cmd.hasOption(PLAIN))
{
password = cmd.getOptionValue(PLAIN);
}
else if (cmd.hasOption(INPUT))
{
String input = cmd.getOptionValue(INPUT);
byte[] fileInput = null;
if ("-".equals(input))
{
ByteArrayOutputStream os = new ByteArrayOutputStream();
int rd;
while ((rd = System.in.read()) != -1)
os.write(rd);
fileInput = os.toByteArray();
}
else
{
try
{
Path file = File.getPath(input);
fileInput = Files.readAllBytes(file);
}
catch (IOException e)
{
System.err.printf("Failed to read from '%s': %s%n", input, e);
System.exit(1);
}
}
password = new String(fileInput, StandardCharsets.UTF_8);
}
else
{
System.err.println(String.format("One of the options --%s, --%s or --%s must be used.",
ENV_VAR, PLAIN, INPUT));
printUsage(options);
System.exit(1);
}
if (password.chars().anyMatch(i -> i < 32))
System.err.println("WARNING: The provided plain text password contains non-printable characters (ASCII<32).");
if (password.length() < MIN_PASS_LENGTH)
System.err.println("WARNING: The provided password is very short, probably too short to be secure.");
int logRounds = cmd.hasOption(LOGROUNDS_OPTION) ? Integer.parseInt(cmd.getOptionValue(LOGROUNDS_OPTION)) : LOGROUNDS_DEFAULT;
if (logRounds < 4 || logRounds > 30)
{
System.err.println(String.format("Bad value for --%s %d. " +
"Please use a value between 4 and 30 inclusively",
LOGROUNDS_OPTION, logRounds));
System.exit(1);
}
// The number of rounds is in fact = 2^rounds.
if (logRounds > 16)
System.err.println(String.format("WARNING: Using a high number of hash rounds, as configured using '--%s %d' " +
"will consume a lot of CPU and likely cause timeouts. Note that the parameter defines the " +
"logarithmic number of rounds: %d becomes 2^%d = %d rounds",
LOGROUNDS_OPTION, logRounds,
logRounds, logRounds, 1 << logRounds));
if (password.getBytes().length > 72)
System.err.println(String.format("WARNING: The provided password has a length of %d bytes, but the underlying hash/crypt algorithm " +
"(bcrypt) can only compare up to 72 bytes. The password will be accepted and work, but only compared up to 72 bytes.",
password.getBytes().length));
String hashed = escape(hashpw(password, logRounds));
System.out.print(hashed);
System.out.flush();
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
}
private static CommandLine parseCommandLine(String[] args, Options options) throws ParseException
{
CommandLineParser parser = new GnuParser();
CommandLine cmd = parser.parse(options, args, false);
if (cmd.hasOption(HELP_OPTION))
{
printUsage(options);
System.exit(0);
}
return cmd;
}
private static Options getOptions()
{
Options options = new Options();
options.addOption("h", HELP_OPTION, false, "Display this help message");
options.addOption("r", LOGROUNDS_OPTION, true, "Number of hash rounds (default: " + LOGROUNDS_DEFAULT + ").");
OptionGroup group = new OptionGroup();
group.addOption(new Option("e", ENV_VAR, true,
"Use value of the specified environment variable as the password"));
group.addOption(new Option("p", PLAIN, true,
"Argument is the plain text password"));
group.addOption(new Option("i", INPUT, true,
"Input is a file (or - for stdin) to read the password from. " +
"Make sure that the whole input including newlines is considered. " +
"For example, the shell command 'echo -n foobar | hash_password -i -' will " +
"work as intended and just hash 'foobar'."));
options.addOptionGroup(group);
return options;
}
private static String hashpw(String password, int rounds)
{
return BCrypt.hashpw(password, BCrypt.gensalt(rounds));
}
private static String escape(String name)
{
return StringUtils.replace(name, "'", "''");
}
public static void printUsage(Options options)
{
String usage = "hash_password [options]";
String header = "--\n" +
"Hashes a plain text password and prints the hashed password.\n" +
"Options are:";
new HelpFormatter().printHelp(usage, header, options, "");
}
}