//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// MicroSafe source file
// Copyright (c) 2006 Elmar Sonnenschein / esoco GmbH
// Last Change: 28.09.2006 by eso
//
// MicroSafe is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// MicroSafe 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
// MicroSafe; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA or use the contact information
// from the GNU website http://www.gnu.org
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
package de.esoco.microsafe.crypto;
import de.esoco.j2me.resource.ResourceBundle;
import de.esoco.j2me.util.Arrays;
import de.esoco.j2me.util.ProgressMonitor;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.engines.TwofishEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
/********************************************************************
* An instance of this class handles the encryption and decryption of data with
* a particular algorithm.
*
* @author eso
*/
public class CryptoHandler
{
//~ Static fields/initializers ---------------------------------------------
/** Char constant for current encryption type (T = Twofish) */
private static final char ENCRYPTION_TYPE = 'T';
private static final int MAX_KEY_LENGTH = 32;
private static final byte KEY_VERIFICATION_VERSION = 1;
//~ Instance fields --------------------------------------------------------
private BufferedBlockCipher aCipher;
private KeyParameter aFullKey;
/**
* If set this monitor will be notified of encryption and other long tasks
*/
private ProgressMonitor rProgressMonitor = null;
//~ Constructors -----------------------------------------------------------
/***************************************
* Constructor that initializes a default cryptographic cipher (currently a
* Twofish cipher) with a byte array key.
*
* @param rKey A byte array containing the key to encrypt the data with
*/
public CryptoHandler(byte[] rKey)
{
this(new TwofishEngine(), rKey);
}
/***************************************
* Initialize the cryptographic engine with a particular algorithm and a
* byte array key.
*
* @param rAlgorithm The encryption algorithm cipher to use
* @param rKey A byte array containing the key to encrypt the data
* with
*/
public CryptoHandler(BlockCipher rAlgorithm, byte[] rKey)
{
SHA1Digest aDigest = new SHA1Digest();
byte[] aKey = new byte[32];
aCipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rAlgorithm));
aDigest.update(rKey, 0, rKey.length);
aDigest.doFinal(aKey, 0);
for (int i = aDigest.getDigestSize(); i < aKey.length; i++)
{
aKey[i] = (byte) i;
}
System.arraycopy(rKey, 0, aKey, 0, rKey.length);
aFullKey = new KeyParameter(aKey);
}
//~ Methods ----------------------------------------------------------------
/***************************************
* Returns a char constant describing the encryption type used by
* CryptoHandler instances.
*
* @return The encryption type constant
*/
public static char getEncryptionType()
{
return ENCRYPTION_TYPE;
}
/***************************************
* Returns the maximum key length in bytes that the crypto handler will use
* when it evaluates a key.
*
* @return The maximum key length in bytes
*/
public static int getMaxKeyLength()
{
return MAX_KEY_LENGTH;
}
/***************************************
* Decrypts an array of byte values with the set key and returns a new array
* with the result.
*
* @param rData The data to decrypt
*
* @return A new byte array with the decrypted data
*
* @throws CryptoException If a cryptography error occurs
*/
public byte[] decrypt(byte[] rData) throws CryptoException
{
if ((rData == null) || (rData.length == 0))
{
return new byte[0];
}
return callCipher(rData, false);
}
/***************************************
* Decrypts an array of byte values with the set key and returns a string.
*
* @param rData The data to decrypt
*
* @return A new byte array with the decrypted data
*
* @throws CryptoException If a cryptography error occurs
*/
public String decryptString(byte[] rData) throws CryptoException
{
return new String(decrypt(rData));
}
/***************************************
* Encrypts an array of byte values with the set key and returns the
* encrypted data in a new byte array.
*
* @param rData The data to encrypt
*
* @return A new byte array with the encrypted data
*
* @throws CryptoException If a cryptography error occurs
*/
public byte[] encrypt(byte[] rData) throws CryptoException
{
if ((rData == null) || (rData.length == 0))
{
return new byte[0];
}
return callCipher(rData, true);
}
/***************************************
* Encrypts a string with the set key and returns the encrypted data in a
* new byte array.
*
* @param sData The data to encrypt
*
* @return A new byte array with the encrypted data
*
* @throws CryptoException If a cryptography error occurs
*/
public byte[] encryptString(String sData) throws CryptoException
{
return encrypt(sData.getBytes());
}
/***************************************
* To check if another CrytptoHandler contains the same encryption
* parameters as this instance.
*
* @param rObj The object to compare with this one
*
* @return TRUE if the object is an equal CryptoHandler
*/
public boolean equals(Object rObj)
{
if (rObj instanceof CryptoHandler)
{
CryptoHandler rOther = (CryptoHandler) rObj;
String a1 = rOther.aCipher.getUnderlyingCipher()
.getAlgorithmName();
String a2 = aCipher.getUnderlyingCipher()
.getAlgorithmName();
byte[] rOtherKey = rOther.aFullKey.getKey();
return ((a1 == a2) && Arrays.equals(rOtherKey, aFullKey.getKey()));
}
else
{
return false;
}
}
/***************************************
* Creates encrypted data that can be used for later verification of the
* internal key with the method <code>verifyKey()</code>.
*
* @param nIterations The number of iterations to perform to increase
* security
*
* @return A new byte array containing the key verification data
*
* @see #verifyKey(byte[])
*/
public byte[] generateKeyVerification(int nIterations)
throws CryptoException
{
byte[] aVerifyData;
byte[] aEncryptedKey = aFullKey.getKey();
if (rProgressMonitor != null)
{
rProgressMonitor.init(nIterations,
ResourceBundle.getCurrent().getString("MS_KeySetup"));
}
try
{
for (int i = 0; i < nIterations; i++)
{
aEncryptedKey = encrypt(aEncryptedKey);
if (rProgressMonitor != null)
{
rProgressMonitor.advance(1);
}
}
}
finally
{
if (rProgressMonitor != null)
{
rProgressMonitor.reset();
}
}
aVerifyData = new byte[aEncryptedKey.length + 2];
aVerifyData[0] = KEY_VERIFICATION_VERSION;
aVerifyData[1] = (byte) nIterations;
System.arraycopy(aEncryptedKey, 0, aVerifyData, 2,
aEncryptedKey.length);
return aVerifyData;
}
/***************************************
* To return a copy of the internal encryption key.
*
* @return A new byte array containing a copy of the key
*/
public byte[] getKey()
{
return Arrays.copy(aFullKey.getKey());
}
/***************************************
* Sets the current progress monitor instance used by the handler. If set
* this monitor will be notified of long running encryption tasks.
*
* @param aMonitor The progress monitor to notify or NULL to disable
*/
public void setProgressMonitor(ProgressMonitor aMonitor)
{
rProgressMonitor = aMonitor;
}
/***************************************
* Verifies a key based on a data set generated previously by a call to the
* method <code>generateKeyVerification()</code>. If the key is not valid an
* InvalidKeyException will be thrown.
*
* @param rVerifyData A data set created by generateKeyVerification()
*
* @throws InvalidKeyException If the key could not be verified
* @throws CryptoException If the de/encryption process involved fails
*
* @see #generateKeyVerification()
*/
public void verifyKey(byte[] rVerifyData) throws CryptoException,
InvalidKeyException
{
boolean bVerified = false;
if (rVerifyData[0] == KEY_VERIFICATION_VERSION)
{
int nLoops = rVerifyData[1];
byte[] aCompare = generateKeyVerification(nLoops);
bVerified = Arrays.equals(rVerifyData, aCompare);
}
if (!bVerified)
{
throw new InvalidKeyException();
}
}
/***************************************
* Internal routine that invokes the cryptographic algorithm.
*
* @param rData The data to convert
* @param bEncrypt TRUE for encryption, FALSE for decryption
*
* @return A new byte array containing the result data
*
* @throws CryptoException If initialisation or excution of the cipher fails
*/
private byte[] callCipher(byte[] rData, boolean bEncrypt)
throws CryptoException
{
try
{
aCipher.init(bEncrypt, aFullKey);
int nLen = rData.length;
int nOutSize = aCipher.getOutputSize(nLen);
byte[] aResult = new byte[nOutSize];
nLen = aCipher.processBytes(rData, 0, nLen, aResult, 0);
nLen += aCipher.doFinal(aResult, nLen);
if (nLen < nOutSize)
{
// if the generated data is shorter (may happen due to padding)
// then copy the output to a smaller buffer
byte[] tmp = new byte[nLen];
System.arraycopy(aResult, 0, tmp, 0, nLen);
aResult = tmp;
}
return aResult;
}
catch (Exception e)
{
throw new CryptoException("Cryptography failure: " +
e.getMessage());
}
}
}