| /* |
| * 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, ""); |
| } |
| } |