facebookarchive / conceal

Conceal provides easy Android APIs for performing fast encryption and authentication of data.
http://facebook.github.io/conceal/
Other
2.96k stars 431 forks source link

Encrypt message on one device and decrypt on another #49

Closed eliaszkubala closed 9 years ago

eliaszkubala commented 9 years ago

Hi,

I have a strange problem with sharing cipher message between two devices. I make a chat which is using this cipher to encode share message, image, video and voice. Everything works well when i type to myself, that's mean encode and decode on same device.

When I write message to someone else e.g. message = "Hi buddy!"; key = "3oqpnnnrhip5gdh02mt6bnslke"; message = String.valueOf(Base64.encodeToString(crypto.encrypt(Base64.encode(message[0].getBytes(), Base64.DEFAULT), new Entity(key)), Base64.DEFAULT)); after encode: "AQEt6KNevPCjsgllKD985iX1lqpwYyzObkYclEK4i/fxxp0bjwLJzIcNAg=="

on another device i get the same encode message and key. But when i try decode i always get IOException.

"com.facebook.crypto.cipher.NativeGCMCipherException: decryptFinal 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at com.facebook.crypto.cipher.NativeGCMCipher.decryptFinal(NativeGCMCipher.java:108) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at com.facebook.crypto.streams.NativeGCMCipherInputStream.ensureTagValid(NativeGCMCipherInputStream.java:126) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at com.facebook.crypto.streams.NativeGCMCipherInputStream.read(NativeGCMCipherInputStream.java:91) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at com.facebook.crypto.streams.NativeGCMCipherInputStream.read(NativeGCMCipherInputStream.java:76) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at com.facebook.crypto.Crypto.decrypt(Crypto.java:131) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at com.incotalk.activity_talk.decode(activity_talk.java:1121) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at com.incotalk.activity_talk$6.onItemClick(activity_talk.java:446) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at android.widget.AdapterView.performItemClick(AdapterView.java:298) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at android.widget.AbsListView.performItemClick(AbsListView.java:1086) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at android.widget.AbsListView$PerformClick.run(AbsListView.java:2855) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at android.widget.AbsListView$1.run(AbsListView.java:3529) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at android.os.Handler.handleCallback(Handler.java:615) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at android.os.Handler.dispatchMessage(Handler.java:92) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at android.os.Looper.loop(Looper.java:137) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at android.app.ActivityThread.main(ActivityThread.java:4745) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at java.lang.reflect.Method.invokeNative(Native Method) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at java.lang.reflect.Method.invoke(Method.java:511) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553) 12-26 09:37:16.399 2327-2327/com.incotalk W/System.err﹕ at dalvik.system.NativeStart.main(Native Method)"

I decode with this line of code: byte[] messageB = Base64.decode(message, Base64.DEFAULT); return new String( Base64.decode(crypto.decrypt(messageB, new Entity(attachmentKeyDecoded)),Base64.DEFAULT), "UTF-8");

return new String( Base64.decode(crypto.decrypt(messageB, new Entity(attachmentKeyDecoded)), Base64.DEFAULT), "UTF-8" )

Please explain why it doesn't work.

siyengar commented 9 years ago

Entity does not actually wrap the key. Entity is meant to be a publicly visible filename to prevent against substitution attacks.

The key comes from the KeyChain you supply while initializing the Crypto object. The default SharedPreferencesKeyChain generates a key per device. You can make the KeyChain return the same key on both devices by creating a custom KeyChain. The key needs to be NativeGCMCipher.KEY_LENGTH length. You can look at the SharedPreferencesKeyChain as a sample https://github.com/facebook/conceal/blob/master/java/com/facebook/crypto/keychain/SharedPrefsBackedKeyChain.java

siyengar commented 9 years ago

I hope that answers your question. I'm closing out the task. Feel free to reopen if you have follow up questions.

eliaszkubala commented 9 years ago

Hi Siyengar, thanks for your answer.

I have two question. I made a class like you sad. CustomSharedPrefsBackedKeyChain.java

Here is a code: http://ideone.com/3Zlgos. I added fixed String key 2cda8e8ebb37f2b1. I worry it doesn't safe so, I thought about create own constructor with random key variable, But I can't initialize Crypto more than once so I can't pass my own key when i wan't decrypt or encrypt data.

crypto = new Crypto( new CustomSharedPrefsBackedKeyChain(c, "new random key"), new SystemNativeCryptoLibrary());

So, my question is,

  1. Is making a KeyChain which return the same key on booth devices and only protect file by random entity is safe?
  2. There is any solution for initialize Cyrpto more than once.
siyengar commented 9 years ago

You can initialize Crypto as many times as you want, just call new Crypto()

For the 2 device problem, you'll need a way to share a key between the 2 devices. It sounds like you want an encrypted communication protocol between devices and not a pure file encryption solution. You might want to consider using a secure communication protocol instead like SSL. This completely depends on the communication medium and the nature of your app.

