【问题标题】:EVP_DecryptFinal_ex:bad decrypt, possible key generation issue in AndroidEVP_DecryptFinal_ex:解密错误,Android 中可能存在密钥生成问题
【发布时间】:2016-08-09 04:54:24
【问题描述】:

所以我遇到了从基于字符串的密码生成密钥的问题。加密步骤工作正常,解密步骤工作直到底部列出的错误,使解密的文件损坏。我使用以下函数来执行加密/解密:

public static boolean decryptFileFromUri(Context context, Uri file, String keyphrase) {
    try {
        File f = new File(getRealPathFromURI(context, file));
        FileInputStream fis = new FileInputStream(f);

        File ef = new File(f.toString().replace(".epf", ""));
        FileOutputStream fos = new FileOutputStream(ef);

        Log.d("HIDEMYPICS","Decrypting: " + f.toString());

        byte[] rawKey = getRawKey(keyphrase.getBytes("UTF8"));
        /*KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(rawKey);
        kgen.init(128, sr); // 192 and 256 bits may not be available
        SecretKey skey = kgen.generateKey();
        byte[] key = skey.getEncoded();*/
        SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        // Wrap the output stream
        CipherOutputStream cos = new CipherOutputStream(fos, cipher);
        // Write bytes
        int b;
        byte[] d = new byte[8];
        while ((b = fis.read(d)) != -1) {
            cos.write(d, 0, b);
        }
        // Flush and close streams.
        cos.flush();
        cos.close();
        fis.close();

        Log.d("HIDEMYPICS","Decrypted to: " + ef.toString());
        return true;
    } catch (IOException e){
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    }
    return false;
}

public static boolean encryptFileFromUri(Context context, Uri file, String keyphrase) {
    try {
        File f = new File(getRealPathFromURI(context, file));
        FileInputStream fis = new FileInputStream(f);

        File ef = new File(f.toString() + ".epf");
        FileOutputStream fos = new FileOutputStream(ef);

        Log.d("HIDEMYPICS","Encrypting: " + f.toString());

        byte[] rawKey = getRawKey(keyphrase.getBytes("UTF8"));
        /*KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(rawKey);
        kgen.init(128, sr); // 192 and 256 bits may not be available
        SecretKey skey = kgen.generateKey();
        byte[] key = skey.getEncoded();*/
        SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        // Wrap the output stream
        CipherOutputStream cos = new CipherOutputStream(fos, cipher);
        // Write bytes
        int b;
        byte[] d = new byte[8];
        while ((b = fis.read(d)) != -1) {
            cos.write(d, 0, b);
        }
        // Flush and close streams.
        cos.flush();
        cos.close();
        fis.close();
        Log.d("HIDEMYPICS","Encrypted to: " + ef.toString());
        return true;
    } catch (IOException e){
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    }
    return false;
}

这是生成原始密钥的函数:

private static byte[] getRawKey(byte[] seed) throws NoSuchAlgorithmException {
    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();

    String result = "";
    for(int index = 0; index < raw.length; index++) {
        result += Integer.toHexString(raw[index]);
        // maybe you have to convert your byte to int before this can be done
        // (cannot check reight now)
    }

    Log.d("HIDEMYPICS","Passphrase: " + new String(seed).toString() + " Key: " + result );
    return raw;
}

对于上述密钥的十六进制转储,“test”输入字符串的输出如下:

加密:

    04-17 09:01:25.088 18341-18341/com.dcheeseman.hidemypics D/HIDEMYPICS: Encrypting: /storage/emulated/0/Download/bailout_5128280_GIFSoup.com-1.gif
04-17 09:01:25.088 18341-18341/com.dcheeseman.hidemypics D/HIDEMYPICS: Passphrase: test Key: ffffff85affffffe21023ffffffb7ffffffe8ffffffc8214031fffffffa5b29ffffff9affffff80

解密:

04-17 09:01:43.808 18341-18341/com.dcheeseman.hidemypics D/HIDEMYPICS: Decrypting: /storage/emulated/0/Download/bailout_5128280_GIFSoup.com-1.gif.epf
04-17 09:01:43.808 18341-18341/com.dcheeseman.hidemypics D/HIDEMYPICS: Passphrase: test Key: ffffff8331ffffffe2ffffff87ffffffe242dffffffa61cffffffc7ffffffb4ffffffa1d74ffffff9affffff9b

解密和加密都以“test”作为密码,但该函数返回 2 个不同的密钥,这就是为什么我认为我收到了 bad-decrypt 错误,其完整跟踪如下所示:

    04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err: java.io.IOException: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at javax.crypto.CipherOutputStream.close(CipherOutputStream.java:136)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at com.dcheeseman.hidemypics.AESUtils.decryptFileFromUri(AESUtils.java:102)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at com.dcheeseman.hidemypics.HideMyPics.onActivityResult(HideMyPics.java:35)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at android.app.Activity.dispatchActivityResult(Activity.java:6808)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at android.app.ActivityThread.deliverResults(ActivityThread.java:4698)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at android.app.ActivityThread.handleSendResult(ActivityThread.java:4745)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at android.app.ActivityThread.access$1500(ActivityThread.java:197)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1730)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at android.os.Looper.loop(Looper.java:145)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6872)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at java.lang.reflect.Method.invoke(Method.java:372)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
