com.android.pwdhashandroid.pwdhash.PasswordHasher.java Source code

Java tutorial

Introduction

Here is the source code for com.android.pwdhashandroid.pwdhash.PasswordHasher.java

Source

//
// PasswordHasher.java
//
// 2009 Jrgen Steinblock
//
// Based on
//
// PasswordHasher.cs
// 
// Copyright (C) 2008 Scott Wegner
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

package com.android.pwdhashandroid.pwdhash;

import java.security.GeneralSecurityException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.regex.Pattern;

import org.apache.http.util.EncodingUtils;

import com.android.pwdhashandroid.sharp2java.Base64Coder;
import com.android.pwdhashandroid.sharp2java.HMACMD5;

import android.util.Log;

/// <summary>
/// A class to create strong passwords that are always reproducible given
/// a URI from the original domain, and the passphrase.  The algorithm
/// uses a HMAC MD5 hash, along with a pseudo-random function used to 
/// assure password strength, and obfuscate the process.
/// </summary>
public class PasswordHasher {

    // Tag for LogCat
    private final static String tag = "PasswordHasher";

    /// <summary>
    /// Domain component of a website to create a password first.  Should
    /// be pre-processed by DomainExtractor to remove extra characters.
    /// </summary>
    protected String Domain;

    /// <summary>
    /// User's master site password.  Hashed with the domain to create a 
    /// strong password.
    /// </summary>
    protected String Passphrase;

    // used by ApplyConstrains helper functions
    private Queue<Character> extras;

    /// <summary>
    /// A prefix used in the password generation process.  This is included
    /// only for compatibility with the original PwdHash plugin.  Note that
    /// other classes access this as well, so it must be public.
    /// </summary>
    public final static String PasswordPrefix = "@@";

    /// <summary>
    /// Create a new PasswordHasher.  Initialize with the full domain that
    /// should be hashed.
    /// </summary>
    /// <param name="domain">The <see cref="System.String"/> domain component
    /// of an original URI</param>
    /// <param name="passphrase">The "master password" that is used for
    /// hashing with.</param>
    public PasswordHasher(String domain, String passphrase) {
        Domain = domain;
        Passphrase = passphrase;
    }

    /// <summary>
    /// Create a strong password from the given URI and passphrase
    /// </summary>
    /// <returns>
    /// A <see cref="System.String"/> representing the generated password
    /// </returns>
    public String GetHashedPassword() throws GeneralSecurityException {
        // Get initial hash
        String hash = B64HmacMd5(Passphrase, Domain);

        // Apply constraints
        int size = Passphrase.length() + PasswordPrefix.length();

        //boolean nonalphanumeric = new Regex("\\W").IsMatch(Passphrase);
        boolean nonalphanumeric = Pattern.compile("\\W").matcher(Passphrase).find();
        String result = ApplyConstraints(hash, size, nonalphanumeric);

        return result;
    }

    /// <summary>
    /// Create a base-64 encoded HMAC MD5 hash from a key and data string
    /// </summary>
    /// <param name="password">
    /// A <see cref="System.String"/> representing the inital password.
    /// This will be used as the "key" for MD5 encryption
    /// </param>
    /// <param name="realm">
    /// A <see cref="System.String"/> representing the initial password.
    /// This will be used as the "data" for MD5 encryption
    /// </param>
    /// <returns>
    /// A <see cref="System.String"/> representing the base-64 encoded hash
    /// function
    /// </returns>
    protected String B64HmacMd5(String password, String realm) throws GeneralSecurityException {

        Log.d(tag, "B64HmacMd5:");
        Log.d(tag, "password:\t" + password);
        Log.d(tag, "realm:\t\t" + realm);

        // Put data in byte arrays to use for MD5      
        byte[] key = EncodingUtils.getAsciiBytes(password);
        byte[] data = EncodingUtils.getAsciiBytes(realm);

        // Create our hash
        HMACMD5 hmac = new HMACMD5(key);
        byte[] hashb = hmac.ComputeHash(data);

        // Store it as a base-64 string
        String hash = Base64Coder.encode(hashb);

        // Remove trailing "=="'s, which are added for strict adherence
        // to MD5 RFC.

        //        Pattern p = Pattern.compile("=+$");
        //        Matcher m = p.matcher(hash);
        //        String newhash = m.replaceAll("");

        String newhash = hash.replaceAll("=+$", "");

        Log.d(tag, "hash:\t\t" + newhash);

        return newhash;
    }

