Java Encrypt a file using aes256/ CBC/PKCS7Padding - java

I'm trying to encrypt a file using aes256- CBC-PKCS7Padding . I'm using bouncy castle library , but I get exception
java.lang.IllegalArgumentException: invalid parameter passed to AES init - org.bouncycastle.crypto.params.ParametersWithIV
at org.bouncycastle.crypto.engines.AESEngine.init(Unknown Source)
at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.init(Unknown Source)
Here the source code :
public class Crypto {
public static final int AES_Key_Size = 256;
public static int blockSize = 16;
private final BlockCipher AESCipher = new AESEngine();
private PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(AESCipher, new PKCS7Padding());
private byte[] IV;
private KeyParameter key;
public Crypto() throws NoSuchAlgorithmException {
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(AES_Key_Size);
SecretKey sk = kg.generateKey();
key = new KeyParameter(sk.getEncoded());
}
public void CryptoZip(File plikZip, File plikAES) throws IOException, DataLengthException, IllegalStateException, InvalidCipherTextException {
byte[] input = Files.readAllBytes(plikZip.toPath());
byte[] cryptOut = encrypt(input);
FileOutputStream fos = new FileOutputStream(plikAES);
fos.write(cryptOut);
fos.close();
}
private byte[] encrypt(byte[] input) throws DataLengthException, IllegalStateException, InvalidCipherTextException {
IV = new byte[blockSize];
SecureRandom random = new SecureRandom();
random.nextBytes(IV);
cipher.init(true, new ParametersWithIV(key, IV)); // problem here
byte[] output = new byte[cipher.getOutputSize(input.length)];
int bytesWrittenOut = cipher.processBytes(
input, 0, input.length, output, 0);
cipher.doFinal(output, bytesWrittenOut);
return output;
}
}
Any suggestion how to fix it and explanation what I'm doing wrong will be really helpful.

What you're missing is the indication of the mode. If that is missing then ECB mode is assumed, and ECB mode doesn't take an IV. So PaddedBufferedBlockCipher does do buffering, but for ECB mode. So the init mode simply passes the parameters to AESEngine, and AESEngine rejects the IV as it only accepts keys.
In your code, the following would be a direct fix for the problem:
private final CBCBlockCipher AESCipherCBC = new CBCBlockCipher(AESCipher);
private final PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(AESCipherCBC, new PKCS7Padding());
I'll include the following rewrite to show you a different way of writing this down. Note that I didn't touch upon handling the IV or the exceptions correctly. Obviously, for large files, you may want to stream the contents and/or to map your files.
// renamed as crypto is a horrible name
public class FileEncryptor {
// lets use all uppercase constant names
public static final int AES_KEY_SIZE = 256;
// only field needed, the rest can be generated on the fly
private final KeyParameter key;
public FileEncryptor() throws NoSuchAlgorithmException {
key = generateKey();
}
private static KeyParameter generateKey() {
// removed KeyGenerator as that's dependent on JCA crypto-API
SecureRandom keyRNG = new SecureRandom();
byte[] keyData = new byte[AES_KEY_SIZE / Byte.SIZE];
keyRNG.nextBytes(keyData);
return new KeyParameter(keyData);
}
// the code doesn't do anything with zip itself, so no need to include it in the method name
public void encryptFile(File plaintextFile, File ciphertextFile) throws IOException, DataLengthException, IllegalStateException, InvalidCipherTextException {
byte[] plaintext = Files.readAllBytes(plaintextFile.toPath());
byte[] ciphertext = encrypt(plaintext);
// try and be symmetric, use Files functionality for reading *and writing*
Files.write(ciphertextFile.toPath(), ciphertext);
}
private byte[] encrypt(byte[] plaintext) throws DataLengthException, IllegalStateException, InvalidCipherTextException {
// create cipher
final BlockCipher aes = new AESFastEngine();
CBCBlockCipher aesCBC = new CBCBlockCipher(aes);
PaddedBufferedBlockCipher aesCBCPadded =
new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
// create IV
byte[] iv = new byte[aes.getBlockSize()];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
// initialize cipher with IV
ParametersWithIV paramsWithIV = new ParametersWithIV(key, iv);
aesCBCPadded.init(true, paramsWithIV); // problem here
// encrypt
byte[] ciphertext = new byte[aesCBCPadded.getOutputSize(plaintext.length)];
int bytesWrittenOut = aesCBCPadded.processBytes(
plaintext, 0, plaintext.length, ciphertext, 0);
aesCBCPadded.doFinal(ciphertext, bytesWrittenOut);
// that's great, but where is your IV now? you need to include it in the returned ciphertext!
return ciphertext;
}
}

