javax.crypto在不同版本的Android OS中的工作方式不同?

时间:2011-06-11 13:38:58

标签: java android encryption javax.crypto

我正在使用此代码段来加密/解密我应用的数据库中的数据:

http://www.androidsnippets.com/encryptdecrypt-strings

似乎javax.crypto.KeyGenerator.generateKey()操作在Android 2.3.3操作系统中的工作方式与其他(以前的?)版本不同。当然,这对我的用户在将设备从2.2升级到2.3.3并且应用程序开始抛出解密数据库的错误时会出现一个主要问题。

这是一个已知问题吗?我是否错误地使用了加密库?任何人都有任何关于如何解决这个问题的建议,以便2.2中加密的数据能够在2.3.3中解密?

我构建了一个测试应用程序,通过加密功能提供值。当我在2.2 AVD上运行它时,我得到一个结果。当我在2.3.3 AVD上运行它时,我得到了不同的结果。

    import java.security.SecureRandom;

    import javax.crypto.Cipher;
    import javax.crypto.KeyGenerator;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;

    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.TextView;

    public class main extends Activity {
        TextView tvOutput;
        static String out;
        String TEST_STRING = "abcdefghijklmnopqrstuvwxyz";
        String PASSKEY = "ThePasswordIsPassord";

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            tvOutput = (TextView) findViewById(R.id.tvOutput);
        }

        @Override
        public void onResume() {
            super.onResume();
            out = "";
            runTest();
            tvOutput.setText(out);
        }

        private void runTest() {
            out = "Test string: " + TEST_STRING + "\n";
            out += "Passkey: " + PASSKEY + "\n";
            try {
                out += "Encrypted: " + encrypt(PASSKEY, TEST_STRING) + "\n";
            } catch (Exception e) {
                out += "Error: " + e.getMessage() + "\n";
                e.printStackTrace();
            }

        }

        public static String encrypt(String seed, String cleartext)
        throws Exception {
            byte[] rawKey = getRawKey(seed.getBytes());
            byte[] result = encrypt(rawKey, cleartext.getBytes());
            return toHex(result) + "\n" + "Raw Key: " + String.valueOf(rawKey)
                    + "\n";
        }

        private static byte[] getRawKey(byte[] seed) throws Exception {
            KeyGenerator kgen = KeyGenerator.getInstance("AES");
            SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
            sr.setSeed(seed);
            kgen.init(128, sr); // 192 and 256 bits may not be available
            SecretKey skey = kgen.generateKey();
            byte[] raw = skey.getEncoded();
            return raw;
        }

        private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
                byte[] encrypted = cipher.doFinal(clear);
            return encrypted;
        }

        public static String toHex(String txt) {
            return toHex(txt.getBytes());
        }

        public static String fromHex(String hex) {
            return new String(toByte(hex));
        }

        public static byte[] toByte(String hexString) {
            int len = hexString.length() / 2;
            byte[] result = new byte[len];
            for (int i = 0; i < len; i++)
                result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2),
                        16).byteValue();
            return result;
        }

        public static String toHex(byte[] buf) {
            if (buf == null)
                return "";
            StringBuffer result = new StringBuffer(2 * buf.length);
            for (int i = 0; i < buf.length; i++) {
                appendHex(result, buf[i]);
            }
            return result.toString();
        }

         private final static String HEX = "0123456789ABCDEF";

        private static void appendHex(StringBuffer sb, byte b) {
            sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
        }
    }

我的main.xml布局如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content" android:id="@+id/tvOutput" />
    </LinearLayout>

由于我是新用户,因此无法发布链接或图片,但如果您想查看结果,则可以解读以下两张图片的网址:

我从2.2获得的信息:

wct.vg/wt/droid/2.2.png

..和2.3.3:

wct.vg/wt/droid/2.3.3.png

3 个答案:

答案 0 :(得分:5)

