From 80f766554a3dd117b2958fd8c55b8fab2b73f9f9 Mon Sep 17 00:00:00 2001 From: Demian Date: Thu, 11 Jun 2015 10:20:37 -0300 Subject: Implemented password hashing using a salt, following this code&guidelines: https://crackstation.net/hashing-security.htm --- src/org/traccar/helper/PasswordHash.java | 231 +++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 src/org/traccar/helper/PasswordHash.java (limited to 'src/org/traccar/helper/PasswordHash.java') diff --git a/src/org/traccar/helper/PasswordHash.java b/src/org/traccar/helper/PasswordHash.java new file mode 100644 index 000000000..98ded0988 --- /dev/null +++ b/src/org/traccar/helper/PasswordHash.java @@ -0,0 +1,231 @@ +package org.traccar.helper; + +/* + * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm). + * Copyright (c) 2013, Taylor Hornby + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import java.security.SecureRandom; + +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.SecretKeyFactory; + +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +/* + * PBKDF2 salted password hashing. + * Author: havoc AT defuse.ca + * www: http://crackstation.net/hashing-security.htm + */ +public class PasswordHash { + public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; + + // The following constants may be changed without breaking existing hashes. + public static final int SALT_BYTE_SIZE = 24; + public static final int HASH_BYTE_SIZE = 24; + public static final int PBKDF2_ITERATIONS = 1000; + + public static final int ITERATION_INDEX = 0; + public static final int SALT_INDEX = 1; + public static final int PBKDF2_INDEX = 2; + + public static class HashingResult { + + public final Integer iterations; + public final String hash; + public final String salt; + + public HashingResult(Integer iterations, String hash, String salt) { + this.hash = hash; + this.salt = salt; + this.iterations = iterations; + } + } + + /** + * Returns a salted PBKDF2 hash of the password. + * + * @param password + * the password to hash + * @return a salted PBKDF2 hash of the password + */ + public static HashingResult createHash(String password) { + return createHash(password.toCharArray()); + } + + /** + * Returns a salted PBKDF2 hash of the password. + * + * @param password + * the password to hash + * @return a salted PBKDF2 hash of the password + */ + public static HashingResult createHash(char[] password) { + // Generate a random salt + SecureRandom random = new SecureRandom(); + byte[] salt = new byte[SALT_BYTE_SIZE]; + random.nextBytes(salt); + + // Hash the password + byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE); + + return new HashingResult(PBKDF2_ITERATIONS, toHex(hash), toHex(salt)); + } + + /** + * Validates a password using a hash. + * + * @param password + * the password to check + * @param correctHash + * the hash of the valid password + * @return true if the password is correct, false if not + */ + public static boolean validatePassword(String password, String correctHash) + throws NoSuchAlgorithmException, InvalidKeySpecException { + return validatePassword(password.toCharArray(), correctHash); + } + + /** + * Validates a password using a hash. + * + * @param password + * the password to check + * @param correctHash + * the hash of the valid password + * @return true if the password is correct, false if not + */ + public static boolean validatePassword(char[] password, String correctHash) + throws NoSuchAlgorithmException, InvalidKeySpecException { + // Decode the hash into its parameters + String[] params = correctHash.split(":"); + int iterations = Integer.parseInt(params[ITERATION_INDEX]); + return validatePassword(password, iterations, params[SALT_INDEX], params[PBKDF2_INDEX]); + } + + public static boolean validatePassword(char[] password, int iterations, + String saltHex, String hashHex) { + byte[] salt = fromHex(saltHex); + byte[] hash = fromHex(hashHex); + // Compute the hash of the provided password, using the same salt, + // iteration count, and hash length + byte[] testHash = pbkdf2(password, salt, iterations, hash.length); + // Compare the hashes in constant time. The password is correct if + // both hashes match. + return slowEquals(hash, testHash); + } + + + + + public static boolean slowEquals(String hash1, String hash2) { + return slowEquals(fromHex(hash1), fromHex(hash2)); + } + + /** + * Compares two byte arrays in length-constant time. This comparison method + * is used so that password hashes cannot be extracted from an on-line + * system using a timing attack and then attacked off-line. + * + * @param a + * the first byte array + * @param b + * the second byte array + * @return true if both byte arrays are the same, false if not + */ + private static boolean slowEquals(byte[] a, byte[] b) { + int diff = a.length ^ b.length; + for (int i = 0; i < a.length && i < b.length; i++) + diff |= a[i] ^ b[i]; + return diff == 0; + } + + /** + * Computes the PBKDF2 hash of a password. + * + * @param password + * the password to hash. + * @param salt + * the salt + * @param iterations + * the iteration count (slowness factor) + * @param bytes + * the length of the hash to compute in bytes + * @return the PBDKF2 hash of the password + */ + private static byte[] pbkdf2(char[] password, byte[] salt, int iterations, + int bytes) { + try { + PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, + bytes * 8); + SecretKeyFactory skf = SecretKeyFactory + .getInstance(PBKDF2_ALGORITHM); + return skf.generateSecret(spec).getEncoded(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (InvalidKeySpecException e) { + throw new RuntimeException(e); + } + } + + /** + * Converts a string of hexadecimal characters into a byte array. + * + * @param hex + * the hex string + * @return the hex string decoded into a byte array + */ + private static byte[] fromHex(String hex) { + byte[] binary = new byte[hex.length() / 2]; + for (int i = 0; i < binary.length; i++) { + binary[i] = (byte) Integer.parseInt( + hex.substring(2 * i, 2 * i + 2), 16); + } + return binary; + } + + /** + * Converts a byte array into a hexadecimal string. + * + * @param array + * the byte array to convert + * @return a length*2 character string encoding the byte array + */ + private static String toHex(byte[] array) { + BigInteger bi = new BigInteger(1, array); + String hex = bi.toString(16); + int paddingLength = (array.length * 2) - hex.length(); + if (paddingLength > 0) + return String.format("%0" + paddingLength + "d", 0) + hex; + else + return hex; + } + + + +} \ No newline at end of file -- cgit v1.2.3