package er.extensions.crypting;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import javax.crypto.Cipher;
import com.webobjects.foundation.NSForwardException;
import er.extensions.foundation.ERXStringUtilities;
/**
* ERXAbstractBlowfishCrypter is a blowfish implementation of the crypter
* interface that allows subclasses to override the source of the blowfish key.
* The blowfish cipher is a two-way cipher meaning the original string that was
* encrypted can be retrieved. The way that this version of the blowfish cipher
* is encrypted it is safe to use as a form value.
*
* @author mschrag
*/
public abstract class ERXAbstractBlowfishCrypter implements ERXCrypterInterface {
/** Block size of blowfish encrypted strings */
private int _blockSize;
/** Used to cache the blowfish encryption cipher */
private Cipher _encryptCipher;
/** Used to cache the blowfish decryption cipher */
private Cipher _decryptCipher;
public ERXAbstractBlowfishCrypter() {
_blockSize = 8;
}
/**
* Sets the block size to use for this cipher.
*
* @param blockSize
* the block size to use for this cipher
*/
public void setBlockSize(int blockSize) {
_blockSize = blockSize;
}
/**
* Returns the block size for this cipher.
*
* @return the block size for this cipher
*/
public int blockSize() {
return _blockSize;
}
/**
* Returns the secret key to use for this cipher.
*
* @return a secret key for the blowfish cipher
*/
protected abstract Key secretBlowfishKey() throws Exception;
/**
* Creates a blowfish cipher for a given mode. The two possible modes for a
* blowfish cipher are: ENCRYPT and DECRYPT.
*
* @param mode
* of the cipher (encrypting or decrypting)
* @return a blowfish cipher initialized with the given mode and with the
* <code>secretKey</code> from the above method.
*/
protected Cipher createBlowfishCipher(int mode) {
try {
Cipher cipher = Cipher.getInstance("Blowfish/ECB/NoPadding");
cipher.init(mode, secretBlowfishKey());
return cipher;
}
catch (java.security.NoSuchAlgorithmException ex) {
throw new NSForwardException(ex, "Couldn't find the Blowfish algorithm; perhaps you do not have the SunJCE security provider installed properly?");
}
catch (Exception e) {
throw new NSForwardException(e);
}
}
/**
* Method used to return the shared instance of the blowfish encryption
* cipher.
*
* @return blowfish encryption cipher
*/
protected Cipher encryptCipher() {
if (_encryptCipher == null) {
_encryptCipher = createBlowfishCipher(Cipher.ENCRYPT_MODE);
}
return _encryptCipher;
}
/**
* Method used to return the shared instance of the blowfish decryption
* cipher.
*
* @return blowfish decryption cipher
*/
protected Cipher decryptCipher() {
if (_decryptCipher == null) {
_decryptCipher = createBlowfishCipher(Cipher.DECRYPT_MODE);
}
return _decryptCipher;
}
/**
* Decodes a blowfish encoded string. Note that the originally encoded
* string should have been encoded with the same secret key as is used for
* the decoding cipher or else you are going to get garbage. To encode a
* string have a look at <code>blowfishEncode</code>.
*
* @param cryptedText
* blowfish encoded string to be decoded
* @return decode clear text string
*/
public String decrypt(String cryptedText) {
if (cryptedText == null) {
return null;
}
int length = cryptedText.length();
if (length % 16 != 0) {
return null;
}
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] clearText = null;
byte[] encryptedBytes = new byte[_blockSize];
int i = 0;
for (int j = 0; j < length;) {
char c1 = cryptedText.charAt(j++);
int b1 = c1 < 'a' ? c1 - '0' : c1 - 'a' + 10;
char c2 = cryptedText.charAt(j++);
int b2 = c2 < 'a' ? c2 - '0' : c2 - 'a' + 10;
encryptedBytes[i++] = (byte) ((b1 << 4) + b2);
if (i == _blockSize) {
// we filled a block
try {
clearText = decryptCipher().doFinal(encryptedBytes);
}
catch (Exception e) {
throw new NSForwardException(e);
}
for (int k = 0; k < _blockSize; k++) {
if (clearText[k] != 0) {
result.write(clearText[k]);
}
}
i = 0;
}
}
if (i != 0) {
for (int j = i; j < _blockSize; j++) {
encryptedBytes[j] = 0;
}
try {
clearText = decryptCipher().doFinal(encryptedBytes);
}
catch (Exception e) {
throw new NSForwardException(e);
}
for (int k = 0; k < _blockSize; k++) {
result.write(clearText[k]);
}
}
return ERXStringUtilities.fromUTF8Bytes(result.toByteArray());
}
/**
* Blowfish encodes a given string using the secret key specified in the
* System property: <b>ERBlowfishCipherKey</b>. The blowfish cipher is a
* two way cipher meaning that given the secret key you can de-cipher what
* the original string is. For one-way encryption look at methods dealing
* with the Sha alogrithm. To decode a blowfish encoded string use the
* method: <code>blowfishDecode</code>. The resultant string from
* encoding a string is safe for use in urls and cookies.
*
* @param clearText
* string to be encrypted
* @return encrypted string
*/
public String encrypt(String clearText) {
if (clearText == null) {
return null;
}
byte clearTextBytes[] = ERXStringUtilities.toUTF8Bytes(clearText);
StringBuilder result = new StringBuilder();
int pos = 0, length = clearTextBytes.length;
byte[] bytesToEncrypt = new byte[_blockSize];
byte[] encryptedBytes = null;
while (pos < length) {
int k = 0;
for (int j = pos; j < length && j < pos + _blockSize; k++, j++) {
bytesToEncrypt[k] = clearTextBytes[j];
}
if (k < _blockSize) {
for (int l = k; l < _blockSize; l++) {
bytesToEncrypt[l] = 0;
}
}
try {
encryptedBytes = encryptCipher().doFinal(bytesToEncrypt);
}
catch (Exception e) {
throw new NSForwardException(e);
}
for (k = 0; k < _blockSize; k++) {
result.append(ERXStringUtilities.HEX_CHARS[(encryptedBytes[k] >>> 4) & 0xf]);
result.append(ERXStringUtilities.HEX_CHARS[encryptedBytes[k] & 0xf]);
}
pos += _blockSize;
}
return result.toString();
}
}