Related

Java AES / GCM decryption fails

I am trying to use GCM Mode for encryption and decryption. Unfortunately decryption doesn't work.
Do I have to use the same initialization vector for both encryption and decryption classes? I already tried that, unsuccessfully...
Could the random argument in keyGen.init(128, random) be the problem?
Encryption code:
public class AES128SymmetricEncryption {
private static final int GCM_NONCE_LENGTH = 12; // in bytes
private static final int GCM_TAG_LENGTH = 16; // in bytes
public static void encode (FileInputStream ciphertextSource, FileOutputStream plaintextDestination)
{
try {
int numRead;
SecureRandom random = SecureRandom.getInstanceStrong();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128, random);
SecretKey key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, getIV(random));
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] buf = new byte[2048];
while ((numRead = ciphertextSource.read(buf)) > 0) {
byte[] decryptedBlock = cipher.update(buf, 0, numRead);
plaintextDestination.write(decryptedBlock);
}
}
catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (plaintextDestination != null) {
ciphertextSource.close();
}
if (plaintextDestination != null) {
plaintextDestination.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static byte[] getIV(SecureRandom random) {
final byte[] nonce = new byte[GCM_NONCE_LENGTH];
random.nextBytes(nonce);
System.out.println(nonce);
return nonce;
}
public static void main(String[] args) throws GeneralSecurityException, IOException
{
Security.addProvider(new BouncyCastleProvider());
FileInputStream fis = new FileInputStream("C:/Users/roehrlef/Desktop/Test Data/Source Data/100KB.jpg");
FileOutputStream fos = new FileOutputStream("C:/Users/roehrlef/Desktop/Test Data/Encrypted Data/encrypted.jpg");
encode(fis, fos);
}
}
Decryption code:
public class AES128SymmetricDecryption {
private static final int GCM_NONCE_LENGTH = 12; // in bytes
private static final int GCM_TAG_LENGTH = 16; // in bytes
public static void decode (FileInputStream ciphertextSource, FileOutputStream plaintextDestination)
{
try {
int numRead = 0;
SecureRandom random = SecureRandom.getInstanceStrong();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128, random);
SecretKey key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, getIV(random));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
CipherInputStream cis = new CipherInputStream(ciphertextSource, cipher);
byte[] buf = new byte[2048];
while ((numRead = cis.read(buf)) > 0) {
byte[] decryptedBlock = cipher.update(buf, 0, numRead);
plaintextDestination.write(decryptedBlock);
}
}
catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (plaintextDestination != null) {
ciphertextSource.close();
}
if (plaintextDestination != null) {
plaintextDestination.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static byte[] getIV(SecureRandom random) {
final byte[] nonce = new byte[GCM_NONCE_LENGTH];
random.nextBytes(nonce);
System.out.println(nonce);
return nonce;
}
public static void main(String[] args) throws GeneralSecurityException, IOException
{
Security.addProvider(new BouncyCastleProvider());
FileInputStream fis = new FileInputStream("C:/Users/roehrlef/Desktop/Test Data/Encrypted Data/encrypted.jpg");
FileOutputStream fos = new FileOutputStream("C:/Users/roehrlef/Desktop/Test Data/Decrypted Data/decrypted.jpg");
decode(fis, fos);
}
}
You're using KeyGenerator twice; once for encryption and once for decryption. This class generates a new random key. With symmetric ciphers you need to use the same key for encryption and decryption (hence the name).
In general you should use the following classes for the following purposes:
For symmetric keys (e.g. AES, HMAC):
KeyGenerator: brand new secret (symmetric) keys;
SecretKeyFactory: decoding secret (symmetric) keys, for instance generated by the method Key#getEncoded() implemented by most key classes;
And for asymmetric public / private key pairs (e.g. RSA):
KeyPairGenerator: brand new public / private asymmetric key pairs;
KeyFactory: decoding public / private (asymmetric) keys from a stored key format, for instance generated by the method Key#getEncoded() implemented by most key classes;
Both symmetric and asymmetric keys may be stored in key stores:
KeyStore: storing keys / certificates in a key container such as PKCS#12 key stores;
Finally there are some other options for creating keys:
KeyAgreement: establishing a key by a key agreement function such as Diffie-Hellman key exchange;
Cipher#unwrap: unwrapping (decrypting) keys created using Cipher#wrap (or a similar function on another platform) with another key.
You should probably either store and retrieve the key in a KeyStore - which you can load / save to file. Note that not all key stores are created equal; Java 9 expanded the functionality of PKCS#12 key stores and made them the default. You code also encode the key and use a SecretKeyFactory to decode it again.
Or you can just cheat and reuse the SecretKey instance you generated during encryption, and implement key storage later. That would be good for testing purposes. In the end you need to share the key for symmetric encryption.
And yes, the IV needs to be identical on both sides. Usually it is just stored in front of the ciphertext. The IV should be unique for each encryption, so you have to use the random number generator over there.

