/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.web.security.hash;
import java.util.StringTokenizer;
import org.gatein.common.util.Base64.EncodingOption;
/**
* A codec able to transform a {@link SaltedHash} to and from {@link String}.
*
* The string representations produced by {@link XmlSafeSaltedHashCodec#encode(SaltedHash)} are granted to be valid XML element
* names if the length of byte arrays returned by {@link SaltedHash#getSalt()} and {@link SaltedHash#getHash()} is divisible by
* 12. This important because {@link XmlSafeSaltedHashCodec#encode(SaltedHash)} uses the URL safe variant of Byte64 encoding
* (see {@link org.gatein.common.util.Base64}) and for byte array lengths other than 12, the string encoded by Byte64 would have
* to be padded with character {@code '*'} which would make the result an invalid XML name.
*
* @author <a href="mailto:ppalaga@redhat.com">Peter Palaga</a>
*
*/
public class XmlSafeSaltedHashCodec implements SaltedHashCodec {
public static final SaltedHashCodec INSTANCE = new XmlSafeSaltedHashCodec();
/**
* Delimiter used to separate the fields of a {@link SaltedHash} in its string representation.
*/
private final char delimiter = '.';
/**
*
*/
public XmlSafeSaltedHashCodec() {
}
/*
* (non-Javadoc)
*
* @see org.exoplatform.web.security.hash.SaltedHashCodec#encode(org.exoplatform.web.security.hash.SaltedHash)
*/
@Override
public String encode(SaltedHash saltedHash) {
if (saltedHash == null) {
return null;
} else {
String salt = toString(saltedHash.getSalt());
String hash = toString(saltedHash.getHash());
String algorithm = saltedHash.getAlgorithm();
int iterationCount = saltedHash.getIterationCount();
/* initialize the buffer with exact size */
StringBuilder buffer = new StringBuilder(algorithm.length() + 8 // iterationCount digit count estimate
+ salt.length() + hash.length() + 3 // delimiter count
);
return buffer.append(algorithm).append(delimiter).append(iterationCount).append(delimiter).append(salt)
.append(delimiter).append(hash).toString();
}
}
/*
* (non-Javadoc)
*
* @see org.exoplatform.web.security.hash.SaltedHashCodec#decode(java.lang.String)
*/
@Override
public SaltedHash decode(String encodedSaltedHash) throws SaltedHashEncodingException {
StringTokenizer st = new StringTokenizer(encodedSaltedHash, String.valueOf(delimiter));
if (st.hasMoreTokens()) {
String algorithm = st.nextToken();
try {
if (st.hasMoreTokens()) {
int iterationCount = Integer.parseInt(st.nextToken());
if (st.hasMoreTokens()) {
String saltString = st.nextToken();
byte[] saltBytes = toBytes(saltString);
if (st.hasMoreTokens()) {
String hashString = st.nextToken();
byte[] hashBytes = toBytes(hashString);
return new SaltedHash(algorithm, iterationCount, saltBytes, hashBytes);
}
}
}
} catch (NumberFormatException e) {
throw new SaltedHashEncodingException("Could not decode salted hash '" + encodedSaltedHash + "'.", e);
}
}
throw new SaltedHashEncodingException("Encoded salted hash '" + encodedSaltedHash + "' too short.");
}
protected String toString(byte[] bytes) {
return org.gatein.common.util.Base64.encodeBytes(bytes, EncodingOption.USEURLSAFEENCODING);
}
protected byte[] toBytes(String str) {
return org.gatein.common.util.Base64.decode(str, EncodingOption.USEURLSAFEENCODING);
}
}