/******************************************************************************
* Product: Adempiere ERP & CRM Smart Business Solution *
* Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. 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, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA *
* or via info@compiere.org or http://www.compiere.org/license.html *
*****************************************************************************/
package org.compiere.util;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Properties;
import java.util.logging.Level;
/**
* Secure engine for encryption and decryption
*
* @author Jorg Janke
* @version $Id: SecureEngine.java,v 1.2 2006/07/30 00:52:23 jjanke Exp $
*/
public class SecureEngine
{
/**
* Initialize Security
* @param ctx context with ADEMPIERE_SECURE class name
*/
public static void init (Properties ctx)
{
if (s_engine == null)
{
String className = ctx.getProperty(SecureInterface.ADEMPIERE_SECURE);
s_engine = new SecureEngine(className);
}
} // init
/**
* Initialize/Test Security
* @param className class name
*/
public static void init (String className)
{
if (s_engine == null)
s_engine = new SecureEngine(className);
else if (className != null && !className.equals(getClassName()))
{
String msg = "Requested Security class = " + className
+ " is not the same as the active class = " + getClassName()
+ "\nMake sure to set the security class in the start script";
log.severe(msg);
System.err.println(msg);
System.exit(10);
}
} // init
/**
* Get Class Name
* @return class name
*/
public static String getClassName()
{
if (s_engine == null)
return null;
return s_engine.implementation.getClass().getName();
} // getClassName
/**
* Convert String and salt to SHA-512 hash with iterations
* https://www.owasp.org/index.php/Hashing_Java
*
* @param value message
* @return HexString of message (length = 128 characters)
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
public static String getSHA512Hash (int iterations, String value, byte[] salt) throws NoSuchAlgorithmException, UnsupportedEncodingException
{
if (s_engine == null)
init(System.getProperties());
return s_engine.implementation.getSHA512Hash(iterations, value, salt);
} // getDigest
/**
* Perform MD5 Digest of value.
* JavaScript version see - http://pajhome.org.uk/crypt/md5/index.html
*
* @param value message
* @return HexString of digested message (length = 32 characters)
*/
public static String getDigest (String value)
{
if (s_engine == null)
init(System.getProperties());
return s_engine.implementation.getDigest(value);
} // getDigest
/**
* Encryption.
* The methods must recognize clear text values
* @param value clear value
* @param AD_Client_ID
* @return encrypted String
*/
public static String encrypt (String value,int AD_Client_ID)
{
if (value == null || value.length() == 0)
return value;
if (s_engine == null)
init(System.getProperties());
//
boolean inQuotes = value.startsWith("'") && value.endsWith("'");
if (inQuotes)
value = value.substring(1, value.length()-1);
//
String retValue = s_engine.implementation.encrypt(value,AD_Client_ID);
if (inQuotes)
return "'" + retValue + "'";
return retValue;
} // encrypt
/**
* Decryption.
* The methods must recognize clear text values
* @param value encrypted value
* @param AD_Client_ID
* @return decrypted String
*/
public static String decrypt (String value, int AD_Client_ID)
{
if (value == null)
return null;
if (s_engine == null)
init(System.getProperties());
boolean inQuotes = value.startsWith("'") && value.endsWith("'");
if (inQuotes)
value = value.substring(1, value.length()-1);
String retValue = null;
if (value.startsWith(SecureInterface.CLEARVALUE_START) && value.endsWith(SecureInterface.CLEARVALUE_END))
retValue = value.substring(SecureInterface.CLEARVALUE_START.length(), value.length()-SecureInterface.CLEARVALUE_END.length());
else
retValue = s_engine.implementation.decrypt(value,AD_Client_ID);
if (inQuotes)
return "'" + retValue + "'";
return retValue;
} // decrypt
/**
* Encrypt value (only implemented for String).
* @param value clear value
* @param AD_Client_ID
* @return encrypted String
*/
public static Object encrypt (Object value, int AD_Client_ID)
{
if (value instanceof String)
return encrypt((String) value, AD_Client_ID);
return value;
} // encrypt
/**
* Decrypt value (only implemented for String)
* @param value encrypted value
* @return decrypted String
*/
public static Object decrypt (Object value, int AD_Client_ID)
{
if (value instanceof String)
return decrypt((String) value, AD_Client_ID);
return value;
} // decrypt
/**
* SecureEngine constructor
* @param className class name if null defaults to org.compiere.util.Secure
*/
private SecureEngine (String className)
{
String realClass = className;
if (realClass == null || realClass.length() == 0)
realClass = SecureInterface.ADEMPIERE_SECURE_DEFAULT;
Exception cause = null;
try
{
Class> clazz = Class.forName(realClass);
implementation = (SecureInterface)clazz.getDeclaredConstructor().newInstance();
}
catch (Exception e)
{
cause = e;
}
if (implementation == null)
{
String msg = "Could not initialize: " + realClass + " - " + cause.toString()
+ "\nCheck start script parameter ADEMPIERE_SECURE";
log.severe(msg);
System.err.println(msg);
System.exit(10);
}
// See if it works
String testE = implementation.encrypt(TEST,0);
String testC = implementation.decrypt(testE,0);
if (!testC.equals(TEST))
throw new IllegalStateException(realClass
+ ": " + TEST
+ "->" + testE + "->" + testC);
if (log.isLoggable(Level.CONFIG)) log.config (realClass + " initialized - " + implementation);
} // SecureEngine
/**
* Use salt in hex form and text hashed compare with plan text.
* If has exception in hash, log to server.
* @param hashedText
* @param hexSalt
* @param planText
* @return true if valid
*/
public static boolean isMatchHash (String hashedText, String hexSalt, String planText){
boolean valid=false;
// always do calculation to prevent timing based attacks
if ( hashedText == null )
hashedText = "0000000000000000";
if ( hexSalt == null )
hexSalt = "0000000000000000";
try {
valid= SecureEngine.getSHA512Hash(1000, planText, Secure.convertHexString(hexSalt)).equals(hashedText);
} catch (NoSuchAlgorithmException ignored) {
log.log(Level.WARNING, "Password hashing not supported by JVM");
} catch (UnsupportedEncodingException ignored) {
log.log(Level.WARNING, "Password hashing not supported by JVM");
}
return valid;
}
/** Test String */
private static final String TEST = "This is a 0123456789 .,; -= Test!";
/** Secure Engine */
private volatile static SecureEngine s_engine = null;
/** The real Engine */
private SecureInterface implementation = null;
/** Logger */
private static CLogger log = CLogger.getCLogger (SecureEngine.class.getName());
} // SecureEngine