Skip to content
This repository has been archived by the owner on Mar 3, 2020. It is now read-only.

Encrypt message on one device and decrypt on another #49

Closed
eliaszkubala opened this issue Dec 26, 2014 · 6 comments
Closed

Encrypt message on one device and decrypt on another #49

eliaszkubala opened this issue Dec 26, 2014 · 6 comments

Comments

@eliaszkubala
Copy link

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.

@eliaszkubala eliaszkubala changed the title Encrypt message on one device and decrypt on another. Encrypt message on one device and decrypt on another Dec 26, 2014
@siyengar
Copy link
Contributor

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
Copy link
Contributor

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

@eliaszkubala
Copy link
Author

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
Copy link
Contributor

siyengar commented Jan 5, 2015

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
Copy link
Author

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
Copy link

huttarl commented Oct 28, 2015

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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants