codenameone

How to Encrypt and Decrypt Files in the App Storage


To secure app storage, I encrypted the storage using EncryptedStorage as explained here. That is,

EncryptedStorage.install("your-pass-encryption-key")

I created a text file to be shared and opened/used by the same app in another device.

The problem is that this file can't be open/used in another device. It says that this file is encrypted.

How can I decrypt it to be able to open or use it in the same app in another device?

EDIT

I did the following to create the file to share as a .txt file so as to be able to share it using plain/text mime type.

String databasePath = Display.getInstance().getDatabasePath("TestDB.db");
String mimeType = "application/vnd.sqlite3";
mimeType = "application/x-sqlite3";
mimeType = "plain/text";

try {
    FileSystemStorage fss = FileSystemStorage.getInstance();
    InputStream is = fss.openInputStream(databasePath);

    //Copy from FSS to Storage
    Util.copy(is, Storage.getInstance().createOutputStream("TestDB.txt"));
    String fileToSharePath = FileSystemStorage.getInstance().getAppHomePath() + "TestDB.txt";
    Log.p("FileToSharePath " + fileToSharePath);
    Display.getInstance().share(fileToSharePath, null, mimeType);
} catch (IOException e) {
    Log.p("Share error: " + e);
}

I did the following to browse the shared file using FileChooser in another device and save it as .db file in the app database directory.

FileChooser.showOpenDialog("txt", ev -> {

    if (ev == null) {
        Log.p("no file selected");
    } else {
        String filePath = (String) ev.getSource();
        Log.p("FilePath " + filePath);
        try {
            FileSystemStorage fss = FileSystemStorage.getInstance();
            InputStream is = fss.openInputStream(filePath);

            String databasePath = Display.getInstance().getDatabasePath("TestDB.db");
            OutputStream os = FileSystemStorage.getInstance().openOutputStream(databasePath);
            Util.copy(is, os);
        } catch (IOException e) {
                Log.p("Create DB error: " + e);
        }
    }
});

