org.hashes.collision.AbstractMITMGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.hashes.collision.AbstractMITMGenerator.java

Source

/**
 *    Copyright 2012 Pedro Ribeiro
 *
 *    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.hashes.collision;

import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hashes.algorithm.HashAlgorithm;
import org.hashes.progress.ProgressMonitor;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;

/**
 * Base class of meet in the middle hash collision generator.
 * 
 * @author ribeirux
 * @version $Revision$
 */
public abstract class AbstractMITMGenerator extends AbstractCollisionGenerator {

    private static final Log LOG = LogFactory.getLog(AbstractMITMGenerator.class);

    private static final int LOOKUP_MAP_SIZE = (int) Math.pow(2, 18);

    private static final int LOOKUP_MAP_KEY_SIZE = 3;

    private static final int KEY_SIZE = 7;

    // inclusive
    private static final char START_KEY = ' ';

    // inclusive
    private static final char END_KEY = '~';

    private final String seed;

    private final int workerThreads;

    /**
     * Creates a new instance with specified hash algorithm and seed.
     * 
     * @param hashAlgorithm the hash algorithm
     * @param seed MITM seed
     */
    public AbstractMITMGenerator(final HashAlgorithm hashAlgorithm, final String seed) {
        this(hashAlgorithm, seed, null);
    }

    /**
     * Creates a new instance with specified hash algorithm and seed.
     * 
     * @param hashAlgorithm the hash algorithm
     * @param seed MITM seed
     * @param workerThreads number of worker threads, If null the number of available processors is used
     */
    public AbstractMITMGenerator(final HashAlgorithm hashAlgorithm, final String seed,
            final Integer workerThreads) {
        super(hashAlgorithm);
        this.seed = Preconditions.checkNotNull(seed, "seed");
        if (workerThreads == null) {
            this.workerThreads = Runtime.getRuntime().availableProcessors();
        } else {
            Preconditions.checkArgument(workerThreads > 0, "workerThreads");
            this.workerThreads = workerThreads;
        }
    }

    @Override
    protected List<String> generateNewCollisions(final int numberOfKeys, final ProgressMonitor monitor) {
        final int hash = this.getHashAlgorithm().hash(this.seed);
        final Map<Integer, String> lookupMap = this.createLookupMap(hash);
        final List<Callable<List<String>>> tasks = this.buildTasks(lookupMap, numberOfKeys, monitor);
        final ExecutorService executor = Executors.newFixedThreadPool(tasks.size());

        try {
            List<Future<List<String>>> results = Collections.emptyList();

            try {
                results = executor.invokeAll(tasks);
            } finally {
                executor.shutdown();
            }

            final Builder<String> collisions = ImmutableList.builder();
            for (final Future<List<String>> future : results) {
                collisions.addAll(future.get());
            }

            return collisions.build();
        } catch (final Exception e) {
            throw new ComputationException(e);
        }
    }

    private Map<Integer, String> createLookupMap(final int hash) {
        final Map<Integer, String> lookupMap = new HashMap<Integer, String>(LOOKUP_MAP_SIZE);
        for (int i = 0; i < LOOKUP_MAP_SIZE; i++) {
            final String sufix = this.randomString(LOOKUP_MAP_KEY_SIZE);
            lookupMap.put(this.hashBack(sufix, hash), sufix);
        }

        return Collections.unmodifiableMap(lookupMap);
    }

    private String randomString(final int size) {
        final StringBuilder random = new StringBuilder(size);
        for (int i = 0; i < size; i++) {
            random.append((char) (Math.random() * (END_KEY - START_KEY + 1) + START_KEY));
        }

        return String.valueOf(random);
    }

    private List<Callable<List<String>>> buildTasks(final Map<Integer, String> lookupMap, final int size,
            final ProgressMonitor monitor) {

        final int range = END_KEY - START_KEY + 1;
        final int maxWorkers = Math.min(range, this.workerThreads);

        if (maxWorkers != this.workerThreads && LOG.isWarnEnabled()) {
            LOG.warn(MessageFormat.format("The number of MITM worker threads is too high ({0}). Using: {1} threads",
                    this.workerThreads, maxWorkers));
        }

        final int interval = range / maxWorkers;
        final AtomicInteger keyCounter = new AtomicInteger();
        final Builder<Callable<List<String>>> tasks = ImmutableList.builder();
        for (int i = 0; i < maxWorkers; i++) {
            final char start = (char) (i * interval + START_KEY);
            final char end = (i == maxWorkers - 1 ? END_KEY : (char) (start + interval - 1));

            tasks.add(new MITMWorker(start, end, keyCounter, size, lookupMap, this.getHashAlgorithm(), monitor));
        }

        return tasks.build();
    }

    /**
     * Hash backwards.
     * 
     * @param key key to hash backwards
     * @param end final hash code
     * @return the backwards hash code
     */
    protected abstract int hashBack(final String key, final int end);

    /**
     * Meet in the middle hash collision worker.
     * 
     * @author ribeirux
     * @version $Revision$
     */
    private static final class MITMWorker implements Callable<List<String>> {

        private final char start;

        private final char end;

        private final AtomicInteger keyCounter;

        private final long maxNumberOfKeys;

        private final Map<Integer, String> lookupMap;

        private final HashAlgorithm hashAlgorithm;

        private final ProgressMonitor monitor;

        private MITMWorker(final char start, final char end, final AtomicInteger keyCounter,
                final int maxNumberOfKeys, final Map<Integer, String> lookupMap, final HashAlgorithm hashAlgorithm,
                final ProgressMonitor monitor) {
            this.start = start;
            this.end = end;
            this.keyCounter = keyCounter;
            this.maxNumberOfKeys = maxNumberOfKeys;
            this.lookupMap = lookupMap;
            this.hashAlgorithm = hashAlgorithm;
            this.monitor = monitor;
        }

        @Override
        public List<String> call() {
            final List<String> collisions = new LinkedList<String>();
            this.crack("", this.start, this.end, collisions);

            return collisions;
        }

        private void crack(final String prefix, final char startChar, final char endChar,
                final List<String> collisions) {
            if (prefix.length() == KEY_SIZE) {
                final String precomp = this.lookupMap.get(this.hashAlgorithm.hash(prefix));
                if (precomp != null) {
                    final int currentValue = this.keyCounter.getAndIncrement();
                    if (currentValue < this.maxNumberOfKeys) {
                        collisions.add(prefix + precomp);
                        this.monitor.update(currentValue);
                    }
                }
            } else {
                for (char i = startChar; (i <= endChar) && (this.maxNumberOfKeys > this.keyCounter.get()); i++) {
                    this.crack(prefix + i, START_KEY, END_KEY, collisions);
                }
            }
        }
    }
}