Java和Javascript之间的加密和解密不起作用

时间:2018-05-20 21:00:04

标签: javascript java encryption cryptography aes

编辑1

在decryptFile方法中,解密部分不会输出任何内容..

let decrypted = CryptoJS.AES.decrypt(e.target.result, CryptoJS.enc.Utf8.parse(key), {
    iv: CryptoJS.enc.Utf8.parse(iv),
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
});


编辑2 注释部分中给出的link部分解决了问题。它确实对跨平台进行加密和解密,但由于PBKDF2具有SHA256散列,它相当慢。我找不到只使用AES部分而不是PKBDF2部分的方法。


原文

我对Java和Javascript版本使用相同的密钥和IV。我无法解密用Java加密的Javascript中的文件,也无法解密用Javascript加密的Java文件。我需要这两个与彼此兼容,但我无法弄清楚如何 我试图解密以前用Java加密的Javascript中的文件。我已经成功地在两者之间实现了解密和加密文本,但是当我想要解密一个用Java加密的文件时,它只是无法工作。

用Java加密/解密文件:

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Test {
    private SecretKey secretKey;
    private IvParameterSpec ivParameterSpec;

    private String key = "ThisIsMyGreatKey";
    private byte[] ivKey = "ABCDEFGHabcdefgh".getBytes();

    public static void main(String[] args) {
        try {
            new Test().run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void run() {
        ivParameterSpec = new IvParameterSpec(ivKey);
        secretKey = new SecretKeySpec(key.getBytes(), "AES");
        encryptOrDecryptFile(Cipher.ENCRYPT_MODE,
            new File("src/cactus.jpg"), new File("src/cactus-encrypted.jpg"));
    }

    private void encryptOrDecryptFile(int mode, File inputFile, File outputFile) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(mode, secretKey, ivParameterSpec);

            // Read input
            byte[] input = new byte[(int) inputFile.length()];
            FileInputStream inputStream = new FileInputStream(inputFile);
            inputStream.read(input);

            // Encrypt and write to output
            byte[] output = cipher.doFinal(input);
            FileOutputStream outputStream = new FileOutputStream(outputFile);
            outputStream.write(output);

            inputStream.close();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在Javascript中加密/解密

<input type="file" id="file-input" onchange="handleFile(this)">
<button onclick="useEncryptionForFile()" id="encrypt-file">Encrypt File</button>
<button onclick="useDecryptionForFile()" id="decrypt-file">Decrypt File</button>
<textarea id="output"></textarea>
<img id="example">

<script>
    let key = "ThisIsMyGreatKey";
    let iv = "ABCDEFGHabcdefgh";
    let useEncryption, useDecryption;

    let input = document.getElementById("file-input");
    let output = document.getElementById("output");
    let example = document.getElementById("example");

    function handleFile(element) {
        if (element.files && element.files[0]) {
            let file = element.files[0];
            if (useDecryption) {
                decryptFile(file);
            } else {
                encryptFile(file);
            }
        }
    }

    function encryptFile(file) {
        let reader = new FileReader();

        reader.onload = function (e) {
            let encrypted = CryptoJS.AES.encrypt(e.target.result, CryptoJS.enc.Utf8.parse(key), {
                iv: CryptoJS.enc.Utf8.parse(iv),
                mode: CryptoJS.mode.CBC,
                padding: CryptoJS.pad.Pkcs7
            });

            output.textContent = encrypted;

            let a = document.createElement("a");
            a.setAttribute('href', 'data:application/octet-stream,' + encrypted);
            a.setAttribute('download', file.name + '.encrypted');
            a.click();
        };

        reader.readAsDataURL(file);
    }

    function decryptFile(file) {
        let reader = new FileReader();
        reader.onload = function (e) {
            let decrypted = CryptoJS.AES.decrypt(e.target.result, CryptoJS.enc.Utf8.parse(key), {
                iv: CryptoJS.enc.Utf8.parse(iv),
                mode: CryptoJS.mode.CBC,
                padding: CryptoJS.pad.Pkcs7
            });

             // Decrypted is emtpy    
            output.textContent = decrypted;

            // Desperate try to get something working
            example.src = "data:image/png;base64," + btoa(decrypted);

            let a = document.createElement("a");
            a.setAttribute('href', decrypted);
            a.setAttribute('download', file.name.replace('encrypted', 'decrypted'));
            a.click();
        };

        reader.readAsText(file);
    }

    function useEncryptionForFile() {
        document.getElementById("encrypt-file").style.backgroundColor = "#757575";
        document.getElementById("decrypt-file").style.backgroundColor = "#FFFFFF";
        useEncryption = true;
      useDecryption = false;
    }

    function useDecryptionForFile() {
        document.getElementById("encrypt-file").style.backgroundColor = "#FFFFFF";
        document.getElementById("decrypt-file").style.backgroundColor = "#757575";
        useDecryption = true;
      useEncryption = false;
    }
</script>    

我还提出了Fiddle,以防你更喜欢:),Java源代码可以是downloaded here

在Java源代码中,我使用cactus.jpg作为文件,但是可以使用任何文件:)。可以找到仙人掌here

如何解密用Java加密的文件?我已经尝试将blob内容转换为String,将数据作为ArrayBuffer检索并将其转换为String,将其作为文本接收并将其传递给解密方法,但似乎没有任何效果。

我在Javascript中使用的库是CryptoJS,在Java中是标准的Crypto库。

我发现了其他类似的(12)问题。但是,我认为它们差别太大,因为这些问题的答案并不涉及这个问题,而是一个小错误。

如果我忘记了任何数据,请告诉我。

3 个答案:

答案 0 :(得分:5)

问题是您将解密结果解释为UTF8字符串。这不是它的工作原理。文件只是任意字节,它们不一定构成UTF8字符串。如果您不尝试将其解释为UTF8,则解密的结果是正确的。

答案 1 :(得分:2)

首先,尝试将简单的加密文本从java发送到javascript,反之亦然,并测试代码是否正常工作。

如果代码适用于简单文本,即,您可以从Java发送加密的字符串并使用JavaScript成功解密,反之亦然,那么您可以做的是Base64编码加密的字节/文件,然后转移文本,然后在另一端解码和解密。

如果代码不适用于简单文本,那么您可以尝试独立加密javascript和java中的简单文本,并检查结果是否相同。如果没有,则java和javascript之间的加密/解密逻辑存在一些不匹配。

编辑:

正如您所提到的,代码适用于String,我在下面显示了一个示例,使用Java中的apache公共编解码器库将文件转换为Base64字符串。

private static String encodeFileToBase64Binary(String fileName) throws IOException {
    File file = new File(fileName);
    byte[] encoded = Base64.encodeBase64(FileUtils.readFileToByteArray(file));
    return new String(encoded, StandardCharsets.US_ASCII);
}

现在您加密此String并将其发送到javascript。在javascript中首先解密String然后将其转换为文件对象。

例如

 function base64toFile(encodedstring,filename,mimeType){
   return new File([encodedstring.arrayBuffer()],filename, {type:mimeType});

}   

//Usage example:
base64toFile('aGVsbG8gd29ybGQ=', 'hello.txt', 'text/plain');

答案 2 :(得分:2)

  

评论部分中给出的link部分解决了问题。它确实对跨平台进行加密和解密,但由于PBKDF2具有SHA256散列,它相当慢。我找不到只使用AES部分而不是PKBDF2部分的方法。

PBKDF2的目的是将用户选择的密码(通常是可变长度的文本字符串,很少有几十个effective entropy *)转换为AES key(必须是精确128,192或256位的二进制字符串,具体取决于所使用的AES变体,并且如果您希望密码具有与其一样强的密码,则应该有效地具有完整的熵** )。

要做到这一点,它需要变慢;使用例如30比特的熵猜测密码的唯一方法就像猜测具有128位熵的AES密钥一样困难,这使得将密码转换为密钥的过程花费的时间与2 相同。 128 - 30 = 2 98 AES加密。当然,这是不可能的,所以在实践中人们倾向于只应用几千(≈2 10 )到几十亿(≈2 30 )PBKDF2迭代和hope that it's enough。如果你的迭代计数更接近范围的上端,如果用户足够聪明并且有足够的动力来选择一个相当不错的密码(例如,随机Diceware密码而不是{{1或者abc123)开头,如果你的对手不是国家安全局。

无论如何,重点是if you want to use password-based encryption,那么你几乎必须使用PBKDF2(或something similar)。但是, not 的含义必然意味着每次加密或解密时都必须运行慢键派生函数。事实上,如果您知道您需要使用相同的密码加密或解密多个文件,那么从密码中获取AES密钥一次,然后将AES密钥存储在内存中会更有效率只要你需要它。 (安全地进行is not trivial, either,但是如果你使用内置的密钥对象存储密钥,许多加密库至少可以很好地处理它,并且在任何情况下它都不可能是最薄弱的环节在您的应用程序的安全性。)

当然,另一个选择是根本不使用密码,而只是生成(伪)随机AES密钥(使用加密安全随机位串生成器)并存储在需要访问它的所有设备上。当然,这里的难点在于安全地存储密钥。

特别是,如果您使用浏览器内的JavaScript在客户端进行加密,那么只需将密钥嵌入JS代码(或页面上的任何其他位置),就会将其公开给任何有权访问的人。浏览器开发控制台(也可能导致密钥留在例如浏览器的磁盘缓存中)。当然你应该使用HTTPS,否则任何人都可以使用窃听客户端的公共WiFi连接也可以获取密钥的副本。

聚苯乙烯。您可能会注意到我实际上并未包含任何显示如何使用PBKDF2进行普通AES加密的代码。我没有做到这一点有两个原因:

  1. 如果您不了解加密原语,那么您可以将密钥派生与加密分开,那么您真的不应该编写加密代码(无论如何,除了玩具运动之外)。

    这可能听起来很苛刻,但这是现实 - 与许多其他编程子领域不同,你可以只拿一段你不完全理解的代码并调整它直到它起作用,加密(以及一般的安全相关代码)你需要知道自己在做什么,并在第一时间做好。

    对于其他类型的代码,似乎在大多数情况下工作通常都足够好,并且您可以修复任何剩余的错误。使用加密,错误的代码很容易完全不安全,而看起来就像它完美地工作一样,你不会发现它被打破直到有人破坏并抢断你工作的所有数据都很难保密。或者可能只有之后的已经发生过。

  2. 在任何情况下,the collection of code samples you linked to(&lt;免责声明&gt;以及我未详细审核过&lt; / disclaimer&gt;)已经包含一组采用二进制的方法AES密钥和使用PBKDF2。具体来说,这些是pa$$w0rdencrypt()方法(与decrypt()encryptString()相对,它们使用PBKDF2)。

    请注意,在调用decryptString()和/或encrypt()时,您需要以实现所需的格式提供AES密钥。通常,这可能取决于代码使用的基础加密库。例如,The Java implementation似乎期待128/8 = 16个元素decrypt()数组。 (如果它也可以直接使用byte[]对象,那将会很好,但它不会。)JS WebCrypto implementation似乎需要一个16字节Uint8Array。对于apparently来说node.js implementation也很好,虽然它也可以接受Buffer

  3. *)也就是说,一个体面的密码破解程序,基于对常见的人类密码选择方法和习惯的深入了解,很少会花费超过几十亿(≈2 30 )或万亿(≈2 40 )试图猜测大多数人为选择的密码。事实上,根据用户的懒惰程度或缺乏经验,即使只测试几千most common passwords也可能非常有效。

    **)也就是说,猜测密钥不应该比猜测一个完全随机选择的相同长度的位串更容易。