【问题标题】:AES-256-CTR Encryption in node JS and decryption in JavaAES-256-CTR 节点 JS 中的加密和 Java 中的解密
【发布时间】:2018-02-16 15:22:33
【问题描述】:

我正在尝试在 nodejs 中进行编码,并且在 nodejs 中进行相同的解密效果很好。但是,当我尝试使用相同的 IV 和秘密在 Java 中进行解密时,它的行为并不符合预期。

这里是sn-p的代码:

nodeJs 中的加密:

   var crypto = require('crypto'),
   algorithm = 'aes-256-ctr',
   _ = require('lodash'),
   secret = 'd6F3231q7d1942874322a@123nab@392';

  function encrypt(text, secret) {
    var iv = crypto.randomBytes(16);
    console.log(iv);
    var cipher = crypto.createCipheriv(algorithm, new Buffer(secret), 
    iv);
    var encrypted = cipher.update(text);

    encrypted = Buffer.concat([encrypted, cipher.final()]);

    return iv.toString('hex') + ':' + encrypted.toString('hex');
}
var encrypted = encrypt("8123497494", secret);
console.log(encrypted);

输出是:

<Buffer 94 fa a4 f4 a1 3c bf f6 d7 90 18 3f 3b db 3f b9>
94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e

在 JAVA 中用于解密的代码片段:

public class Test {
    
    public static void main(String[] args) throws Exception {
        String s = 
   "94faa4f4a13cbff6d790183f3bdb3fb9:fae8b07a135e084eb91e";
        String seed = "d6F3231q7d1942874322a@123nab@392";

        decrypt(s, seed);
    }

    private static void decrypt(String s, String seed)
            throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException,
            InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        String parts[] = s.split(":");
        String ivString = parts[0];
        String encodedString = parts[1];
        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

        byte[] secretBytes = seed.getBytes("UTF-8");
        
        IvParameterSpec ivSpec = new IvParameterSpec(hexStringToByteArray(ivString));
        
        /*Removed after the accepted answer
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] thedigest = md.digest(secretBytes);*/ 
        
        SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
        
        cipher.init(Cipher.DECRYPT_MODE, skey, ivSpec);
        byte[] output = cipher.doFinal(hexStringToByteArray(encodedString));

        System.out.println(new String(output));
    }
}

输出:�s˸8ƍ�

我在响应中得到了一些垃圾值。尝试了很多选项,但似乎没有一个有效。任何线索/帮助表示赞赏。

【问题讨论】:

  • 嗯...您正在输出用 NodeJS 编码的 IV 和密文十六进制。您正在用 Java 打印原始输出。为什么您期望它们是相同的?我觉得您没有编写此代码,您只是从某个地方复制并粘贴它而没有尝试理解它。
  • @LukeJoshuaPark:Java 代码用于解密;它应该产生与 JS 加密代码的 input 相同的输出,这是一个纯(非十六进制)文本字符串。无论如何,我已经验证了 JS 代码的输出可以使用openssl enc 命令行工具解密(这并不奇怪,因为 node.js 加密模块是基于 OpenSSL 的),所以错误(和/或标准不匹配)必须在 Java 端。
  • @ManojBhardwaj:这个问题及其答案已有两年半的历史了。它们的副本已经传播到许多 SO 镜像站点和档案馆,您永远不会将它们全部更改。关键是要在网络上留下来,而你试图编辑它所做的只是吸引更多的注意力。我建议您尽快更改项目中的密钥。

标签: java node.js security encryption cryptography


【解决方案1】:

在您的 JS 代码中,您直接使用 32 个字符的字符串 d6F3231q7d19428743234@123nab@234 作为 AES 密钥,每个 ASCII 字符直接映射到单个密钥字节。

在 Java 代码中,您首先使用 MD5 散列相同的字符串,然后使用 MD5 输出作为 AES 密钥。难怪他们不会匹配。

在这两种情况下,您可能应该做的是:

  1. 随机生成一个32字节的字符串(大部分不会是可打印的ASCII字符)并作为key;或
  2. 使用 key derivation function (KDF) 获取任意输入字符串并将其转换为伪随机 AES 密钥。

在后一种情况下,如果输入字符串的entropy 可能少于 256 位(例如,如果它是用户选择的密码,其中大多数最多只有几十位熵),那么您应该确保使用实现 key stretching 的 KDF 来减缓暴力猜测攻击。


附言。为了解决下面的 cmets,MD5 输出一个 16 字节的摘要,当用作 AES SecretKeySpec 时将产生一个 AES-128 密钥。要在 Java 中使用 AES-256,您需要提供一个 32 字节的密钥。如果trying to use a 32-byte AES key in Java throws an InvalidKeyException,您可能使用的是旧版本的 Java,其加密策略有限,不允许超过 128 位的加密密钥。如this answer to the linked question 所述,您需要升级到 Java 8 更新 161 或更高版本,或者获取并安装适用于您的 Java 版本的无限制加密策略文件。

