Out of memory error with Cipher.dofinal() - android

I am developing an Android application for which I am decrypting a large file using Cipher Class of Android.
Code:
private byte[] decrypt_chunk(byte[] data, ByteString chunk_encryption_key) {
SecretKeySpec skeySpec = new SecretKeySpec(chunk_encryption_key.toByteArray(), 1, 16, "AES");
Cipher cipher;
byte[] decrypted = new byte[0];
try {
cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(getIV(0)));
decrypted = cipher.doFinal(data);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (ShortBufferException e) {
e.printStackTrace();
}
return decrypted;
}
I am getting "OutOfMemory" error while decrypting large files.
I have following questions:
What chanegs in the current code can fix the OOM error?
Will Cipher.update() can help to fix the issue? If yes, how to implement it?
Thank you.

Using the Cipher.update() method is one possible way.
However in your case I recommend to you to use a CipherOutputStream instead. I assume you have an InputStream for retrieving the data from the server and an FileOutputStream for saving the data into a file.
Using the CipherOutputStream is pretty simple, just "wrap-it" around the FileOutputStream:
FileOutputStream fout = ...
CipherOutputStream cout = new CipherOutputStream(fout, cipher);
Now continue to use cout instead of fout and everything you write into it will be automatically encrypted.

Related

how decrypt with decipher.doFinal(file_encrypt)