    /// <summary>
    /// Augment the generated password to adhere to some constraints.
    /// Specifically, it should have at least one upper-case, one lower-case
    /// and one numeral character.  Also, if specified, it should contain
    /// at least one symbol.  Otherwise, it shouldn't contain any.  Then,
    /// trim it to be the specified length, and add some pseudo-entropy to
    /// make it harder to guess.
    /// </summary>
    /// <param name="hash">
    /// A <see cref="System.String"/> representing the initial password hash
    /// </param>
    /// <param name="size">
    /// A <see cref="System.Int32"/> containing the final size of the
    /// password
    /// </param>
    /// <param name="nonalphanumeric">
    /// A <see cref="System.Boolean"/> determining whether or not the final
    /// password should contain symbols
    /// </param>
    /// <returns>
    /// A <see cref="System.String"/> containing the final string, with
    /// constraints applied.
    /// </returns>
    protected String ApplyConstraints(String hash, int size, boolean nonalphanumeric) {
        Log.d(tag, "ApplyConstraints:");

        int startingSize = size - 4; // Leave room for extra characters  <-- this implicitly means that password has to be at least 2 chars long

        // String result = hash.substring(0, startingSize);

        // If the password is longer then the actual hash, return the hash,
        // otherwise only the first "startingSize" bits of the hash
        String result = startingSize > hash.length() ? hash : hash.substring(0, startingSize);

        // to avoid Exception for startingsize longer then hash, we return the length of the hash as sthe startinpoint if longer 
        extras = CreateQueue(
                hash.substring(startingSize > hash.length() ? hash.length() : startingSize).toCharArray());

        Pattern matchSymbol = Pattern.compile("\\W");
        //Regex matchSymbol = new Regex("\\W");

        Log.d(tag, "startingSize:\t" + startingSize);
        Log.d(tag, "result (start):\t" + result);
        Log.d(tag, "extras (start):\t" + new String(extras.toString()));
        Log.d(tag, "nonalphanumeric:\t" + nonalphanumeric);

        // Add the extra characters
        Log.d(tag, "A-1Z:\t\t\t");

        result += (Pattern.compile("[A-Z]").matcher(result).find() ? nextExtraChar() : nextBetween('A', 26));
        Log.d(tag, result);

        Log.d(tag, "a-z:\t\t\t");
        result += (Pattern.compile("[a-z]").matcher(result).find() ? nextExtraChar() : nextBetween('a', 26));
        Log.d(tag, result);

        Log.d(tag, "0-9:\t\t\t");
        result += (Pattern.compile("[0-9]").matcher(result).find() ? nextExtraChar() : nextBetween('0', 10));
        Log.d(tag, result);

        Log.d(tag, "\\W:\t\t\t");
        result += (Pattern.compile("\\W").matcher(result).find() && nonalphanumeric ? nextExtraChar() : '+');
        Log.d(tag, result);

        while (matchSymbol.matcher(result).find() && !nonalphanumeric) {
            //Log.d(tag, "Replace '" + matchSymbol.Match(result) + "':\t\t");
            //Log.d(tag, "Replace '" + matchSymbol.matcher(result).group() + "':\t\t");

            result = result.replaceFirst("\\W", Character.toString(nextBetween('A', 26))); // 
            Log.d(tag, result);
        }

        // Rotate the result to make it harder to guess the inserted locations
        Log.d(tag, "Rotate ");
        char[] rotateArr = result.toCharArray();
        rotateArr = rotate(rotateArr, nextExtra());
        result = new String(rotateArr);
        Log.d(tag, result);

        return result;
    }

    /* 
     * These functions are used in the ApplyConstraints method.  They are
     * provided as a convenience, and to make the code more readable.  They
     * also exist in the original pwdhash javascript code.
     */

    // Get the next extra character as an int if one exists-- otherwise 0   
    private int nextExtra() {
        if (extras.size() != 0)
            return (int) (extras.remove());
        else
            return 0;
    }

    // Get the next extra as a character
    private char nextExtraChar() {
        return (char) (nextExtra());
    }

    // Rotate the character array a given number of times
    private char[] rotate(char[] arr, int amount) {
        //      Log.d(tag, amount + "x:\t");

        Queue<Character> q = CreateQueue(arr);

        while (amount-- != 0) {
            q.add(q.remove());
            Log.d(tag, q.toString());
        }

        Character[] chars = (Character[]) q.toArray(new Character[0]);
        return CharacterToCharArray(chars);

        //      return new char[10]; 

        //      CSQueue q = new CSQueue(arr);
        //      while (amount-- != 0) {
        //         q.put(q.get());
        //         Log.d(tag, new String(q.toArray()));
        //      }
        //
        //      return q.toArray();
    }

    // Return a integer within the given bounds
    private int between(int min, int interval, int offset) {
        return min + offset % interval;
    }

    // Get the next extra character within the given bounds
    private char nextBetween(char baseChar, int interval) {
        return (char) (between((int) (baseChar), interval, nextExtra()));
    }

    private char[] CharacterToCharArray(Character[] chars) {
        char[] chars2 = new char[chars.length];

        for (int i = 0; i < chars.length; i++) {
            chars2[i] = chars[i];
        }

        return chars2;
    }

    private Queue<Character> CreateQueue(char[] chars) {
        Queue<Character> q = new LinkedList<Character>();

        for (int i = 0; i < chars.length; i++) {
            q.add(chars[i]);
        }

        return q;
    }

}