3

google introduced security-crypto jetpack library

i want to use this library for encrypt image files, in documents of library there is no sample for encryption of image files.

i converted image to bitmap - bitmap to byte array - then used library As mentioned in the document finaly file is encrypted, but when the file is decoded, a black image appears i donot get any exception where is the problem?

public class MainActivity extends AppCompatActivity {
private ImageView imageView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    imageView =findViewById(R.id.image);

    // get image from asset and convert to bitmap
    Bitmap bitmap = getBitmapFromAsset(this,"temp.jpg");
    byte[] bytesOfBitmap= BitmapToByteArray(bitmap);


    // encrypt
    File pathForSaveEncryptedFile=new File(getFilesDir().getAbsolutePath() + File.separator+"encrypted.me");
    try {
        encryptToFile(this,pathForSaveEncryptedFile,bytesOfBitmap);
    } catch (GeneralSecurityException | IOException e) {
        e.printStackTrace();
    }

    // decrypt and show to image view
    try {
        byte[] decryptedFile = decryptFile(this,pathForSaveEncryptedFile);
        imageView.setImageBitmap(ByteArrayToBitmap(decryptedFile));
    } catch (GeneralSecurityException | IOException e) {
        e.printStackTrace();
    }

}

private void encryptToFile(Context context,File pathToSave,byte[] contents) throws GeneralSecurityException, IOException {
    MasterKey mainKey = new MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build();
    EncryptedFile encryptedFile = new EncryptedFile.Builder(context,
            pathToSave,
            mainKey,
            EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build();

    OutputStream outputStream = encryptedFile.openFileOutput();
    outputStream.write(contents);
    outputStream.flush();
    outputStream.close();
}

private byte[] decryptFile(Context context ,File target) throws GeneralSecurityException, IOException {

    MasterKey mainKey = new MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build();
    EncryptedFile encryptedFile = new EncryptedFile.Builder(context,
            target,
            mainKey,
            EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build();
    InputStream inputStream = encryptedFile.openFileInput();
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    int nextByte = inputStream.read();
    while (nextByte != -1) {
        byteArrayOutputStream.write(nextByte);
        nextByte = inputStream.read();
    }
    return byteArrayOutputStream.toByteArray();
}

private byte[] BitmapToByteArray(Bitmap bitmap){
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
    return stream.toByteArray();
}

private Bitmap ByteArrayToBitmap(byte[] bytes){
    return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}

private Bitmap getBitmapFromAsset(Context context, String fileName) {
    AssetManager assetManager = context.getAssets();
    InputStream istr;
    Bitmap bitmap = null;
    try {
        istr = assetManager.open(fileName);
        bitmap = BitmapFactory.decodeStream(istr);
    } catch (IOException e) {
        // handle exception
    }

    return bitmap;
}

}

also i converted image to base64(String) and use the library but not work.

PerracoLabs
  • 16,449
  • 15
  • 74
  • 127

1 Answers1

3

You have two ways to encrypt / decrypt bitmaps.

Solution 1

To use the streams provided by EncryptedFile. No need to use Base64 encoding/decoding at all.

Encrypt:

private void encrypt(final Context context, final File target, final Bitmap bitmap) throws GeneralSecurityException, IOException {
    final MasterKey mainKey = new MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build();
    
    final EncryptedFile file = new EncryptedFile.Builder(context,
            target, mainKey, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
            .build();

    final OutputStream stream = file.openFileOutput();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream);
    stream.flush();
    stream.close();
}

Decrypt:

private Bitmap decrypt(final Context context, final File target) throws GeneralSecurityException, IOException  {
    final MasterKey mainKey = new MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build();
    
    final EncryptedFile file = new EncryptedFile.Builder(context,
            target, mainKey, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
            .build();

    final InputStream stream = file.openFileInput();
    return BitmapFactory.decodeStream(stream);
}

Solution 2

  1. Before encrypting, encode the bitmap bytes to base64.
  2. After decrypting, decode from base64 before converting back to bitmap.

For your specific code sample do as next:

private byte[] bitmapToByteArray(final Bitmap bitmap) {
    final ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);

    final String data = Base64.encodeToString(stream.toByteArray(), Base64.DEFAULT);

    return data.getBytes();
}

private Bitmap byteArrayToBitmap(final byte[] bytes) {
    final byte[] data = Base64.decode(bytes, Base64.DEFAULT);
    return BitmapFactory.decodeByteArray(data, 0, data.length);
}
PerracoLabs
  • 16,449
  • 15
  • 74
  • 127
  • 1
    Base64 encoding data **before** encryption usually make no sense. – President James K. Polk Oct 13 '20 at 15:29
  • 1
    @President James K. Polk. In this case is necessary, because the Bitmap.compress method transforms the image into Jpeg format, and not base64. The jetpack security encryption / decryption fails to handle it. If you try the question source you will see that it fails without both base64 encoding/decoding. – PerracoLabs Oct 13 '20 at 15:37
  • 1
    But why? I may be missing something but I see nothing in the code that would be fixed by base64-encoding the plaintext. – President James K. Polk Oct 13 '20 at 15:57
  • 2
    Looks like the method `inputStream.read()` (i.e. `EncryptedFile$EncryptedFileInputStream#read()`) in `decryptFile()` returns a `0xFFFFFFFF` (-1) for `0xFF`, which is reserved for _EOF_. Thus, usually a too short/corrupted plaintext is returned (which can easily be verified using a short plaintext containing a `0xFF`). Therefore Base64 encoding is a possible (but not efficient) solution, as it eliminates the `0xFF` values. An alternative is _not_ to use the above `read()` overload, but `read(byte[] buffer)` instead, or Solution 1 from the answer. – Topaco Oct 13 '20 at 20:33
  • 2
    @Topaco: That seems like a gigantic bug if true. – President James K. Polk Oct 13 '20 at 20:38
  • Just checked the bug tracker. The bug seems to be already known: [162568139](https://issuetracker.google.com/issues/162568139) (Google login required) from 08/2020: _Encrypted data written in EncryptedFile is incomplete when read._ From the description: _Checking that nextByte != -1 will abort the reading if you have a 0xff byte in your data..._ – Topaco Oct 14 '20 at 06:49
  • @PerracoLabs : thanks for this answer it is correct. but still i do not understand why my code not work without convert to base64. google library need byte array for encryption why its not work without base64 convert – mabna groupe Oct 14 '20 at 09:49