AES encryption/decryption from c# to java

I'm using AES encryption/decryption algorithm in my application.
On the server side I use c# to encrypt/decrypt the data.
And on client side(android) I use java to decrypt the data.
C# encryption/decryption code
static readonly string PasswordHash = "52";
static readonly string SaltKey = "dfkjsadfinewdfadsfkmeoinmsdflksdflk";
static readonly string VIKey = "#EUBRHDFBFG8867";
public static string Encrypt(string plainText)
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
byte[] keyBytes = new Rfc2898DeriveBytes(PasswordHash,Encoding.ASCII.GetBytes(SaltKey)).GetBytes(256 / 8);
var symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC, Padding =PaddingMode.Zeros };
var encryptor = symmetricKey.CreateEncryptor(keyBytes,Encoding.ASCII.GetBytes(VIKey));
byte[] cipherTextBytes;
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor,CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
cipherTextBytes = memoryStream.ToArray();
cryptoStream.Close();
}
memoryStream.Close();
}
return Convert.ToBase64String(cipherTextBytes);
}
public static string Decrypt(string encryptedText)
{
byte[] cipherTextBytes = Convert.FromBase64String(encryptedText);
byte[] keyBytes = new Rfc2898DeriveBytes(PasswordHash,Encoding.ASCII.GetBytes(SaltKey)).GetBytes(256 / 8);
var symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC, Padding =PaddingMode.None }
var decryptor = symmetricKey.CreateDecryptor(keyBytes,Encoding.ASCII.GetBytes(VIKey));
var memoryStream = new MemoryStream(cipherTextBytes);
var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
byte[] plainTextBytes = new byte[cipherTextBytes.Length];
int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount).TrimEnd("\0".ToCharArray());
}
Java Decryption method
public String decrypt(String dataToDecrypt) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException
{
byte[] encryptedCombinedBytes = Base64.decodeBase64(dataToDecrypt.getBytes());
String saltKey = "dfkjsadfinewdfadsfkmeoinmsdflksdflk";
String password = "52";
String IVKey = "#EUBRHDFBFG8867";
PBKDF2Parameters p = new PBKDF2Parameters("HmacSHA256", "ASCII", saltKey.getBytes(), 8);
byte[] mEncryptedPassword = new PBKDF2Engine(p).deriveKey(password);
byte[] ivbytes = Arrays.copyOfRange(IVKey.getBytes(), 0, 16);
SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");
Cipher mCipher = Cipher.getInstance("AES/CBC/NoPadding");
mCipher.init(Cipher.DECRYPT_MODE, mSecretKeySpec, new IvParameterSpec(ivbytes));
byte[] encryptedTextBytes = Arrays.copyOfRange(encryptedCombinedBytes, 16, encryptedCombinedBytes.length);
byte[] decryptedTextBytes = mCipher.doFinal(encryptedTextBytes);
return new String(decryptedTextBytes, "UTF-8");
}
C# decryption method works fine and give the result string.
I cannot figure out the problem in Java decryption code. It runs and give me some garbage value.
EDIT
I can not edit anything on the server side.I just have to replicate the decryption in java decryption.
I dont know how to use passwordHash, saltKey and IVkey
First of all, you've switched the password and the salt around.
Second, PBKDF2 uses HMAC/SHA-1 as default. As far as I know that's also the default for Rfc2898DeriveBytes:
Implements password-based key derivation functionality, PBKDF2, by using a pseudo-random number generator based on HMACSHA1.
You should also never call getBytes without specifying the character set in Java, but this is probably not an issue for your current runtime.
These are comments on the code only; do not use CBC over network connections without integrity/authenticity protection.