【讨论】:

  • 实际情况下的密钥应该是客户端特定的密钥,用于对其数据中的许多字段进行加密。因此,我无法为每个单独的加密更改它。另外,如果我不使用 MD5 散列相同的字符串,我会在线程“main”java.security.InvalidKeyException: Illegal key size 中得到 InvalidKey Exception Exception
  • @AshishPandey:见stackoverflow.com/questions/3862800/…。显然,您可能需要升级 Java 或安装新的加密策略以启用 AES-256 加密/解密。 MD5 输出一个 128 位(= 16 字节)的摘要,当用作 AES SecretKeySpec 时将创建一个 AES-128 密钥。
  • 在此之后,将版本更新为更新 161,并按照建议更新设置中的 crypto.policy=unlimited,使解密工作正常。非常感谢:)
【解决方案2】:

在 Java 代码中,您在使用 secret 的 MD5 哈希作为密钥之前:

MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(secretBytes);
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");

然而,在您的 NodeJS 代码中,您不会在任何地方这样做。因此,您在加密和解密时使用了两个不同的密钥。

不要在不了解代码的情况下复制和粘贴代码。尤其是加密代码。

【讨论】:

  • 在节点中,要使用该算法,它明确要求密钥长度必须为 32 字节。在 Java 中,解密时,它告诉我密钥大小是非法的。你能检查一下,我在这里想念什么吗?我使用了摘要,这样它就不会引发错误。
【解决方案3】:

面对同样的任务(不过128,适应256很容易),这里是用cmets工作的Java/NodeJs代码。

为了便于阅读,它还采用 Base64 封装,但如果您愿意,也可以轻松删除。 Java 端(加密/解密):

import java.lang.Math; // headers MUST be above the first class
import java.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;

// one class needs to have a main() method
public class MyClass
{
    private static void log(String s)
    {
        System.out.print("\r\n"+s);
    }

    public static SecureRandom IVGenerator() {
       return new SecureRandom();
    }

  // arguments are passed using the text field below this editor
  public static void  main(String[] args)
  {
    String valueToEncrypt = "hello, stackoverflow!";
    String key = "3e$C!F)H@McQfTjK";

    String encrypted = "";
    String decrypted = "";

    //ENCODE part
    SecureRandom IVGenerator = IVGenerator();
    byte[] encryptionKeyRaw = key.getBytes();
    //aes-128=16bit IV block size
    int ivLength=16;
    byte[] iv = new byte[ivLength];
    //generate random vector
    IVGenerator.nextBytes(iv);

    try {
        Cipher encryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
        encryptionCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKeyRaw, "AES"), new IvParameterSpec(iv));
        //encrypt
        byte[] cipherText = encryptionCipher.doFinal(valueToEncrypt.getBytes());

        ByteBuffer byteBuffer = ByteBuffer.allocate(ivLength + cipherText.length);
        //storing IV in first part of whole message
        byteBuffer.put(iv);
        //store encrypted bytes
        byteBuffer.put(cipherText);
        //concat it to result message
        byte[] cipherMessage = byteBuffer.array();
        //and encrypt to base64 to get readable value
        encrypted = new String(Base64.getEncoder().encode(cipherMessage));
    } catch (Exception e) {
        throw new IllegalStateException(e);
    }
    //END OF ENCODE CODE
    log("encrypted and saved as Base64 : "+encrypted);

    ///DECRYPT CODE : 
    try {
        //decoding from base64
        byte[] cipherMessageArr = Base64.getDecoder().decode(encrypted);
        //retrieving IV from message
        iv = Arrays.copyOfRange(cipherMessageArr, 0, ivLength);
        //retrieving encrypted value from end of message
        byte[] cipherText = Arrays.copyOfRange(cipherMessageArr, ivLength, cipherMessageArr.length);
        Cipher decryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKeyRaw, "AES");
        decryptionCipher.init(Cipher.DECRYPT_MODE,secretKeySpec , ivSpec);
        //decrypt
        byte[] finalCipherText = decryptionCipher.doFinal(cipherText);
        //converting to string
        String finalDecryptedValue = new String(finalCipherText);
        decrypted = finalDecryptedValue;
    } catch (Exception e) {
        throw new IllegalStateException(e);
    }
    log("decrypted from Base64->aes128 : "+decrypted);
    //END OF DECRYPT CODE

  }
}

它可以很容易地通过在线 java 编译器进行测试(此示例在 https://www.jdoodle.com/online-java-compiler 上编写)。

NodeJs 解密端:

const crypto = require('crypto');
const ivLength = 16;
const algorithm = 'aes-128-ctr';

const encrypt = (value, key) => {
    //not implemented, but it could be done easy if you will see to decrypt
    return value;
};

function decrypt(value, key) {
    //from base64 to byteArray
    let decodedAsBase64Value = Buffer.from(value, 'base64');        
    let decodedAsBase64Key = Buffer.from(key);
    //get IV from message
    let ivArr = decodedAsBase64Value.slice(0, ivLength);
    //get crypted message from second part of message
    let cipherTextArr = decodedAsBase64Value.slice(ivLength, decodedAsBase64Value.length);
    let cipher = crypto.createDecipheriv(algorithm, decodedAsBase64Key, ivArr);
    //decrypted value
    let decrypted = cipher.update(cipherTextArr, 'binary', 'utf8');
    decrypted += cipher.final('utf8');
    return decrypted;
}

【讨论】:

    猜你喜欢
    • 2020-01-20
    • 2021-09-30
    • 2022-10-16
    • 2014-08-05
    • 2022-12-12
    • 1970-01-01
    • 2022-08-08
    • 1970-01-01
    • 2015-06-09
    相关资源
    最近更新 更多