org.nuxeo.ecm.directory.PasswordHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.directory.PasswordHelper.java

Source

/*
 * (C) Copyright 2010-2016 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * 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.
 *
 * Contributors:
 *     Florent Guillaume
 */
package org.nuxeo.ecm.directory;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;

import org.apache.commons.codec.binary.Base64;

/**
 * Helper to check passwords and generated hashed salted ones.
 */
public class PasswordHelper {

    public static final String SSHA = "SSHA";

    public static final String SMD5 = "SMD5";

    private static final String HSSHA = "{SSHA}";

    private static final String HSMD5 = "{SMD5}";

    private static final String SHA1 = "SHA-1";

    private static final String MD5 = "MD5";

    private static final int SALT_LEN = 8;

    private static final Random random = new SecureRandom();

    // utility class
    private PasswordHelper() {
    }

    /**
     * Checks if a password is already hashed.
     *
     * @return {@code true} if the password is hashed
     */
    public static boolean isHashed(String password) {
        return password.startsWith(HSSHA) || password.startsWith(HSMD5);
    }

    /**
     * Returns the hashed string for a password according to a given hashing algorithm.
     *
     * @param algorithm the algorithm, {@link #SSHA} or {@link #SMD5}, or {@code null} to not hash
     * @param password the password
     * @return the hashed password
     */
    public static String hashPassword(String password, String algorithm) {
        if (algorithm == null || "".equals(algorithm)) {
            return password;
        }
        String digestalg;
        String prefix;
        if (SSHA.equals(algorithm)) {
            digestalg = SHA1;
            prefix = HSSHA;
        } else if (SMD5.equals(algorithm)) {
            digestalg = MD5;
            prefix = HSMD5;
        } else {
            throw new RuntimeException("Unknown algorithm: " + algorithm);
        }

        byte[] salt = new byte[SALT_LEN];
        synchronized (random) {
            random.nextBytes(salt);
        }
        byte[] hash = digestWithSalt(password, salt, digestalg);
        byte[] bytes = new byte[hash.length + salt.length];
        System.arraycopy(hash, 0, bytes, 0, hash.length);
        System.arraycopy(salt, 0, bytes, hash.length, salt.length);
        return prefix + Base64.encodeBase64String(bytes);
    }

    /**
     * Verify a password against a hashed password.
     * <p>
     * If the hashed password is {@code null} then the verification always fails.
     *
     * @param password the password to verify
     * @param hashedPassword the hashed password
     * @return {@code true} if the password matches
     */
    public static boolean verifyPassword(String password, String hashedPassword) {
        if (hashedPassword == null) {
            return false;
        }
        String digestalg;
        int len;
        if (hashedPassword.startsWith(HSSHA)) {
            digestalg = SHA1;
            len = 20;
        } else if (hashedPassword.startsWith(HSMD5)) {
            digestalg = MD5;
            len = 16;
        } else {
            return hashedPassword.equals(password);
        }
        String digest = hashedPassword.substring(6);

        byte[] bytes = Base64.decodeBase64(digest);
        if (bytes == null) {
            // invalid base64
            return false;
        }
        if (bytes.length < len + 2) {
            // needs hash + at least two bytes of salt
            return false;
        }
        byte[] hash = new byte[len];
        byte[] salt = new byte[bytes.length - len];
        System.arraycopy(bytes, 0, hash, 0, hash.length);
        System.arraycopy(bytes, hash.length, salt, 0, salt.length);
        return MessageDigest.isEqual(hash, digestWithSalt(password, salt, digestalg));
    }

    public static byte[] digestWithSalt(String password, byte[] salt, String algorithm) {
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            md.update(password.getBytes("UTF-8"));
            md.update(salt);
            return md.digest();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(algorithm, e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

}