What is the simplest way to encrypt/ decrypt a byte array using BouncyCastle's block cipher?

If I have a BlockCipher and a byte[] that I got from a String containing a secret message, what's the easiest way to get a byte[] of the message encrypted?
In the normal Java API, I could just do cipher.doFinal(secretMessage), but there doesn't seem to be anything like that here, it only processes blocks.
I know I can use a BufferedBlockCipher, but this still doesn't simplify things significantly. What's the easiest high-level way to use this cipher?
OK, so using the lightweight API and counter mode, which is one of the easiest and modern modes you would get:
public class BouncyEncrypt {
private static final int IV_SIZE = 16;
public static void main(String[] args) throws Exception {
// key should really consist of 16 random bytes
byte[] keyData = new byte[256 / Byte.SIZE];
KeyParameter key = new KeyParameter(keyData);
byte[] ciphertext = encryptWithAES_CTR(key, "owlstead");
System.out.println(decryptWithAES_CTR(key, ciphertext));
}
private static byte[] encryptWithAES_CTR(KeyParameter key, String in)
throws IllegalArgumentException, UnsupportedEncodingException,
DataLengthException {
// iv should be unique for each encryption with the same key
byte[] ivData = new byte[IV_SIZE];
SecureRandom rng = new SecureRandom();
rng.nextBytes(ivData);
ParametersWithIV iv = new ParametersWithIV(key, ivData);
SICBlockCipher aesCTR = new SICBlockCipher(new AESFastEngine());
aesCTR.init(true, iv);
byte[] plaintext = in.getBytes("UTF-8");
byte[] ciphertext = new byte[ivData.length + plaintext.length];
System.arraycopy(ivData, 0, ciphertext, 0, IV_SIZE);
aesCTR.processBytes(plaintext, 0, plaintext.length, ciphertext, IV_SIZE);
return ciphertext;
}
private static String decryptWithAES_CTR(KeyParameter key, byte[] ciphertext)
throws IllegalArgumentException, UnsupportedEncodingException,
DataLengthException {
if (ciphertext.length < IV_SIZE) {
throw new IllegalArgumentException("Ciphertext too short to contain IV");
}
ParametersWithIV iv = new ParametersWithIV(key, ciphertext, 0, IV_SIZE);
SICBlockCipher aesCTR = new SICBlockCipher(new AESFastEngine());
aesCTR.init(true, iv);
byte[] plaintext = new byte[ciphertext.length - IV_SIZE];
aesCTR.processBytes(ciphertext, IV_SIZE, plaintext.length, plaintext, 0);
return new String(plaintext, "UTF-8");
}
}
Counter mode does not require padding and is fully online, so you only have to call processBytes. For CBC mode you should look at PaddedBufferedBlockCipher. Still you would have slightly a tiny amount of buffer handling during decryption: before decryption you don't know the amount of padding that is present.
You could remove the IV code and the UTF-8 character decoding + exception handling, but you would be insecure and possibly incompatible. This code prefixes the IV to the ciphertext.
BouncyCastle implements the "normal Java API" so you can use Cipher.doFinal(String.getBytes()) you just have to specify the Provider "BC" when getting the Cipher: Cipher.getInstance("YourTransformation", "BC")
Use Bouncy Castle's CipherOutputStream. It's the closest thing to the Java API.
static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider();
public static void main(String[] args) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance("Threefish-1024", PROVIDER);
kg.init(1024);
KeyParameter key = new KeyParameter(kg.generateKey().getEncoded());
byte[] tweak = new byte[16];
TweakableBlockCipherParameters params = new TweakableBlockCipherParameters(key, tweak);
byte[] plaintext = "Hi! I'm cat!".getBytes();
byte[] ciphertext = encrypt(params, plaintext);
System.out.println(new String(decrypt(params, ciphertext)));
// prints "Hi! I'm cat!"
}
static byte[] encrypt(TweakableBlockCipherParameters params, byte[] plaintext) throws Exception {
return encryptOrDecrypt(true, params, plaintext);
}
static byte[] decrypt(TweakableBlockCipherParameters params, byte[] ciphertext) throws Exception {
return encryptOrDecrypt(false, params, ciphertext);
}
static byte[] encryptOrDecrypt(boolean encrypt, TweakableBlockCipherParameters params, byte[] bytes) throws Exception {
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new CBCBlockCipher(
new ThreefishEngine(ThreefishEngine.BLOCKSIZE_1024)), new PKCS7Padding());
cipher.init(encrypt, params);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
CipherOutputStream cos = new CipherOutputStream(baos, cipher);
cos.write(bytes);
// calling CipherOutputStream.close is mandatory
// it acts like Cipher.doFinal
cos.close();
return baos.toByteArray();
}
Link to my answer to a similar and related question.

