【问题标题】:Encryption and Decryption between Java and Javascript won't workJava和Javascript之间的加密和解密不起作用
【发布时间】:2018-05-20 21:00:03
【问题描述】:

编辑 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 cmets部分给出的link部分解决了这个问题。它确实加密和解密跨平台,但由于 PBKDF2 和 SHA256 散列,它相当慢。我找不到只使用 AES 部分而不使用 PKBDF2 部分的方法。


原文

我对 Java 和 Javascript 版本使用相同的密钥和 IV。我无法解密用 Java 加密的 Javascript 文件,也无法解密用 Javascript 加密的 Java 文件。我需要这两者相互兼容,但我不知道如何 我正在尝试用 Javascript 解密以前用 Java 加密的文件。我已经成功实现了两者之间的解密和加密文本,但是当我想解密一个用 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 内容转换为字符串,将数据作为 ArrayBuffer 检索并将其转换为字符串,将其作为文本接收并将其传递给解密方法,但似乎没有任何效果。

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

我发现了其他类似的 (1,2) 问题。但是,我认为它们差异太大,因为这些问题的答案与这个问题无关,而是一个小错误。

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

【问题讨论】:

  • 相同的加密-解密代码是否适用于简单文本而不是文件?你可以试试 Hello World 之类的。
  • 它适用于文本
  • 老实说,如果您“找不到只使用 AES 部分而不使用 PKBDF2 部分的方法”,那么您不应该编写加密代码。至少不是作为学习练习以外的任何东西,永远不会在生产中使用。 Crypto 根本不是一个领域,您可以在其中获取一些您并不真正理解的代码并对其进行破解,直到它似乎起作用。如果您尝试,您最终可能会得到一个看起来乍一看还可以,但洞大到足以让 NSA 监视车通过的东西。

标签: javascript java encryption cryptography aes


【解决方案1】:

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

【讨论】:

  • 那是文本的解密方法,一个有效。一个不能正常工作的是decryptFile方法,它使用了Latin1
  • 没关系。您不能将随机二进制数据提供给文本编码器,并希望数据恰好采用正确的格式。它不是那样工作的。二进制数据实际上和语义上不是字符串。答案仍然适用。
  • 问题仍然存在,如何更改 Javascript 中的方法以便解密。即使我将它从 Latin1 更改为无编码或其他任何方式,它也不起作用
  • “它不会工作”对问题的描述不是很有帮助。使用 从输出中删除与字符编码有关的任何内容后观察到的行为更新问题中的代码。
  • 您的代码 still 末尾有.toString()...我真的不知道如何更清楚这一点。您不能将二进制数据转换为字符串...
【解决方案2】:

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

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

如果代码对简单文本不起作用,那么您可以尝试在javascript和java中单独加密一个简单文本,并检查结果是否相同。如果不是,说明java和javascript的加解密逻辑有些不匹配。

编辑:

正如您提到的,代码适用于 String,我在下面展示了一个使用 java 中的 apache 通用编解码器库将文件转换为 Base64 String 的示例。

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);
}

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

例如。

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

}   

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

【讨论】:

  • 那你有没有尝试我的建议?
【解决方案3】:

cmets 部分给出的link 部分解决了这个问题。它确实加密和解密跨平台,但由于 PBKDF2 和 SHA256 散列,它相当慢。我找不到只使用 AES 部分而不使用 PKBDF2 部分的方法。

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

要做到这一点,它需要慢一点;使猜测具有 30 位熵的密码与猜测具有 128 位熵的 AES 密钥一样困难的唯一方法是使将密码转换为密钥的过程花费 2 128 - 30 = 298 AES 加密。当然,这实际上是不可能的,所以在实践中人们倾向于只应用几千(& 210)到几十亿(& 230)PBKDF2迭代和hope that it's enough。如果您的迭代计数更接近范围的上限,如果用户足够聪明并且有足够的动力选择一个相当好的密码(例如,随机的 Diceware 密码短语而不是 abc123 或 @ 987654339@) 开始,如果你的对手不是 NSA。

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

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

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


附言。您可能会注意到,我实际上并没有包含任何代码来展示如何使用上面的 PBKDF2 进行纯 AES 加密。我没有这样做有两个原因:

  1. 如果您对正在使用的加密原语不够了解,例如无法将密钥派生与加密分开,那么您真的不应该编写加密代码(除了玩具练习之外的任何东西)无论如何。

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

    对于其他类型的代码,似乎在大多数情况下都可以正常工作的东西通常已经足够好了,您可以在出现任何剩余的错误时修复它们。使用加密,有缺陷的代码很容易变得完全不安全,而 看起来 看起来它运行良好,而且你不会发现它已经被破坏,直到有人破坏它并窃取你努力保密的所有数据.或者可能只是在很久之后已经发生了。

  2. 无论如何,the collection of code samples you linked to我没有详细审查过已经包含一组采用二进制 AES 密钥并执行 使用 PBKDF2。具体来说,这些是 encrypt()decrypt() 方法(与使用 PBKDF2 的 encryptString()decryptString() 相反)。

    请注意,在调用encrypt() 和/或decrypt() 时,您需要以实现预期的格式提供AES 密钥。一般来说,这可能取决于代码使用的底层加密库。例如,The Java implementation 似乎需要一个 128 / 8 = 16 元素 byte[] 数组。 (如果它也可以直接采用SecretKeySpec 对象,那就太好了,但事实并非如此。)JS WebCrypto implementation 似乎想要一个 16 字节的 Uint8Array 来代替。 apparently 也适用于 node.js implementation,尽管它也可以接受 Buffer


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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-30
    • 2018-02-04
    • 2016-04-22
    • 1970-01-01
    • 2021-12-07
    • 2014-04-13
    • 2014-07-12
    • 1970-01-01
    相关资源
    最近更新 更多