I need to convert a JCE based code to a Bouncy Castle based code. I'm completely new to Bouncy Castle and couldn't find a easy-to-understand introduction to this topic in general or my issue specifically. This is the JCE based class:
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import com.sun.crypto.provider.SunJCE;
public class JCEBlowfishEncrypterDecrypter {
private static final String ALGORITHM = "Blowfish";
public static SecretKeySpec key;
public static String crypt(String msg, String k) throws Exception {
SecretKeySpec key = init(k);
return Hex.byte2hex(intCrypt(msg, key));
}
public static String decrypt(String msg, String k) throws Exception {
SecretKeySpec key = init(k);
return new String(intDecrypt(Hex.hex2byte(msg), key));
}
private static byte[] intCrypt(String msg, SecretKeySpec key) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(1, key);
return cipher.doFinal(msg.getBytes());
}
private static byte[] intDecrypt(byte encrypted[], SecretKeySpec key) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(2, key);
return cipher.doFinal(encrypted);
}
private static SecretKeySpec init(String myKey) throws NoSuchAlgorithmException {
SunJCE sunJce = new SunJCE();
Security.addProvider(sunJce);
byte raw[] = myKey.getBytes();
return new SecretKeySpec(raw, ALGORITHM);
}
}
... And this is the Bouncy Castle based class:
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.engines.BlowfishEngine;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
public class BouncyCastleBlowfishEncrypterDecrypter {
public static String crypt(String msg, String key) throws Exception {
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new PKCS7Padding());
cipher.init(true, new KeyParameter(key.getBytes()));
byte[] inputAsBytes = msg.getBytes();
byte[] encryptedAsBytes = new byte[cipher.getOutputSize(inputAsBytes.length)];
int numberOfBytesCopiedOnEncryptedAsBytes = cipher.processBytes(inputAsBytes, 0, inputAsBytes.length,
encryptedAsBytes, 0);
cipher.doFinal(encryptedAsBytes, numberOfBytesCopiedOnEncryptedAsBytes);
return Hex.byte2hex(encryptedAsBytes);
}
public static String decrypt(String msg, String key) throws Exception {
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new PKCS7Padding());
cipher.init(false, new KeyParameter(key.getBytes()));
byte[] outputConvertedAsBytes = Hex.hex2byte(msg);
byte[] decryptedAsBytes = new byte[cipher.getOutputSize(outputConvertedAsBytes.length)];
int numberOfBytesCopiedOnDecryptedAsBytes = cipher.processBytes(outputConvertedAsBytes, 0,
outputConvertedAsBytes.length, decryptedAsBytes, 0);
cipher.doFinal(decryptedAsBytes, numberOfBytesCopiedOnDecryptedAsBytes);
return new String(decryptedAsBytes);
}
}
... And this is the main method:
public static void main(String[] args) throws Exception {
String encrypted = JCEBlowfishEncrypterDecrypter.crypt("Test", "mmTSQOFzSL9xAwXGLMEe1Q==");
String decrypted = JCEBlowfishEncrypterDecrypter.decrypt(encrypted, "mmTSQOFzSL9xAwXGLMEe1Q==");
System.out.println("--------------");
System.out.println("encrypted -> " + encrypted);
System.out.println("decrypted -> " + decrypted);
encrypted = BouncyCastleBlowfishEncrypterDecrypter.crypt("Test",
"mmTSQOFzSL9xAwXGLMEe1Q==");
decrypted = BouncyCastleBlowfishEncrypterDecrypter.decrypt(encrypted,
"mmTSQOFzSL9xAwXGLMEe1Q==");
System.out.println("--------------");
System.out.println("encrypted -> " + encrypted);
System.out.println("decrypted -> " + decrypted);
}
... But I get some strange additional characters when decrypting: What's wrong?
In the meantime, you have changed your original post, so I refer you to the original post and list all necessary changes:
BufferedBlockCipher
must be replaced by PaddedBufferedBlockCipher
and PKCS7Padding
must be specified.cipher.getOutputSize()
returns the size including the padding, which is why the buffer decryptedAsBytes
after unpadding is larger than the decrypted data. The actual size of the decrypted data (i.e. without padding) can be determined via the return values of cipher.processBytes()
and cipher.doFinal()
and used during decoding in conjunction with a suitable String
overload.Sample implementation:
import java.nio.charset.StandardCharsets;
import org.bouncycastle.crypto.engines.BlowfishEngine;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;
...
public static String crypt(String msg, String key) throws Exception {
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new PKCS7Padding()); // Fix 1: Apply PKCS#7 padding
cipher.init(true, new KeyParameter(key.getBytes(StandardCharsets.UTF_8)));
byte[] plaintext = msg.getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)];
int length = cipher.processBytes(plaintext, 0, plaintext.length, ciphertext, 0);
cipher.doFinal(ciphertext, length);
return Hex.toHexString(ciphertext);
}
public static String decrypt(String ciphertextHex, String key) throws Exception {
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new PKCS7Padding()); // Fix 1: Apply PKCS#7 padding
cipher.init(false, new KeyParameter(key.getBytes(StandardCharsets.UTF_8)));
byte[] ciphertext = Hex.decode(ciphertextHex);
byte[] decrypted = new byte[cipher.getOutputSize(ciphertext.length)];
int length = cipher.processBytes(ciphertext, 0, ciphertext.length, decrypted, 0);
length += cipher.doFinal(decrypted, length); // Fix 2: determine the actual size of the decrypted data
return new String(decrypted, 0, length, StandardCharsets.UTF_8); // only take the actual length into account when decoding
}
Further issues:
In the code, the encoding is missing in many getBytes()
and new String()
calls. This should be specified, as otherwise the platform-dependent default encoding is used intransparently.
Possibly, the Base64 decoding of the key is missing in the code (mmTSQOFzSL9xAwXGLMEe1Q==
is UTF-8 encoded 24 bytes long and Base64 decoded 16 bytes long; technically both are possible, as Blowfish allows key sizes between 4 and 56 bytes; usually the raw key is applied).
This is probably a legacy application, so the mode and algorithm cannot be changed. But for the sake of completeness: ECB is an insecure mode, better use a mode with IV (e.g. CBC) or even better authenticated encryption. The current standard AES is considered more secure than Blowfish.
Note that Base64 is more efficient than hex encoding.