eliaszkubala commented 9 years ago

Okey,

I made this class CustomSharedPrefsBackedKeyChain and it's work.

package XXX;
import com.facebook.crypto.exception.KeyChainException;
import com.facebook.crypto.keychain.KeyChain;
import com.facebook.crypto.keychain.SecureRandomFix;
import java.security.SecureRandom;
import java.util.Arrays;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;

import com.facebook.crypto.cipher.NativeGCMCipher;
import com.facebook.crypto.mac.NativeMac;

/**
 * Created by TheKing on 2014-12-29.
 */
public class CustomSharedPrefsBackedKeyChain implements KeyChain {
    // Visible for testing.
    /* package */ static final String SHARED_PREF_NAME = "crypto";
    /* package */ static final String CIPHER_KEY_PREF = "cipher_key";
    /* package */ static final String MAC_KEY_PREF = "mac_key";

    private final SharedPreferences mSharedPreferences;
    private final SecureRandom mSecureRandom;
    private String key = null;

    protected byte[] mCipherKey;
    protected boolean mSetCipherKey;

    protected byte[] mMacKey;
    protected boolean mSetMacKey;

    private static final SecureRandomFix sSecureRandomFix = new SecureRandomFix();
    private String log = "KeyChain";

    public CustomSharedPrefsBackedKeyChain(Context context, String key) {
        Log.d(log, "CustomSharedPrefsBackedKeyChain");
        mSharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
        mSecureRandom = new SecureRandom();
        this.key = key;
    }

    @Override
    public synchronized byte[] getCipherKey() throws KeyChainException {
        if (!mSetCipherKey) {
            mCipherKey = maybeGenerateKey(CIPHER_KEY_PREF, NativeGCMCipher.KEY_LENGTH);
        }
        mSetCipherKey = true;
        return mCipherKey;
    }

    @Override
    public byte[] getMacKey() throws KeyChainException {
        if (!mSetMacKey) {
            mMacKey = maybeGenerateKey(MAC_KEY_PREF, NativeMac.KEY_LENGTH);
        }
        mSetMacKey = true;
        return mMacKey;
    }

    @Override
    public byte[] getNewIV() throws KeyChainException {
        sSecureRandomFix.tryApplyFixes();
        byte[] iv = new byte[NativeGCMCipher.IV_LENGTH];
        mSecureRandom.nextBytes(iv);
        return iv;
    }

    @Override
    public synchronized void destroyKeys() {
        mSetCipherKey = false;
        mSetMacKey = false;
        Arrays.fill(mCipherKey, (byte) 0);
        Arrays.fill(mMacKey, (byte) 0);
        mCipherKey = null;
        mMacKey = null;
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.remove(CIPHER_KEY_PREF);
        editor.remove(MAC_KEY_PREF);
        //editor.commit();
    }

    /**
     * Generates a key associated with a preference.
     */
    private byte[] maybeGenerateKey(String pref, int length) throws KeyChainException {
        String base64Key = mSharedPreferences.getString(pref, null);

        String key;
        if(length == NativeGCMCipher.KEY_LENGTH)
            key = this.key;
        else if(length == NativeMac.KEY_LENGTH)
            key = (this.key + this.key + this.key +this.key);
        else
            key = this.key;

        base64Key = key.toString();

        if (base64Key == null) {
            // Generate key if it doesn't exist.
            return generateAndSaveKey(pref, length);
        } else {
            return base64Key.getBytes();
        }
    }

    private byte[] generateAndSaveKey(String pref, int length) throws KeyChainException {
        sSecureRandomFix.tryApplyFixes();
        byte[] key = new byte[length];
        if(length == NativeGCMCipher.KEY_LENGTH)
            key = this.key.getBytes();
        else if(length == NativeMac.KEY_LENGTH)
            key = (this.key + this.key + this.key +this.key).getBytes();
        else
            key = this.key.getBytes();

        mSecureRandom.nextBytes(key);
        // Store the session key.
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(
                pref,
                encodeForPrefs(key));
        //editor.commit();

        Log.d(log, key.toString());

        return key;
    }

    /**
     * Visible for testing.
     */
  byte[] decodeFromPrefs(String keyString) {
        if (keyString == null) {
            return null;
        }
        Log.d(log, keyString.toString());
        return Base64.decode(keyString, Base64.DEFAULT);
    }

    /**
     * Visible for testing.
    */
    String encodeForPrefs(byte[] key) {
        if (key == null ) {
                return null;
        }
        return Base64.encodeToString(key, Base64.DEFAULT);
    }

}

To initialize use this

Crypto crypto = new Crypto(new CustomSharedPrefsBackedKeyChain(c, key), new SystemNativeCryptoLibrary());

Thanks very much for your time.

huttarl commented 9 years ago

@eliaszkubala thank you for sharing this implementation! I had a very similar need.