android cipher不解密加密数据的前16个字节/字符

时间:2014-11-15 20:14:25

标签: java android encryption aes

我正在处理文件加密/解密应用。我正在使用一个简单的.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);

2 个答案:

答案 0 :(得分:7)

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

一般来说,CBC很简单......无论你以前的加密模块的输出是什么,在加密之前将xor加到当前输入上

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

现在......你可以想象,当你想解密时,你需要相同的IV ......

因为你不能保存,所以AES实现每次都会给你一个随机的IV ......

因此,你没有解密第1块的所有信息...这是AES的前16个字节......

当处理CBC模式数据时,它总是一个很好的选择,只需在您的cypertext输出中添加使用过的IV ...... IV应该是随机的......这不是秘密......

答案 1 :(得分:2)

像@ÐarkSquirrel42已经指出CBC的en / decryption例程似乎将前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);