I doing encryption and decryption of document. Initially i read the file in byte array and passed that byte array to my encrypeted method. Upto 50MB size , i am able to encrypt the file without any issue. But if i increase my file SIZE to 80 MB , it is faling in cipher.doFinal() saying out of memoryException.
So how to encrypt bigger file without any issues? and also is doFinal() method have any size limitation. Please let me know.
and this is my code:
public String decrypt(byte[] file_encrypt) throws Exception {
String key22 = myKey;
byte[] b = key22.getBytes();
final SecretKey key = new SecretKeySpec(b, "DESede");
final IvParameterSpec iv = new IvParameterSpec(new byte[8]);
final Cipher decipher = Cipher.getInstance("DESede/CBC/NoPadding");
decipher.init(Cipher.DECRYPT_MODE, key, iv);
final byte[] plainText = decipher.doFinal(file_encrypt);
try {
String dir = Environment.getExternalStorageDirectory() + File.separator + ".android";
String dir2 = Environment.getExternalStorageDirectory() + File.separator + ".android/.androidmain";
File folder = new File(dir); //folder name
File folder2 = new File(dir2); //folder name
if (!folder.exists())
folder.mkdirs();
if (!folder2.exists())
folder2.mkdirs();
File file = new File(Environment.getExternalStorageDirectory() + File.separator + ".android/.androidmain/file");
if (file.exists()) {
// Toast.makeText(contInst, "111", Toast.LENGTH_SHORT).show();
} else {
// Toast.makeText(contInst, "3333", Toast.LENGTH_SHORT).show();
file.createNewFile();
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
bos.write(plainText);
bos.flush();
bos.close();
videoplay.setSource(Uri.fromFile(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return "ok";
}
When encrypting or decrypting, both the plain text and the encrypted text should be in the memory(RAM) at least one time.
But Java allow you to use limited memory. If you want to go from 50MB to 80MB, maybe using android:largeHeap=true in the application of your manifest will work. But if you want even a higher limit, you may want to perform the encryption by splitting up the file and performing encryption piece by piece.

How can I make pdf file readable only by my app?

Is it possible to download a PDF from server and save in format that only my application can read?
This is my working example used in my app ....hope this help you
Is it possible to download pdf from server and save
Yes i have use retrofit library to download pdf file from server you can use Volly or Loopj AsyncTask as well
After downloading pdf file you will get InputeStream object of file than encrypt that and store in app private folder (so no other application can able to use it)
public static File encryptAndSaveFileInPrivateFolder(
Context context, String albumName, InputStream inputStream, String fullFileName) {
File file = null;
try {
// Get the directory for the app's private pictures directory.
File fileDirectory = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES),""+albumName);
if (!fileDirectory.exists()) {
fileDirectory.mkdirs();
}
file = new File(fileDirectory,""+fullFileName);
if (file.exists()) {
file.delete();
}
FileOutputStream output = new FileOutputStream(file);
encrypt(inputStream,output);
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
This method encrypt your file
public static void encrypt(InputStream fis,FileOutputStream fos ) throws IOException, NoSuchAlgorithmException
, NoSuchPaddingException, InvalidKeyException {
// Here you read the cleartext.
//FileInputStream fis = new FileInputStream("data/cleartext");
// This stream write the encrypted text. This stream will be wrapped by another stream.
// FileOutputStream fos = new FileOutputStream("data/encrypted");
String password ="passwordProtectd";
// Length is 16 byte
byte[] inputByte = password.getBytes("UTF-8");
SecretKeySpec sks = new SecretKeySpec(inputByte, "AES");
// SecretKeySpec sks = new SecretKeySpec(password.getBytes(), "AES");
// Create cipher
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks);
// Wrap the output stream
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
// Write bytes
int b;
byte[] d = new byte[8];
while((b = fis.read(d)) != -1) {
cos.write(d, 0, b);
}
// Flush and close streams.
cos.flush();
cos.close();
fis.close();
}
Than show pdf using intent
public void decryptFileAndShow(Context context,File mFile) {
try{
if (null != mFile) {
String parentPath = mFile.getAbsoluteFile().getParent();//Actual encrypted file path
File tempFile = new File(parentPath, "report.pdf"); //Created new file that decrypted format after view we will delete this
//tempFile =File.createTempFile("prefix","TestMyPDF.pdf", context.getExternalFilesDir(""));
Utilities.decrypt( new FileInputStream(mFile), new FileOutputStream(tempFile));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(tempFile), "application/pdf");
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
context.startActivity(intent);
tempFile.deleteOnExit();
}
}catch (Exception e){
e.printStackTrace();
}
}
Last the method used for decryption
public static void decrypt(FileInputStream fis,FileOutputStream fos ) throws IOException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException {
// FileInputStream fis = new FileInputStream("data/encrypted");
// FileOutputStream fos = new FileOutputStream("data/decrypted");
String password ="passwordProtectd";
byte[] inputByte = password.getBytes("UTF-8");
SecretKeySpec sks = new SecretKeySpec(inputByte, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks);
CipherInputStream cis = new CipherInputStream(fis, cipher);
int b;
byte[] d = new byte[8];
while((b = cis.read(d)) != -1) {
fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();
}
Is it possible to download pdf from server.
Yes, it is possible. Download your pdf and encrypt and decrypt your file accordingly.
You can try like this using CipherOuputStream and CipherInputStream:
byte[] buf = new byte[1024];
Encryption:
public void encrypt(InputStream in, OutputStream out) {
try {
// Bytes written to out will be encrypted
out = new CipherOutputStream(out, ecipher);
// Read in the cleartext bytes and write to out to encrypt
int numRead = 0;
while ((numRead = in.read(buf)) >= 0) {
out.write(buf, 0, numRead);
}
out.close();
} catch (java.io.IOException e) {
}
}
Decryption:
public void decrypt(InputStream in, OutputStream out) {
try {
// Bytes read from in will be decrypted
in = new CipherInputStream(in, dcipher);
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = in.read(buf)) >= 0) {
out.write(buf, 0, numRead);
}
out.close();
} catch (java.io.IOException e) {
}
}
If I understand your question correctly, you want your application to be the only application that reads this particular PDF files. You want to know how you can ensure that you do that.
Since your requirement is not genuine(hacky), the solution also has to be a bit hacky.
You can download the file and store it with your own custom extension (eg. file.mypdf)
Have an intent filter, that supports mimetypes matching .mypdf files
You could have the server encrypt the download and your client side app decrypt it using a public/private key pair. This would prevent anyone with a snooping proxy from observing and saving your content, but it wouldn't deter the most determined users from stealing these documents as they would eventually exist in decrypted form somewhere. To achieve that you'd probably ave to create your own PDF viewer and damage the bytes of the PDF in a way that your viewer can do, but no other viewer can recover

Fast file encryption in android

Hi i am creating a sample to lock file or folder in android. I have a issue when i encrypt or decrypt large size (more than 1 GB) file it takes too much time.
Please help me to encrypt and decrypt file fastly.
Here i am attaching code which i am using on below
if (!isEncrypted) {
FileInputStream fis = new FileInputStream(path);
// This stream write the encrypted text. This stream will be wrapped by another stream.
FileOutputStream fos = new FileOutputStream(path + ".abcd");
// Length is 16 byte
SecretKeySpec sks = new SecretKeySpec("abcdefghijklmnop".getBytes(), "AES");
// Create cipher
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks);
// Wrap the output stream
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
// Write bytes
int b;
byte[] d = new byte[1024];
try {
while ((b = fis.read(d)) != -1) {
cos.write(d, 0, b);
}
// Flush and close streams.
cos.flush();
cos.close();
fis.close();
new File(path).deleteOnExit();
isEncrypted = true;
runOnUiThread(new Runnable() {
#Override
public void run() {
btnEncrypt.setText("Decrypt Path");
deleteMyFile(path);
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
} else {
FileInputStream fis = new FileInputStream(path + ".abcd");
FileOutputStream fos = new FileOutputStream(path);
SecretKeySpec sks = new SecretKeySpec("abcdefghijklmnop".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks);
CipherInputStream cis = new CipherInputStream(fis, cipher);
int b;
byte[] d = new byte[1024];
while ((b = cis.read(d)) != -1) {
fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();
isEncrypted = false;
runOnUiThread(new Runnable() {
#Override
public void run() {
deleteMyFile(path + ".abcd");
btnEncrypt.setText("Encrypt Path");
}
});
}
} catch (final Exception e) {
runOnUiThread(new Runnable() {
#Override
public void run() {
progressDialog.setMessage(e.getMessage());
}
});
I hope you found a way to do this. But, here is how I do it.
I realized that writing our own AES classes in java Or C is not recommendable way if you are especially just focused on the end result. Java is pretty slow in comparison with C, but in java you can use multithreading whereas in C you can't. Of course there are aes c programs which do a better job. But using them is difficult. You said you want to encrypt 1GB file under a minute. So, do this.
Install Userland Or Termux and install openssl. Next cd to your file storage location and try aes encrypt command. To automate it, write a bash script and run it. Openssl can easily do 23 MBps to 68 MBps.

How to write a file to external storage?

I tried to encrypt and save a file to the same location in the external storage with a different file name. But the way I have used is seems like wrong. Please help someone.
public static void encrypt(SecretKey secretKey, String filePath, IvParameterSpec iv){
try {
String file = "";
// Here you read the cleartext.
FileInputStream fis = new FileInputStream(filePath);
// This stream write the encrypted text. This stream will be wrapped by another stream.
//String filePath2 = filePath+"enc";
file = filePath.substring(0,filePath.length()-5)+"enc.jpeg";
FileOutputStream fos = new FileOutputStream(file);
Log.i(TAG, "Uri = "+file);
// Create cipher
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// Wrap the output stream
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
// Write bytes
int b;
byte[] d = new byte[8];
while ((b = fis.read(d)) != -1) {
cos.write(d, 0, b);
}
// Flush and close streams.
cos.flush();
cos.close();
fis.close();
}catch(IOException e){
e.printStackTrace();
}catch (NoSuchAlgorithmException e){
e.printStackTrace();
}catch(NoSuchPaddingException e){
e.printStackTrace();
}catch(InvalidKeyException e){
e.printStackTrace();
}/*catch (InvalidAlgorithmParameterException e){
e.printStackTrace();
}*/
}
Manifest file containing both read and write permissions.
You specify "AES/ECB/NoPadding".
With ECB there is no iv and thus no need to supply one on calling the encrypt method. ECB mode is insecure, see ECB mode, scroll down to the Penguin.
AES is a block cipher and as such it encrypts block sized portions at a time thus the input needs to be a multiple of the block size. Padding accomplished this transparently but you have specified "NoPadding" so the input file size ,just be an exact multiple of the block size, for AES that is 16-bytes. Instead use PKCS#7 (some tined referred to as PKCS#5) padding.
The simplest solution is to use a library that puts all the elements of secure encryption together including password derivation, a random iv, padding, encryption authentication. Consider RNCryptor, it provides all of this plus versioning. See RNCryptor README and RNCryptor-Spec for more information.

Android Video Encryption and playback

I am building a app form where users can download a video but can not play using inbuild video player.
Only from within the app user can play it.
I tried the following:
Downloaded the video using android downloadManager to download folder.
then I am encrypting the video using android cipher and algo AES.
then I am deleting the origin downloaded video.
I have implemented videoview,to play video.
from app I am de-cipering the video and then plugging it into video view source.
but it's not working.
All my videos are in .mp4 format.
Here is the code for ciphering and de-cyphering video(taken on another stackover answer, but don't have link anymore)
try{
FileInputStream fis = new FileInputStream(new File(c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME))));
File outfile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/appvideo/1.mp4");
int read;
if(!outfile.exists())
outfile.createNewFile();
File decfile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/appvideo/2.mp4");
if(!decfile.exists())
decfile.createNewFile();
FileOutputStream fos = new FileOutputStream(outfile);
FileInputStream encfis = new FileInputStream(outfile);
FileOutputStream decfos = new FileOutputStream(decfile);
Cipher encipher = Cipher.getInstance("AES");
Cipher decipher = Cipher.getInstance("AES");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecretKey skey = kgen.generateKey();
encipher.init(Cipher.ENCRYPT_MODE, skey);
CipherInputStream cis = new CipherInputStream(fis, encipher);
decipher.init(Cipher.DECRYPT_MODE, skey);
CipherOutputStream cos = new CipherOutputStream(decfos,decipher);
while((read = cis.read())!=-1)
{
fos.write((char)read);
fos.flush();
}
fos.close();
while((read=encfis.read())!=-1)
{
cos.write(read);
cos.flush();
}
cos.close();
}catch (Exception e) {
// TODO: handle exceptione
e.printStackTrace();
}
and my videoplay back code is
try {
//set the media controller in the VideoView
videoView.setMediaController(mediaControls);
mediaControls.setAnchorView(videoView);
//set the uri of the video to be played
videoView.setVideoURI(Uri.fromFile(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/appvVideo/3.mp4")));
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
Can anyone tell me what am i doing wrong.
what is good way for
download and encrypt the video and later decrypt form app that can be play only with app?

Categories

Resources