From 200ef5bb3fd4f194c47518e0633d7e8bb6ab6d79 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Wed, 1 Jul 2015 11:47:14 +1200 Subject: Re-implement hashing class --- src/org/traccar/helper/Hashing.java | 82 +++++++++-- src/org/traccar/helper/PasswordHash.java | 231 ------------------------------- src/org/traccar/model/User.java | 7 +- 3 files changed, 73 insertions(+), 247 deletions(-) delete mode 100644 src/org/traccar/helper/PasswordHash.java (limited to 'src/org') diff --git a/src/org/traccar/helper/Hashing.java b/src/org/traccar/helper/Hashing.java index 3f3e30e6e..000821451 100644 --- a/src/org/traccar/helper/Hashing.java +++ b/src/org/traccar/helper/Hashing.java @@ -1,21 +1,79 @@ +/* + * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * + * Licensed 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.traccar.helper; -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; public class Hashing { - - public static byte[] sha256(String text) { + + public static final int ITERATIONS = 1000; + public static final int SALT_SIZE = 24; + public static final int HASH_SIZE = 24; + + public static class HashingResult { + + public final String hash; + public final String salt; + + public HashingResult(String hash, String salt) { + this.hash = hash; + this.salt = salt; + } + } + + private static byte[] function(char[] password, byte[] salt) { try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(text.getBytes("UTF-8")); - return md.digest(); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); + PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, HASH_SIZE * Byte.SIZE); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + return factory.generateSecret(spec).getEncoded(); + } catch (GeneralSecurityException error) { + throw new SecurityException(error); + } + } + + private static SecureRandom random = new SecureRandom(); + + public static HashingResult createHash(String password) { + byte[] salt = new byte[SALT_SIZE]; random.nextBytes(salt); + byte[] hash = function(password.toCharArray(), salt); + return new HashingResult( + ChannelBufferTools.convertByteArray(hash), + ChannelBufferTools.convertByteArray(salt)); + } + + public static boolean validatePassword(String password, String hashHex, String saltHex) { + byte[] hash = ChannelBufferTools.convertHexString(hashHex); + byte[] salt = ChannelBufferTools.convertHexString(saltHex); + return slowEquals(hash, function(password.toCharArray(), salt)); + } + + /** + * 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. + */ + 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; } } diff --git a/src/org/traccar/helper/PasswordHash.java b/src/org/traccar/helper/PasswordHash.java deleted file mode 100644 index 98ded0988..000000000 --- a/src/org/traccar/helper/PasswordHash.java +++ /dev/null @@ -1,231 +0,0 @@ -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 diff --git a/src/org/traccar/model/User.java b/src/org/traccar/model/User.java index a838ef018..bcb03804c 100644 --- a/src/org/traccar/model/User.java +++ b/src/org/traccar/model/User.java @@ -15,9 +15,8 @@ */ package org.traccar.model; +import org.traccar.helper.Hashing; import org.traccar.helper.IgnoreOnSerialization; -import org.traccar.helper.PasswordHash; -import org.traccar.helper.PasswordHash.HashingResult; public class User implements Factory { @@ -77,11 +76,11 @@ public class User implements Factory { } public boolean isPasswordValid(String inputPassword) { - return PasswordHash.validatePassword(inputPassword.toCharArray(), PasswordHash.PBKDF2_ITERATIONS, this.salt, this.hashedPassword); + return Hashing.validatePassword(inputPassword, this.hashedPassword, this.salt); } public void hashPassword(String password) { - HashingResult hashingResult = PasswordHash.createHash(password); + Hashing.HashingResult hashingResult = Hashing.createHash(password); this.hashedPassword = hashingResult.hash; this.salt = hashingResult.salt; } -- cgit v1.2.3