/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.felix.webconsole.internal.servlet; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** * The <code>Password</code> class encapsulates encoding and decoding * operations on plain text and hashed passwords. * <p> * Encoded hashed passwords are strings of the form * <code>{hashAlgorithm}base64-encoded-password-hash</code> where * <i>hashAlgorithm</i> is the name of the hash algorithm used to hash * the password and <i>base64-encoded-password-hash</i> is the password * hashed with the indicated hash algorithm and subsequently encoded in * Base64. */ class Password { // the default hash algorithm (part of the Java Platform since 1.4) private static final String DEFAULT_HASH_ALGO = "SHA-256"; // the hash algorithm used to hash the password or null // if the password is not hashed at all private final String hashAlgo; // the hashed or plain password private final byte[] password; /** * Returns {@code true} if the given {@code textPassword} is hashed * and encoded as described in the class comment. * * @param textPassword * @return * @throws NullPointerException if {@code textPassword} is {@code null}. */ static boolean isPasswordHashed( final String textPassword ) { return getEndOfHashAlgorithm( textPassword ) >= 0; } /** * Returns the given plain {@code textPassword} as an encoded hashed * password string as described in the class comment. * * @param textPassword * @return * @throws NullPointerException if {@code textPassword} is {@code null}. */ static String hashPassword( final String textPassword ) { final byte[] bytePassword = Base64.getBytesUtf8( textPassword ); return hashPassword( DEFAULT_HASH_ALGO, bytePassword ); } Password( String textPassword ) { this.hashAlgo = getPasswordHashAlgorithm( textPassword ); this.password = getPasswordBytes( textPassword ); } /** * Returns {@code true} if this password matches the password * {@code toCompare}. If this password is hashed, the {@code toCompare} * password is hashed, too, with the same hash algorithm before * comparison. * * @param toCompare * @return * @throws NullPointerException if {@code toCompare} is {@code null}. */ boolean matches( final byte[] toCompare ) { return Arrays.equals( this.password, hashPassword( toCompare, this.hashAlgo ) ); } /** * Returns this password as a string hashed and encoded as described * by the class comment. If this password has not been hashed originally, * the default hash algorithm <i>SHA-256</i> is applied. */ public String toString() { return hashPassword( this.hashAlgo, this.password ); } private static String hashPassword( final String hashAlgorithm, final byte[] password ) { final String actualHashAlgo = ( hashAlgorithm == null ) ? DEFAULT_HASH_ALGO : hashAlgorithm; final byte[] hashedPassword = hashPassword( password, actualHashAlgo ); final StringBuffer buf = new StringBuffer( 2 + actualHashAlgo.length() + hashedPassword.length * 3 ); buf.append( '{' ).append( actualHashAlgo.toLowerCase() ).append( '}' ); buf.append( Base64.newStringUtf8( Base64.encodeBase64( hashedPassword ) ) ); return buf.toString(); } private static String getPasswordHashAlgorithm( final String textPassword ) { final int endHash = getEndOfHashAlgorithm( textPassword ); if ( endHash >= 0 ) { return textPassword.substring( 1, endHash ); } // password is plain text, hence no algorithm return null; } private static byte[] getPasswordBytes( final String textPassword ) { final int endHash = getEndOfHashAlgorithm( textPassword ); if ( endHash >= 0 ) { final String encodedPassword = textPassword.substring( endHash + 1 ); return Base64.decodeBase64( encodedPassword ); } return Base64.getBytesUtf8( textPassword ); } private static int getEndOfHashAlgorithm( final String textPassword ) { if ( textPassword.startsWith( "{" ) ) { final int endHash = textPassword.indexOf( "}" ); if ( endHash > 0 ) { return endHash; } } return -1; } private static byte[] hashPassword( final byte[] pwd, final String hashAlg ) { // no hashing if no hash algorithm if ( hashAlg == null || hashAlg.length() == 0 ) { return pwd; } try { final MessageDigest md = MessageDigest.getInstance( hashAlg ); return md.digest( pwd ); } catch ( NoSuchAlgorithmException e ) { throw new IllegalStateException( "Cannot hash the password: " + e ); } } }