Android java.io.IOException: write failed: EBADF (Bad file number) - java

I have read several posts about the EBADF error that does not resolve my issue. What makes my case unique is that I am trying to write to the getFilesDir, that should be writable by the application.
I am getting the EBADF error when executing the encrypt method. The file argument is created using:
new File(mContext.getFilesDir(), "file.dat")
Where is a listing of the encrypt method:
public static void encrypt(File file, String password, List<Object> objects) {
byte[] salt = generateSalt();
byte[] iv = generateIV();
Cipher c = createCipher(password, salt, Cipher.ENCRYPT_MODE, iv);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(salt);
fos.write(iv);
try (CipherOutputStream cos = new CipherOutputStream(fos, c);
ObjectOutputStream oos = new ObjectOutputStream(cos)) {
for (Object o : objects) {
oos.writeObject(o);
}
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Can anyone see why I am getting the exception?
Here is the exception log:
Caused by: java.lang.RuntimeException: java.io.IOException: write failed: EBADF (Bad file number)
at za.co.lot24media.password.util.EncryptUtil.encrypt(EncryptUtil.java:69)
at za.co.lot24media.password.store.Store.save(Store.java:94)
at za.co.lot24media.password.store.Store.createSamples(Store.java:179)
at za.co.lot24media.password.store.Store.load(Store.java:76)
at za.co.lot24media.password.activity.login.LoginAction$2.doInBackground(LoginAction.java:62)
at za.co.lot24media.password.activity.login.LoginAction$2.doInBackground(LoginAction.java:55)
at android.os.AsyncTask$2.call(AsyncTask.java:292)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231) 
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
at java.lang.Thread.run(Thread.java:818) 
Caused by: java.io.IOException: write failed: EBADF (Bad file number)
at libcore.io.IoBridge.write(IoBridge.java:502)
at java.io.FileOutputStream.write(FileOutputStream.java:186)
at java.io.OutputStream.write(OutputStream.java:82)
at javax.crypto.CipherOutputStream.close(CipherOutputStream.java:129)
at za.co.lot24media.password.util.EncryptUtil.encrypt(EncryptUtil.java:64)
** EDITED **
The issue seems to be with the ObjectOutputStream writing to the CipherOutputStream. When I remove the ObjectOutputStream from the encrypt() method, the method succeeds. The code below works:
public static void encrypt(File file, String password, StoreDataRecord storeDataRecord) {
byte[] salt = generateSalt();
byte[] iv = generateIV();
Cipher c = createCipher(password, salt, Cipher.ENCRYPT_MODE, iv);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(salt);
fos.write(iv);
try (CipherOutputStream cos = new CipherOutputStream(fos, c)) {
cos.write(new byte[10]);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
I used byte[10] just to write arbitrary data to the stream.
** EDIT 2 **
The following solution also works, writing the data to a ByteArrayOutputStream first:
public static void encrypt(File file, String password, StoreDataRecord storeDataRecord) {
byte[] salt = generateSalt();
byte[] iv = generateIV();
Cipher c = createCipher(password, salt, Cipher.ENCRYPT_MODE, iv);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(salt);
fos.write(iv);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
CipherOutputStream cos = new CipherOutputStream(fos, c)) {
oos.writeObject(storeDataRecord.getVersion());
oos.writeObject(storeDataRecord.getItems());
cos.write(bos.toByteArray());
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

As i see your encrypt method call on doInBackground so it can produce some complicated situation like move to another fragment or create two instance of fragment and this make android confuse. Best way is you ignore encrypt method when onDestroy() called.
And make sure your streams not closed before you use them.
Hope it's helps

Related

CipherOutputStream causes IllegalBlockSizeException on receiving message

I'm writing server/client system that uses AES.
The following method (server side) works fine:
public static byte[] encode(byte type, byte subType, String string, Cipher cipher)
throws NullPointerException, IOException {
Objects.requireNonNull(string);
Objects.requireNonNull(cipher);
byte[] data = null;
try {
data = string.getBytes(StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e1) {};
ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream();
bytesOutput.write(type);
bytesOutput.write(subType);
bytesOutput.write(data);
byte[] plainBytes = bytesOutput.toByteArray();
byte[] bytes = null;
try {
bytes = cipher.doFinal(plainBytes);
} catch (Exception e) {
e.printStackTrace();
}
byte[] base64Bytes = Base64.getEncoder().encode(bytes);
return base64Bytes;
}
But when I replaced it with the following code I got IllegalBlockSizeException on client side.
public static byte[] encode(byte type, byte subType, String string, Cipher cipher)
throws NullPointerException, EncodingException {
Objects.requireNonNull(string);
Objects.requireNonNull(cipher);
byte[] data = null;
try {
data = string.getBytes(StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e1) {};
CipherOutputStream cipherOutput = null;
try {
ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream();
OutputStream base64Output = Base64.getEncoder().wrap(bytesOutput);
cipherOutput = new CipherOutputStream(base64Output, cipher);
cipherOutput.write(type);
cipherOutput.write(subType);
cipherOutput.write(data);
cipherOutput.flush();
byte[] bytes = bytesOutput.toByteArray();
return bytes;
} catch (IllegalStateException | IOException e) {
throw new EncodingException(e);
} finally {
if (cipherOutput != null)
try {
cipherOutput.close();
} catch (IOException e) {};
}
}
}
What is wrong with second method? Both methods use the same cipher. Nothing changed on server excluding the encode method.

Last block incomplete in decryption while decrypting a file

I have few AES encrypted videos which I am required to decrypt before playing with ExoPlayer. These videos are going to be included in assets folder of the app while few of them needs to be on SD Card.
I have been using a Utility Class provided to decrypt the videos, but it seems that it's not working properly.
static String key = "xxx"; // key should be exactly 16bit long
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES";
public static void encrypt(File inputFile, File outputFile) throws CryptoException {
doCrypto(Cipher.ENCRYPT_MODE, inputFile, outputFile);
}
public static void decrypt(File inputFile, File outputFile) throws CryptoException {
doCrypto(Cipher.DECRYPT_MODE, inputFile, outputFile);
}
private static void doCrypto(int cipherMode, File inputFile, File outputFile) throws CryptoException {
try {
Key secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(cipherMode, secretKey);
FileInputStream inputStream = new FileInputStream(inputFile);
byte[] inputBytes = new byte[(int) inputFile.length()];
inputStream.read(inputBytes);
byte[] outputBytes = cipher.doFinal(inputBytes);
FileOutputStream outputStream = new FileOutputStream(outputFile);
outputStream.write(outputBytes);
inputStream.close();
outputStream.close();
} catch (IOException | NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
throw new CryptoException("Error encrypting/decrypting file", e);
}
}
For the videos in assets folder, I tried to directly pass the InputStream returned from getAssets().open(filePath), however it gave some error related to padding. So I instead copied the video file to Internal Storage using the following code first
public static void copyFromAssets(Context context, String filePath, File outputFile) {
InputStream in = null;
OutputStream out = null;
try {
in = context.getAssets().open(filePath);
out = new FileOutputStream(outputFile);
copyFile(in, out);
} catch (IOException e) {
e.printStackTrace();
LumberJack.e("tag", "Failed to copy asset file: " + filePath);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// NOOP
}
}
}
}
private static void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
The video was there, but since it's an encrypted video I couldn't play it. when I tried to decrypt the extracted video file, I got the following exception -
javax.crypto.IllegalBlockSizeException: last block incomplete in decryption
at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:853)
at javax.crypto.Cipher.doFinal(Cipher.java:1502)
at com.example.utilities.CryptoUtils.doCrypto(CryptoUtils.java:42)
at com.example.utilities.CryptoUtils.decrypt(CryptoUtils.java:29)
at com.example.activities.HomeActivity.onVideoPlayButtonClick(HomeActivity.java:107)
at java.lang.reflect.Method.invoke(Native Method)
at org.greenrobot.eventbus.EventBus.invokeSubscriber(EventBus.java:485)
at org.greenrobot.eventbus.EventBus.postToSubscription(EventBus.java:420)
at org.greenrobot.eventbus.EventBus.postSingleEventForEventType(EventBus.java:397)
at org.greenrobot.eventbus.EventBus.postSingleEvent(EventBus.java:370)
at org.greenrobot.eventbus.EventBus.post(EventBus.java:251)
at com.example.viewmodels.BaseDataLevelItemView$1.onClick(BaseDataLevelItemView.java:65)
at android.view.View.performClick(View.java:5210)
at android.view.View$PerformClick.run(View.java:21169)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5451)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
I am not sure what am I doing wrong here. The same decryption code have been in use in the earlier version of the app with same videos, but giving issues now. I have checked with some other answers on Stackoverflow related to the same exception, but most of them was related to encoding issue in the cipher text. I don't have a string here, but a file.
How can I find out, what's wrong here and how can I correct it, if you've already found out the problem?
Thanks to the suggestions from Ebbe M. Pedersen, I actually tried to encrypt a file and tried decrypting with the same code which worked.
So, it was confirmed that there was some other algorithm or something else was used to encrypt the files. Luckily I found it in the old commits.

Encrypted save and decrypted load of an ArrayList of serializable objects

I save and load in sd card a file that contains an ArrayList of serializable object with these two methods
save method
public static void saveUserList(ArrayList<User> userList) {
if (storageAvailable()) {
try {
createFolder();
FileOutputStream userList = new FileOutputStream(
baseDir + File.separator + baseAppDir + File.separator
+ fileName);
ObjectOutputStream oos = new ObjectOutputStream(
userList);
oos.writeObject(userList);
oos.close();
} catch (Exception exc) {
exc.printStackTrace();
}
}
}
load method
public static ArrayList<User> loadUserList() {
if (storageAvailable()) {
ArrayList<User> userList = new ArrayList<User>();
try {
FileInputStream userList = new FileInputStream(baseDir
+ File.separator + baseAppDir + File.separator
+ fileName);
ObjectInputStream oos = new ObjectInputStream(
userList);
userList = (ArrayList<User>) oos.readObject();
oos.close();
} catch (Exception exc) {
exc.printStackTrace();
}
return userList;
} else {
return null;
}
}
Now I want that the method saveUserList encrypts the content of the file during the save according a specific String keyword and the method loadUserList decrypts the file with the same keyword to return the arrayList.
How could I do this?
I have given a look to CipherOutputStream but I haven't understood how should I use this.
The method proposed to use Conceal library
public static void saveUserListCrypted(ArrayList<User> userList) {
if (storageAvailable()) {
try {
createFolder();
Crypto crypto = new Crypto(
new SharedPrefsBackedKeyChain(context),
new SystemNativeCryptoLibrary());
FileOutputStream userList = new FileOutputStream(
baseDir + File.separator + baseAppDir + File.separator
+ fileName);
OutputStream cryptedStream = crypto.getCipherOutputStream(
userList, new Entity("UserList");
ObjectOutputStream oos = new ObjectOutputStream(
cryptedStream);
oos.writeObject(userList);
oos.close();
} catch (Exception exc) {
exc.printStackTrace();
}
}
}
cause this error
this error java.lang.UnsupportedOperationException 02-12 21:29:05.026 2051-2051/com.myapp W/System.err﹕ at com.facebook.crypto.streams.NativeGCMCipherOutputStream.write
Try (adding the appropriate checks and try blocks that I have omitted to make the code more readable) something like this to save
public static void AESObjectEncoder(Serializable object, String password, String path) {
try {
Cipher cipher = null;
cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, fromStringToAESkey(password));
SealedObject sealedObject = null;
sealedObject = new SealedObject(object, cipher);
CipherOutputStream cipherOutputStream = null;
cipherOutputStream = new CipherOutputStream(new BufferedOutputStream(new FileOutputStream(path)), cipher);
ObjectOutputStream outputStream = null;
outputStream = new ObjectOutputStream(cipherOutputStream);
outputStream.writeObject(sealedObject);
outputStream.close();
}
and this to load
public static Serializable AESObjectDedcoder(String password, String path) {
Cipher cipher = null;
Serializable userList = null;
cipher = Cipher.getInstance("AES/CBC/PKCS7Pdding");
//Code to write your object to file
cipher.init(Cipher.DECRYPT_MODE, fromStringToAESkey(password));
CipherInputStream cipherInputStream = null;
cipherInputStream = new CipherInputStream(new BufferedInputStream(new FileInputStream(path)), cipher);
ObjectInputStream inputStream = null;
inputStream = new ObjectInputStream(cipherInputStream);
SealedObject sealedObject = null;
sealedObject = (SealedObject) inputStream.readObject();
userList = (Serializable) sealedObject.getObject(ciper);
return userList;
}
to create a SecretKey from a String you can use this
public static SecretKey fromStringToAESkey(String s) {
//256bit key need 32 byte
byte[] rawKey = new byte[32];
// if you don't specify the encoding you might get weird results
byte[] keyBytes = new byte[0];
try {
keyBytes = s.getBytes("ASCII");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.arraycopy(keyBytes, 0, rawKey, 0, keyBytes.length);
SecretKey key = new SecretKeySpec(rawKey, "AES");
return key;
}
NOTE:
this code encrypts and decrypts twice to show the way of use of both sealed object and Cipher streams
I suggest taking a look at Conceal, recently released by facebook: http://facebook.github.io/conceal/
This should be a trivial modification to wrap a Conceal output stream with an ObjectOutputStream used in your current code:
public static void saveUserList(ArrayList<User> userList) {
if (storageAvailable()) {
try {
createFolder();
Crypto crypto = new Crypto(
new SharedPrefsBackedKeyChain(context),
new SystemNativeCryptoLibrary());
FileOutputStream userList = new FileOutputStream(
baseDir + File.separator + baseAppDir + File.separator
+ fileName);
OutputStream cryptedStream = crypto.getCipherOutputStream(
userList, new Entity("UserList");
ObjectOutputStream oos = new ObjectOutputStream(
cryptedStream);
oos.writeObject(userList);
oos.close();
} catch (Exception exc) {
exc.printStackTrace();
}
}
}
I'll leave the restore as an exercise for the reader. ;)
You could simply use AES Encoding:
private static byte[] getEncrypt(final String key, final String message) throws GeneralSecurityException {
final byte[] rawData = key.getBytes(Charset.forName("US-ASCII"));
if (rawData.length != 16) {
// If this is not 16 in length, there's a problem with the key size, nothing to do here
throw new IllegalArgumentException("You've provided an invalid key size");
}
final SecretKeySpec seckeySpec = new SecretKeySpec(rawData, "AES");
final Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.ENCRYPT_MODE, seckeySpec, new IvParameterSpec(new byte[16]));
return ciph.doFinal(message.getBytes(Charset.forName("US-ASCII")));
}
private static String getDecrypt(String key, byte[] encrypted) throws GeneralSecurityException {
final byte[] rawData = key.getBytes(Charset.forName("US-ASCII"));
if (rawData.length != 16) {
// If this is not 16 in length, there's a problem with the key size, nothing to do here
throw new IllegalArgumentException("Invalid key size.");
}
final SecretKeySpec seckeySpec = new SecretKeySpec(rawData, "AES");
final Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.DECRYPT_MODE, seckeySpec, new IvParameterSpec(new byte[16]));
final byte[] decryptedmess = ciph.doFinal(encrypted);
return new String(decryptedmess, Charset.forName("US-ASCII"));
}
Then, for trying it, this example may be valid for you:
final String encrypt = "My16inLengthKey5";
final byte[] docrypt = getEncrypt(encrypt, "This is a phrase to be encrypted!");
final String douncrypt = getDecrypt(encrypt.toString(), docrypt);
Log.d("Decryption", "Decrypted phrase: " + douncrypt);
Of course, douncrypt must match This is a phrase to be encrypted!

Double layer encryption: "Input length must be multiple of 16 when decrypting with padded cipher..."

I have an application consisting of three services: Client, Server and TokenService. In order to access data on the Server, Client has to obtain SecurityToken object from TokenService. The communication between parties is encrypted using shared keys (Client and TokenService share a key 'A' and TokenService and Server share a different key 'B'). When Client sends request to TokenService then the communication is encrypted with 'A'. When TokenService returns SecurityToken object, this object is encrypted with B and A like this: ((SecurityToken)B)A). This doubly encrypted object first goes back to Client, Client decrypts it with A, puts it into another object, attaches some additional information (String with request) and sends it to the Server where SecurityToken gets decrypted with B.
Everything works fine until I'am decrypting SecurityToken object on the server side. I get Exception:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:749)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:675)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at mds.hm5.sharedclasses.Decryptor.decryptData(Decryptor.java:40)
at mds.hm5.tokenservice.Main2.main(Main2.java:28)
I was able to recreate this error (without remote communication between parties) like this:
public static void main(String[] args) {
SecurityToken s = new SecurityToken(false, "2");
try {
byte[] bytes = Encryptor.getBytesFromObject(s);
bytes = Encryptor.encryptData(bytes, "secretkey1");
bytes = Encryptor.encryptData(bytes, "secretkey2");
bytes = Base64.encodeBase64(bytes);
System.out.println(bytes);
bytes = Base64.decodeBase64(bytes);
bytes = Decryptor.decryptData(bytes, "secretkey2");
bytes = Decryptor.decryptData(bytes, "secretkey1");
SecurityToken s2 = (SecurityToken) Decryptor.getObjectFromBytes(bytes);
System.out.println(s2.getRole());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
I have no idea what I'am doing wrong here. Is it impossible to create two layers of encryption just like that? Am I missing something?
Additional information:
Here is my Encryptor class:
public class Encryptor {
public static byte[] encryptData(byte[] credentials, String key){
Cipher c;
SecretKeySpec k;
byte[] byteCredentials = null;
byte[] encryptedCredentials = null;
byte[] byteSharedKey = null;
try {
byteCredentials = getBytesFromObject(credentials);
byteSharedKey = getByteKey(key);
c = Cipher.getInstance("AES");
k = new SecretKeySpec(byteSharedKey, "AES");
c.init(Cipher.ENCRYPT_MODE, k);
encryptedCredentials = c.doFinal(byteCredentials);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return encryptedCredentials;
}
public static byte[] getBytesFromObject(Object credentials) throws IOException{
//Hmmm.... now I'm thinking I should make generic type for both: Token and ITU_Credentials object, that would have this getBytes and getObject methods.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null;
byte[] newBytes = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(credentials);
newBytes = bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
out.close();
bos.close();
}
return newBytes;
}
private static byte[] getByteKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException{
//Converting key to SHA-1 and trimming to mach maximum lenght of key
byte[] bkey = key.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
bkey = sha.digest(bkey);
bkey = Arrays.copyOf(bkey, 16);
return bkey;
}
And here is my Decryptor class:
public class Decryptor {
public static byte[] decryptData(byte[] encryptedCredentials, String key){
Cipher c;
SecretKeySpec k;
byte[] byteSharedKey = null;
byte[] byteObject = null;
try {
byteSharedKey = getByteKey(key);
c = Cipher.getInstance("AES");
k = new SecretKeySpec(byteSharedKey, "AES");
c.init(Cipher.DECRYPT_MODE, k);
byteObject = c.doFinal(encryptedCredentials);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return byteObject;
}
public static Object getObjectFromBytes(byte[] credentials) throws IOException, ClassNotFoundException{
ByteArrayInputStream bis = new ByteArrayInputStream(credentials);
ObjectInput in = null;
ITU_Credentials credentialsObj = null;
try {
in = new ObjectInputStream(bis);
credentialsObj = (ITU_Credentials)in.readObject();
} finally {
bis.close();
in.close();
}
return credentialsObj;
}
private static byte[] getByteKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException{
//Converting key to SHA-1 and trimming to mach maximum lenght of key
byte[] bkey = key.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
bkey = sha.digest(bkey);
bkey = Arrays.copyOf(bkey, 16);
return bkey;
}
public static void main(String[] args) {
new Encryptor();
}
}
EDIT:
As advised, I replaced all e.printStackTrace(); with throw new RuntimeException(e); in the Decriptor class to properly throw exceptions:
public class Decryptor {
public static byte[] decryptData(byte[] encryptedCredentials, String key){
Cipher c;
SecretKeySpec k;
byte[] byteSharedKey = null;
byte[] byteObject = null;
try {
byteSharedKey = getByteKey(key);
c = Cipher.getInstance("AES");
k = new SecretKeySpec(byteSharedKey, "AES");
c.init(Cipher.DECRYPT_MODE, k);
byteObject = c.doFinal(encryptedCredentials);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (IllegalBlockSizeException e) {
throw new RuntimeException(e);
} catch (BadPaddingException e) {
throw new RuntimeException(e);
}
return byteObject;
}
public static Object getObjectFromBytes(byte[] credentials) throws IOException, ClassNotFoundException{
ByteArrayInputStream bis = new ByteArrayInputStream(credentials);
ObjectInput in = null;
ITU_Credentials credentialsObj = null;
try {
in = new ObjectInputStream(bis);
credentialsObj = (ITU_Credentials)in.readObject();
} finally {
bis.close();
in.close();
}
return credentialsObj;
}
private static byte[] getByteKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException{
//Converting key to SHA-1 and trimming to mach maximum lenght of key
byte[] bkey = key.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
bkey = sha.digest(bkey);
bkey = Arrays.copyOf(bkey, 16);
return bkey;
}
public static void main(String[] args) {
new Encryptor();
}
}
Now the exception looks as follows:
Exception in thread "main" java.lang.RuntimeException: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at mds.hm5.sharedclasses.Decryptor.decryptData(Decryptor.java:51)
at mds.hm5.tokenservice.Main2.main(Main2.java:28)
Caused by: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:749)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:675)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at mds.hm5.sharedclasses.Decryptor.decryptData(Decryptor.java:40)
... 1 more
I think the root of your problem is:
byte[] bytes = Encryptor.getBytesFromObject(s);
bytes = Encryptor.encryptData(bytes, "secretkey1");
which goes to:
//etc.//
byte[] encryptedCredentials = null;
byte[] byteSharedKey = null;
try {
byteCredentials = getBytesFromObject(credentials);
//Whoops! credentials is already a byte array.
//etc.//
catch (and eat) exception.....
return encryptedCredentials;
And, since you eat the exception and just return null, as home has advised against in the comments, then it keeps moving until it gets to the decryption step, where it throws an exception you hadn't anticipated (when it fails to decrypt, an IllegalBlockSizeException which is none of the eight types of Exception that you catch there), and gives you something useful.
That's what I think is going on anyway.

last block incomplete with CipherInputStream/CipherOutputStream, even with padding AES/CBC/PKCS5Padding

Actually, I searched lot from internet and in stackoverflow too for this,
Initially I don't used padding in my encryption and decryption,
But Finally I got solution from here
https://stackoverflow.com/a/10775577/1115788
and I updated my code with padding as AES/CBC/PKCS5Padding
and the same error is coming, and last block is not decrypted...
I'm working on this for last two day, but no solution found
my Crypter Code:
package mani.droid.browsedropbox;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Crypter {
Cipher encipher;
Cipher decipher;
CipherInputStream cis;
CipherOutputStream cos;
FileInputStream fis;
byte[] ivbytes = new byte[]{(byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o', (byte)'p'};
IvParameterSpec iv = new IvParameterSpec(ivbytes);
public boolean enCrypt(String key, InputStream is, OutputStream os)
{
try {
byte[] encoded = new BigInteger(key, 16).toByteArray();
SecretKey seckey = new SecretKeySpec(encoded, "AES");
encipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
encipher.init(Cipher.ENCRYPT_MODE, seckey, iv);
cis = new CipherInputStream(is, encipher);
copyByte(cis, os);
return true;
}
catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
public boolean deCrypt(String key, InputStream is, OutputStream os)
{
try {
byte[] encoded = new BigInteger(key, 16).toByteArray();
SecretKey seckey = new SecretKeySpec(encoded, "AES");
encipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
encipher.init(Cipher.DECRYPT_MODE, seckey, iv);
cos = new CipherOutputStream(os, encipher);
copyByte(is, cos);
//cos.close();
return true;
}
catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
public void copyByte(InputStream is, OutputStream os) throws IOException
{
byte[] buf = new byte[8192];
int numbytes;
while((numbytes = is.read(buf)) != -1)
{
os.write(buf, 0, numbytes);
os.flush();
}
os.close();
is.close();
}
}
I've had exactly the same problem.
The accepted solution works because you've used cipher mode that does not require padding, but this is not a way crypto-related issues are fixed.
According to the CipherOutputStream documentation, you have to call close() method in order to finalize encryption properly (i.e., padding block is added).
This method invokes the doFinal method of the encapsulated cipher
object, which causes any bytes buffered by the encapsulated cipher to
be processed. The result is written out by calling the flush method of
this output stream.
This method resets the encapsulated cipher object to its initial state
and calls the close method of the underlying output stream.
If you want to preserve OutputStream open even after calling CipherOutputStream.close() method, you can wrap OutputStream to the stream that does not close it. For example:
public class NotClosingOutputStream extends OutputStream {
private final OutputStream os;
public NotClosingOutputStream(OutputStream os) {
this.os = os;
}
#Override
public void write(int b) throws IOException {
os.write(b);
}
#Override
public void close() throws IOException {
// not closing the stream.
}
#Override
public void flush() throws IOException {
os.flush();
}
#Override
public void write(byte[] buffer, int offset, int count) throws IOException {
os.write(buffer, offset, count);
}
#Override
public void write(byte[] buffer) throws IOException {
os.write(buffer);
}
}
Then you can use:
...
cos = new CipherOutputStream(new NotClosingOutputStream(os), encipher);
copyByte(is, cos);
cos.close();
...
Note the os stream does not get closed, you need to do it on your own when appropriate.
Finally I got answer for my own question, with trial and error
Actually here Conflict is I set Padding in encipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
and Set IV with some values.....,
Finally I got Answer only just replaced the Algorithm
From:
AES/CBC/PKCS7Paddinng
To:
AES/CFB8/NoPadding
and its worked like charm...., So I suggest this answer for others who struggling with this problem, if you solved you problem, mention here for others...
I've seen CipherInputStream fail with padding problems too. This behaviour varied across different versions of the JVM. Eg 7u55 32 bit my code worked fine, 7u55 64 bit same code failed... and I also saw failures on later 32 bit JVMs. Workaround was to use byte array methods and avoid CipherInputStream.
Not sure if this is relevant to OP's problem, but this may help someone.
When you repeatedly get that java.io.IOException: last block incomplete in decryption regardless of what you change, check if you are still using the file from some previous run. If your read/write test code appends to that file, you will always get that exception -- unless you delete the corrupt file that you write to.
Using CipherInputStream with padding is possible, switching to NoPadding is a workaround but not a solution.
Padding is applied when CipherInputStream reaches the end of the stream. The important point is that you have to call the read() method of the CipherInputStream at least twice for getting all the data.
The following example demonstrates reading a CipherInputStream with padding:
public static void test() throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecureRandom rnd = new SecureRandom();
byte[] keyData = new byte[16];
byte[] iv = new byte[16];
rnd.nextBytes(keyData);
rnd.nextBytes(iv);
SecretKeySpec key = new SecretKeySpec(keyData, "AES");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
CipherOutputStream out = new CipherOutputStream(buffer, cipher);
byte[] plain = "Test1234567890_ABCDEFG".getBytes();
out.write(plain);
out.flush();
out.close();
byte[] encrypted = buffer.toByteArray();
System.out.println("Plaintext length: " + plain.length);
System.out.println("Padding length : " + (cipher.getBlockSize() - (plain.length % cipher.getBlockSize())));
System.out.println("Cipher length : " + encrypted.length);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
CipherInputStream in = new CipherInputStream(new ByteArrayInputStream(encrypted), cipher);
buffer = new ByteArrayOutputStream();
byte[] b = new byte[100];
int read;
while ((read = in.read(b)) >= 0) {
buffer.write(b, 0, read);
}
in.close();
// prints Test1234567890_ABCDEFG
System.out.println(new String(buffer.toByteArray()));
}
For those who are struggling with aes encryption / decryption with image file, here is my example, and it works like a charm.
public static String decrypt(String textToDecrypt) {
byte[] dataDecrypted = null;
try {
Cipher cipher = getCipher();
SecretKey key = getKey();
IvParameterSpec iv = getIV();
if(cipher == null) {
return null;
}
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] dataToDecrypt = Base64.decode(textToDecrypt, Base64.NO_WRAP);
ByteArrayInputStream bais = new ByteArrayInputStream(dataToDecrypt);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Wrap the output stream
CipherOutputStream cos = new CipherOutputStream(baos, cipher);
// Read bytes
int count = 0;
byte[] buffer = new byte[DEFAULT_BYTE_READ_WRITE_BLOCK_BUFFER_SIZE];
while ((count = bais.read(buffer)) != -1) {
cos.write(buffer, 0, count);
}
cos.close(); // manually do close for the last bit
dataDecrypted = baos.toByteArray();
// Close streams.
baos.close();
bais.close();
} catch (Exception e) {
e.printStackTrace();
}
return (dataDecrypted == null ? "" : Base64.encodeToString(dataDecrypted, Base64.NO_WRAP));
}
public static String encrypt(String textToEncrypt) {
byte[] dataEncrypted = null;
try {
Cipher cipher = getCipher();
SecretKey key = getKey();
IvParameterSpec iv = getIV();
if (cipher == null) {
return null;
}
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] dataToEncrypt = Base64.decode(textToEncrypt, Base64.NO_WRAP);
ByteArrayInputStream bais = new ByteArrayInputStream(dataToEncrypt);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Wrap the output stream
CipherOutputStream cos = new CipherOutputStream(baos, cipher);
// Read bytes
int count = 0;
byte[] buffer = new byte[DEFAULT_BYTE_READ_WRITE_BLOCK_BUFFER_SIZE];
while ((count = bais.read(buffer)) != -1) {
cos.write(buffer, 0, count);
}
cos.close(); // manually do close for the last bit
dataEncrypted = baos.toByteArray();
// Close streams.
baos.close();
bais.close();
} catch (Exception e) {
e.printStackTrace();
}
return (dataEncrypted == null ? "" : Base64.encodeToString(dataEncrypted, Base64.NO_WRAP));
}

Categories