Solution

  • The following creates a encrypted SampleText.txt to app storage which can't be read when open in text editor. It can only be read by the app.

       EncryptedStorage.install("your-pass-encryption-key")
                    
       private void writeAndReadCipherFile() {
        try {    
            //write encrypted file to the app storage
            try (Writer w = new OutputStreamWriter(Storage.getInstance().createOutputStream("SampleText.txt"))) {
                w.write("Hey there big brother");
            } catch (Exception e) {
                Log.p("Error " + e);
            }
    
            //read encrypted file from the app storage
            InputStream is = Storage.getInstance().createInputStream("SampleText.txt");
            int nextChar = is.read();
          
            StringBuilder sb = new StringBuilder();
            while (nextChar > -1) {
                sb.append((char) nextChar);
                nextChar = is.read();
            }
            
            Log.p("FileContent " + sb.toString());
        } catch (Exception e) {
            Log.p("Error " + e);
        }
    }
    

    The following reads and encrypt database file content using BouncyCastle and save encrypted database file in the app home path directory home/.cn1 It also read encrypted file content, decrypts and save the decrypted file in home/.cn1.

    ISO-8859-1 encoding is required in OutputStreamWriter to ensure all characters in the file bytes are read as expected. Windows-1252 encoding also tries but some hexadecimal are read as ?.

    private void encryptDatabaseFile() {
        try {
            String filePath = Display.getInstance().getDatabasePath("TestDB.db");
            FileSystemStorage fss = FileSystemStorage.getInstance();
            InputStream is = fss.openInputStream(filePath);
            byte[] plainBytes = Util.readInputStream(is);
    
            //encrypt database file content
            byte[] cipherByteArray = FilesCipher.encryptFile(plainBytes);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cipherByteArray);
    
            String encryptedFilePath = fss.getAppHomePath() + "EncryptedDB.db";
            try (Writer w = new OutputStreamWriter(fss.openOutputStream(encryptedFilePath), "ISO-8859-1")) {//ISO-8859-1 Windows-1252 UTF-8 
                int nextChar = byteArrayInputStream.read();
                while (nextChar > -1) {
                    char[] charArray = {(char) nextChar};
                    w.write(charArray);
                    nextChar = byteArrayInputStream.read();
                }
            } catch (Exception e) {
                Log.p("Error " + e);
            }
    
            //decrypt database file content
            InputStream decryptInputStream = fss.openInputStream(encryptedFilePath);
            byte[] cipherBytes = Util.readInputStream(decryptInputStream);
            byte[] plainByteArray = FilesCipher.decryptFile(cipherBytes);
            ByteArrayInputStream decryptedInputStream = new ByteArrayInputStream(plainByteArray);
    
            String decryptedFilePath = fss.getAppHomePath() + "DecryptedDB.db";
            try (Writer w = new OutputStreamWriter(fss.openOutputStream(decryptedFilePath), "ISO-8859-1")) {//ISO-8859-1 Windows-1252 UTF-8 
                int nextChar = decryptedInputStream.read();
                while (nextChar > -1) {
                    char[] charArray = {(char) nextChar};
                    w.write(charArray);
                    nextChar = decryptedInputStream.read();
                }
            } catch (Exception e) {
                Log.p("Error " + e);
            }
        } catch (Exception e) {
            Log.p("Error " + e);
        }
    }
    

    The following FilesCipher class handles encryption and decryption

    import com.codename1.io.Log;
    import java.io.UnsupportedEncodingException;
    import org.bouncycastle.crypto.CipherParameters;
    import org.bouncycastle.crypto.CryptoException;
    import org.bouncycastle.crypto.engines.AESEngine;
    import org.bouncycastle.crypto.modes.CBCBlockCipher;
    import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
    import org.bouncycastle.crypto.params.KeyParameter;
    import org.bouncycastle.crypto.params.ParametersWithIV;
    import org.bouncycastle.util.encoders.Base64;
    
    public class FilesCipher {
    
        private static PaddedBufferedBlockCipher cipher;
        private static final String SECRET_KEY = "Your 16 Character Secret Key";
        private static final String I_VECTOR = "Your 16 Character initialization vector";
    
        public static byte[] encryptFile(byte[] plainBytes) {
            byte[] byteArray = null;
            try {
                cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
    
                byte sKey[] = SECRET_KEY.getBytes("UTF-8");
                byte[] iv = I_VECTOR.getBytes("UTF-8");
    
                KeyParameter keyParam = new KeyParameter(sKey);
    
                CipherParameters ivAndKey = new ParametersWithIV(keyParam, iv);
                cipher.init(true, ivAndKey);
                byte[] cipherBytes = cipherData(plainBytes);
                byteArray = Base64.encode(cipherBytes);
    
            } catch (UnsupportedEncodingException | IllegalArgumentException
                    | CryptoException e) {
                Log.p("Error " + e);
            }
            return byteArray;
        }
    
        public static byte[] decryptFile(byte[] cipherBytes) {
            byte[] byteArray = null;
            try {
                cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
    
                byte sKey[] = SECRET_KEY.getBytes("UTF-8");
                byte[] iv = I_VECTOR.getBytes("UTF-8");
    
                KeyParameter keyParam = new KeyParameter(sKey);
    
                byte[] cipherData = Base64.decode(cipherBytes);
                CipherParameters ivAndKey = new ParametersWithIV(keyParam, iv);
    
                cipher.init(false, ivAndKey);
    
                byteArray = cipherData(cipherData);
            } catch (UnsupportedEncodingException | IllegalArgumentException
                    | CryptoException e) {
                Log.p("Error " + e);
            }
            return byteArray;
        }
    
        private static byte[] cipherData(byte[] data) throws CryptoException {
            int minSize = cipher.getOutputSize(data.length);
            byte[] outBuf = new byte[minSize];
            int len1 = cipher.processBytes(data, 0, data.length, outBuf, 0);
            int len2 = cipher.doFinal(outBuf, len1);
            int actualLen = len1 + len2;
            byte[] result = new byte[actualLen];
            System.arraycopy(outBuf, 0, result, 0, result.length);
            return result;
        }
    }
    

    This also makes it possible to keep the database encrypted. That is, after database is created, this database file can be encrypted using FilesCipher.encryptFile() then save this as the database.

    Before executing Create, Read, Update & Delete queries, decrypt the database file using FilesCipher.decryptFile().

    After executing queries, encrypt the database again using FilesCipher.encryptFile()