【问题标题】:android cipher doesn't decrypt first 16 bytes / characters of encrypted dataandroid cipher 不会解密加密数据的前 16 个字节/字符
【发布时间】:2015-01-13 00:58:08
【问题描述】:

我正在开发一个文件加密/解密应用程序。我正在使用一个简单的 .txt 文件进行测试。当我从应用程序中选择文件并选择加密时,整个文件数据都会被加密。但是,当我解密时,只有部分文件数据被解密。由于某种原因,前 16 个字节/字符没有被解密。

test_file.txt 内容:"This sentence is used to check file encryption/decryption results."

加密结果:"¾mÁSTÐÿT:Y­„"O¤]ÞPÕµß~ëqrÈb×ßq²¨†ldµJ,O|56\e^-’@þûÝû"

解密结果:"£ÿÒÜÑàh]VÄþ„- used to check file encryption/decryption results."

logcat 中没有任何错误。

我做错了什么?

文件加密方法:

public void encryptFile(String password, String filePath) {
    byte[] encryptedFileData = null;
    byte[] fileData = null;

    try {
        fileData = readFile(filePath);//method provided below

        // 64 bit salt for testing only
        byte[] salt = "goodsalt".getBytes("UTF-8");
        SecretKey key = generateKey(password.toCharArray(), salt);//method provided below

        byte[] keyData = key.getEncoded();
        SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);

        encryptedFileData = cipher.doFinal(fileData);

        saveData(encryptedFileData, filePath);//method provided below
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

读取文件内容的方法:

public byte[] readFile(String filePath) {
    byte[] fileData;
    File file = new File(filePath);
    int size = (int) file.length();
    fileData = new byte[size];

    try {
        BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
        inputStream.read(fileData);
        inputStream.close();
    } 
    catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    catch (IOException e) {
        e.printStackTrace();
    }

    return fileData;
}

秘钥生成方法:

private SecretKey generateKey(char[] password, byte[] salt) 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.
    final int iterations = 1000;

    // Generate a 256-bit key
    final int outputKeyLength = 256;

    SecretKeyFactory secretKeyFactory;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // Use compatibility key factory -- only uses lower 8-bits of passphrase chars
        secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1And8bit");
    }
    else {
        // Traditional key factory. Will use lower 8-bits of passphrase chars on
        // older Android versions (API level 18 and lower) and all available bits
        // on KitKat and newer (API level 19 and higher).
        secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    }

    KeySpec keySpec = new PBEKeySpec(password, salt, iterations, outputKeyLength);

    return secretKeyFactory.generateSecret(keySpec);
}

将加密/解密数据保存到文件的方法:

private void saveData(byte[] newFileData, String filePath) {
    File file = new File(filePath);

    try {
        BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));

        outputStream.write(newFileData);
        outputStream.flush();
        outputStream.close();
    }
    catch (IOException e) {
        e.printStackTrace();
    }
}

文件解密方法:

public void decryptFile(String password, String filePath) {
    byte[] decryptedFileData = null;
    byte[] fileData = null;

    try {
        fileData = readFile(filePath);

        byte[] salt = "goodsalt".getBytes("UTF-8");//generateSalt();
        SecretKey key = generateKey(password.toCharArray(), salt);

        byte[] keyData = key.getEncoded();
        SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, sKeySpec);

        decryptedFileData = cipher.doFinal(fileData);

        saveData(decryptedFileData, filePath);
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

这行代码加密文件:

//simple password for testing only
encryptor.encryptFile("password", "storage/emulated/0/Download/test_file.txt");

这行解密文件:

encryptor.decryptFile("password", "storage/emulated/0/Download/test_file.txt");

编辑:感谢 DarkSquirrel42 和 Oncaphillis。你们真棒!

将这行代码添加到加密和解密函数中解决了我的问题。

//note: the initialization vector (IV) must be 16 bytes in this case
//so, if a user password is being used to create it, measures must
//be taken to ensure proper IV length; random iv is best and should be
//stored, possibly alongside the encrypted data
IvParameterSpec ivSpec = new IvParameterSpec(password.getBytes("UTF-8"));

然后,

cipher.init(Cipher.XXXXXXX_MODE, sKeySpec, ivSpec);

【问题讨论】:

  • 不要使用可预测的 IV,而是生成一个随机的 IV 并添加到密文中。

标签: java android encryption aes


【解决方案1】:

您的问题与密码的操作模式有关 ... cbc 或密码块链接模式

一般来说,CBC 很简单...取之前加密块的输出,然后在加密之前将其异或到当前输入

对于第一个块,我们显然有问题......没有前一个块......因此我们引入了一个叫做 IV 的东西......一个初始化向量......一个随机字节的块长度......

现在...正如您可以想象的那样,当您想要解密时,您将需要相同的 IV...

由于您不保存它,AES 实现每次都会给您一个随机 IV ...

因此,您没有解密块 1 的所有信息......这是 AES 情况下的前 16 个字节......

在处理 CBC 模式数据时,在您的密文输出中简单地预先添加使用过的 IV 总是一个不错的选择...... IV 应该是随机的......这不是秘密......

【讨论】:

    【解决方案2】:

    就像@ÐarkSquirrel42 已经指出 CBC 的 en/decrytion 例程似乎将前 16 个字节解释为初始化向量。这对我有用:

            // got to be random
            byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
            IvParameterSpec ivspec = new IvParameterSpec(iv);
            cipher.init(Cipher.XXXXX_MODE, sKeySpec,ivspec);
    

    【讨论】:

    • 固定的或可预测的 IV 不应与 AES-CBC 一起使用,尤其是在密钥不变的情况下。
    • @ArtjomB。这就是为什么我评论了// got to be random
    • 但每次都必须是随机的,而不仅仅是一次。评论就是这样,cmets。代码应反映您的意图。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-16
    • 1970-01-01
    • 1970-01-01
    • 2012-05-30
    相关资源
    最近更新 更多