04-17 09:33:24.158 823-823/com.dcheeseman.hidemypics W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)

提前感谢您对此问题的任何帮助!

【问题讨论】:

  • 从问题中删除未使用的代码。
  • 您声明:“2 个不同的密钥”,密钥必须相同,因此要调试的区域是密钥派生getRawKey。还要检查 IV 是如何创建并传递给解密的,您需要研究库文档。

标签: android encryption passwords encryption-symmetric


【解决方案1】:

您声明:“2 个不同的键”,键必须相同。

通常您预先共享一个密钥或密码,如果您使用密码,则使用 PBKDF2 等功能从中派生一个密钥。如果您使用密码派生,则必须对加密和解密使用相同的确定性函数。使用的函数必须从每一侧的密码中产生相同的密钥。由于您使用的是随机生成器,因此推导不是确定性的,这是行不通的。

【讨论】:

  • 不会在安全随机函数上设置种子作为强制算法具有确定性的一种方式吗?我遇到的问题是我正在尝试使用确定性函数,并且每次运行它都会生成不同的密钥。
  • 不适用于加密 PRNG,它们是从系统上的事件中播种和重新播种的。使用 PBKDF2(基于密码的密钥派生函数 2),这就是它的设计目的。 PBKDF2 还需要一个迭代计数以使计算花费更长的时间,这对于密钥推导以延迟攻击者多次尝试是可取的,使用导致推导大约 100 毫秒的值。
  • 听取了您的建议并找到了获取 PBKDF2 的文档,并修复了密钥生成问题。感谢您的回复!
【解决方案2】:

采纳了 Zaph 的建议并切换到 PBKDF2,现在可以生成一致的密钥。这是任何好奇的人的代码。

public static SecretKey generateKey(Context c, char[] passphraseOrPin) throws NoSuchAlgorithmException, InvalidKeySpecException {
    // Number of PBKDF2 hardening rounds to use. Larger values increase
    // computation time. You should select a value that causes computation
    // to take >100ms.
    byte[] salt = Settings.Secure.getString(c.getContentResolver(),
            Settings.Secure.ANDROID_ID).getBytes();

    final int iterations = 1000;

    final int outputKeyLength = 128;

    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
    SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
    Log.d("HIDEMYPICS","Secret Key: " + toHex(secretKey.getEncoded()) );
    return secretKey;
}

public static boolean decryptFileFromUri(Context context, Uri file, String keyphrase) {
    try {
        File f = new File(getRealPathFromURI(context, file));
        FileInputStream fis = new FileInputStream(f);

        File ef = new File(f.toString().replace(".epf", ""));
        FileOutputStream fos = new FileOutputStream(ef);

        Log.d("HIDEMYPICS","Decrypting: " + f.toString());

        SecretKey key = generateKey(context, keyphrase.toCharArray());
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        // Wrap the output stream
        CipherOutputStream cos = new CipherOutputStream(fos, cipher);
        // Write bytes
        int b;
        byte[] d = new byte[8];
        while ((b = fis.read(d)) != -1) {
            cos.write(d, 0, b);
        }
        // Flush and close streams.
        cos.flush();
        cos.close();
        fis.close();

        Log.d("HIDEMYPICS","Decrypted to: " + ef.toString());
        return true;
    } catch (IOException e){
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (InvalidKeySpecException e) {
        e.printStackTrace();
    }
    return false;
}

public static boolean encryptFileFromUri(Context context, Uri file, String keyphrase) {
    try {
        File f = new File(getRealPathFromURI(context, file));
        FileInputStream fis = new FileInputStream(f);

        File ef = new File(f.toString() + ".epf");
        FileOutputStream fos = new FileOutputStream(ef);

        Log.d("HIDEMYPICS","Encrypting: " + f.toString());

        SecretKey key = generateKey(context, keyphrase.toCharArray());
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        // Wrap the output stream
        CipherOutputStream cos = new CipherOutputStream(fos, cipher);
        // Write bytes
        int b;
        byte[] d = new byte[8];
        while ((b = fis.read(d)) != -1) {
            cos.write(d, 0, b);
        }
        // Flush and close streams.
        cos.flush();
        cos.close();
        fis.close();
        Log.d("HIDEMYPICS","Encrypted to: " + ef.toString());
        return true;
    } catch (IOException e){
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (InvalidKeySpecException e) {
        e.printStackTrace();
    }
    return false;
}

注意:它仍然不能 100% 工作,在解密过程中会损坏前几个字节,从而导致文件头损坏。如果您想查看结果,我已经在这里开始了一个单独的问题帖子:CipherOutputStream corrupting headers in Android

【讨论】:

    猜你喜欢
    • 2015-09-13
    • 1970-01-01
    • 2020-04-30
    • 1970-01-01
    • 2016-10-26
    • 2016-03-22
    • 1970-01-01
    • 2020-09-16
    • 1970-01-01
    相关资源
    最近更新 更多