RSA keyblock wrapper: javax.crypto.BadPaddingException: Decryption error

In a larger application doing other things - I need to encrypt and decrypt a file. So I have been looking around and have implemented these two core functions that basically use RSA keys to wrap a random AES key that encrypts a file. The symmetric key and iv are written to the start of the file.
I'm getting an exception ("javax.crypto.BadPaddingException: Decryption error") in the decrypt function portion of below. On the unpackKeyandIV line -- the doFinal. Specifically this line is the Exception point:
Object[] keyIv = unpackKeyAndIV(xCipher.doFinal(keyBlock));
I've checked and remade the RSA key pairs. I've also checked the save/load of the keyBlock.
My gut is the problem has something to do with how I write/read the keyBlock --- or encoding perhaps?
One goal is to keep the RSA/AES instance as generic as possible so as not to need Bouncy Castle or extra Java security unlimited strength extensions.
Any thoughts on where I might be going wrong.
Thanks in advance.
[Final update: This code below is working. Error was passing in a corrupted privKey]
// RSA_INSTANCE = "RSA";
// ASSYM_CRYPTO_STR = 1024;
// SYM_CRYPTO_STR = 128;
// SYM_CRYPTO = "AES";
// AES_INSTANCE = "AES/CTR/NoPadding";
//
// File in = plain input file
// File out = encrypted output file
// Key pubKey = public Key (that wraps a random AES key)
public static void encryptFile(File in, File out, Key pubKey) throws Exception {
FileInputStream fin;
FileOutputStream fout;
int nread = 0;
byte[] inbuf = new byte[1024];
fout = new FileOutputStream(out);
fin = new FileInputStream(in);
SecureRandom random = new SecureRandom();
// symmetric wrapping
Key sKey = createKeyForAES(Config.SYM_CRYPTO_STR, random);
IvParameterSpec sIvSpec = createCtrIvForAES(0, random);
// encrypt symmetric key with RSA/pub key
Cipher xCipher = Cipher.getInstance(Config.RSA_INSTANCE);
xCipher.init(Cipher.ENCRYPT_MODE, pubKey, random);
byte[] keyBlock = xCipher.doFinal(packKeyAndIv(sKey, sIvSpec));
fout.write(keyBlock);
// encrypt data with symmetric key
Cipher sCipher = Cipher.getInstance(Config.AES_INSTANCE);
sCipher.init(Cipher.ENCRYPT_MODE, sKey, sIvSpec);
// Now read our file and encrypt it.
while((nread = fin.read(inbuf)) > 0) {
fout.write(sCipher.update(inbuf, 0, nread)); // cannot be null, by construction
}
// NB doFinal() cannot return null, but can return a zero-length array, which is benign below.
fout.write(sCipher.doFinal());
fout.flush();
fin.close();
fout.close();
}
// Decrypt File
public static void decryptFile(File in, File out, Key privKey) throws Exception {
FileInputStream fin;
FileOutputStream fout;
int nread = 0;
byte[] inbuf = new byte[1024];
fout = new FileOutputStream(out);
fin = new FileInputStream(in);
byte[] keyBlock = new byte[128];
nread = fin.read(keyBlock);
Cipher xCipher = Cipher.getInstance(Config.RSA_INSTANCE);
Cipher sCipher = Cipher.getInstance(Config.AES_INSTANCE);
// symmetric key/iv unwrapping step
xCipher.init(Cipher.DECRYPT_MODE, privKey);
Object[] keyIv = unpackKeyAndIV(xCipher.doFinal(keyBlock));
// decryption step
sCipher.init(Cipher.DECRYPT_MODE, (Key)keyIv[0], (IvParameterSpec)keyIv[1]);
while((nread = fin.read(inbuf)) >0) {
fout.write(sCipher.update(inbuf,0,nread));
}
fout.write(sCipher.doFinal());
fout.flush();
fin.close();
fout.close();
}
public static byte[] packKeyAndIv(Key key, IvParameterSpec ivSpec) throws IOException {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
bOut.write(ivSpec.getIV());
bOut.write(key.getEncoded());
return bOut.toByteArray();
}
public static Object[] unpackKeyAndIV(byte[] data) {
byte[] keyD = new byte[16];
byte[] iv = new byte[data.length - 16];
return new Object[] {
new SecretKeySpec(data, 16, data.length - 16, "AES"),
new IvParameterSpec(data, 0, 16)
};
}
Per edits and comments. Error was a corrupted privKey being passed into the decrypt function. Above code works fine.
try adding the following under your constructor -
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

AES - simple encrypt in Java, decrypt with openssl

I am trying to do a simple AES encryption in Java, using Java Cryto, that can then be decrypted in ObjectiveC, using OpenSSL.
as I am not doing the ObjectiveC side, I want to make sure it works, using the openSSL command line, but I always get "bad magic number"
Here is my Java code
public class EncryptionUtils {
private static final String AES_CIPHER_METHOD = "AES";
private static final int AES_KEY_SIZE = 128;
public static byte[] generateAesKey() throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_CIPHER_METHOD);
keyGenerator.init(AES_KEY_SIZE);
SecretKey key = keyGenerator.generateKey();
return key.getEncoded();
}
public static SecretKeySpec createAesKeySpec(byte[] aesKey) {
return new SecretKeySpec(aesKey, AES_CIPHER_METHOD);
}
public static void aesEncryptFile(File in, File out, SecretKeySpec aesKeySpec) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IOException {
Cipher aesCipher = Cipher.getInstance(AES_CIPHER_METHOD);
aesCipher.init(Cipher.ENCRYPT_MODE, aesKeySpec);
InputStream inputStream = new FileInputStream(in);
try {
OutputStream outputStream = new CipherOutputStream(new FileOutputStream(out), aesCipher);
try {
IOUtils.copy(inputStream , outputStream);
} finally {
outputStream.close();
}
} finally {
inputStream.close();
}
}
}
//testcode
#Test
public void testAesEncryptFile() throws IOException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
byte[] aesKey = EncryptionUtils.generateAesKey();
SecretKeySpec aesKeySpec = EncryptionUtils.createAesKeySpec(aesKey);
EncryptionUtils.aesEncryptFile(new File("C:\\test\\test.txt"), new File("C:\\test\\test-encrypted.txt"), aesKeySpec);
FileOutputStream outputStream = new FileOutputStream("C:\\test\\aes.key");
outputStream.write(aesKey);
outputStream.close();
}
#Test
public void testAesDecryptFile() throws IOException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
FileInputStream keyFis = new FileInputStream("C:\\test\\aes.key");
ByteArrayOutputStream keyBaos = new ByteArrayOutputStream();
IOUtils.copy(keyFis, keyBaos);
SecretKeySpec keySpec = new SecretKeySpec(keyBaos.toByteArray(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
FileInputStream fileInputStream = new FileInputStream("C:\\test\\test-encrypted.txt");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copy(fileInputStream, baos);
byte[] decrypted = cipher.doFinal(baos.toByteArray());
FileOutputStream outputStream = new FileOutputStream("C:\\test\\test-decrypted.txt");
outputStream.write(decrypted);
outputStream.close();
}
Now that runs as expected, file "test-encrypted.txt" is indeed encrypted, and "test-decrypted.txt" == "test.txt"
I then tried to run a decryption on the command line using OpenSSL
openssl enc -d -aes-128-ecb -in test-encrypted.txt -k aes.key
however, this always give me
bad magic number
From what I can see, the using algorithm "AES" in Java uses "ECB" mode by default, so the above should work. What am I doing wrong.
The problem is indeed due to the key that is computed from the password by OpenSSL.
Most likely the reason is that OpenSSL has its own algorithm to derive a key, EVP_BytesToKey, from the password, and that is not the same as Java's.
The only solution I found was to use a Java reimplementation of that algorithm:
private static final int KEY_LENGTH = 32;
private byte[] deriveKey(String encryptionPassword, byte[] salt) throws NoSuchAlgorithmException {
final byte[] passAndSalt = ArrayUtils.addAll(encryptionPassword.getBytes(), salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3 && keyAndIv.length < KEY_LENGTH; i++) {
final byte[] dataToHash = ArrayUtils.addAll(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("SHA-256");
hash = md.digest(dataToHash);
keyAndIv = ArrayUtils.addAll(keyAndIv, hash);
}
return Arrays.copyOfRange(keyAndIv, 0, KEY_LENGTH);
}
ArrayUtils is part of Apache Commons library.
Full usage:
IvParameterSpec initializationVectorSpec = new IvParameterSpec(
Hex.decodeHex(encryptionInitializationVector.toCharArray()));
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] salt = new SecureRandom().generateSeed(8);
byte[] key = deriveKey(encryptionPassword, salt);
Key keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, initializationVectorSpec);
byte[] rawEncryptedInput = cipher.doFinal(input.getBytes());
byte[] encryptedInputWithPrependedSalt = ArrayUtils.addAll(ArrayUtils.addAll(
"Salted__".getBytes(), salt), rawEncryptedInput);
return Base64.getEncoder()
.encodeToString(encryptedInputWithPrependedSalt);
Credit to this answer for showing me the way.
The problem is with the key. The -k argument expects a passphrase, not a file. In turn, when a passphrase is used by the openssl encryption routine, a magic and salt is put in front of the encrypted result. That's the magic that cannot be found.
To use the openssl command line, print out the key in hex and use the -K option instead of the lowercase -k option.
You could also use:
`cat aes.key`
as argument after -K, given that aes.key contains the key in hexadecimals.

Categories

Resources