你滥用伪随机数生成器,它的种子作为一个关键的派生函数 - 这真的是非常糟糕的风格。伪随机数生成器“SHA1PRNG”不是像AES那样的标准 - 因此您永远不知道您获得了什么实现。 另请参阅Is there a SHA1PRNG standard

难怪你得到不同的结果。基于给定种子获得确定性结果不是您可以从伪随机数函数中获得的属性。

如果您想从密码中获取加密密钥,请使用Key Derivation Function之类的PKCS#5 / PBKDF2。 PBKDF2的实施是AFAIR包含在Bouncy Castle中。

答案 1 :(得分:2)

答案就在这个问题中:BouncyCastle AES error when upgrading to 1.45

答案 2 :(得分:1)

我要感谢为这个问题做出贡献的每个人。

以下是我最终提出的如何使用密码进行加密/解密的示例,这在Android 2.2和2.3.3之间似乎是一致的。

主要活动:

package cc.ndl.testencryption;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class main extends Activity {
    TextView tvOutput;
    static String out;
    String TEST_STRING = "abcdefghijklmnopqrstuvwxyz";
    static String PASSKEY = "ThePasswordIsPassord";
    static byte[] SALT = { 1, 2, 4, 5 };
    static int ITERATIONS = 1979;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        tvOutput = (TextView) findViewById(R.id.tvOutput);
    }

    @Override
    public void onResume() {
        super.onResume();
        out = "";
        runTest();
        tvOutput.setText(out);
    }

    private void runTest() {
        out = "Test string: " + TEST_STRING + "\n";
        out += "Passkey: " + PASSKEY + "\n";
        try {
            Crypto crypto = new Crypto(PASSKEY);
            String encryptedData = crypto.encrypt(TEST_STRING);
            out += "Encrypted: " + encryptedData + "\n";
            out += "Decrypted: " + crypto.decrypt(encryptedData);
        } catch (Exception e) {
            out += "Error: " + e.getMessage() + "\n";
            e.printStackTrace();
        }

    }
}

主要布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:id="@+id/tvOutput" />
</LinearLayout>

加密对象:

package cc.ndl.testencryption;

import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

public class Crypto {

    Cipher ecipher;
    Cipher dcipher;

    // 8-byte Salt
    byte[] salt = { 1, 2, 4, 5, 7, 8, 3, 6 };

    // Iteration count
    int iterationCount = 1979;

    Crypto(String passPhrase) {
        try {
            // Create the key
            KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt,
                    iterationCount);
            SecretKey key = SecretKeyFactory.getInstance(
                    "PBEWITHSHA256AND128BITAES-CBC-BC").generateSecret(keySpec);
            ecipher = Cipher.getInstance(key.getAlgorithm());
            dcipher = Cipher.getInstance(key.getAlgorithm());

            // Prepare the parameter to the ciphers
            AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt,
                    iterationCount);

            // Create the ciphers
            ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
            dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
        } catch (Exception e) {
        }
    }

    public String encrypt(String str) {
        String rVal;
        try {
            // Encode the string into bytes using utf-8
            byte[] utf8 = str.getBytes("UTF8");

            // Encrypt
            byte[] enc = ecipher.doFinal(utf8);

            // Encode bytes to base64 to get a string
            rVal = toHex(enc);
        } catch (Exception e) {
            rVal = "Error encrypting: " + e.getMessage();
        }
        return rVal;
    }

    public String decrypt(String str) {
        String rVal;
        try {
            // Decode base64 to get bytes
            byte[] dec = toByte(str);

            // Decrypt
            byte[] utf8 = dcipher.doFinal(dec);

            // Decode using utf-8
            rVal = new String(utf8, "UTF8");
        } catch (Exception e) {
            rVal = "Error encrypting: " + e.getMessage();
        }
        return rVal;
    }

    private static byte[] toByte(String hexString) {
        int len = hexString.length() / 2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2),
                    16).byteValue();
        return result;
    }

    private static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }

    private final static String HEX = "0123456789ABCDEF";

    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }
}