使用Node.js加密模块加密并使用Java解密(在Android应用程序中)

Shh*_*Shh 14 java android cryptography node.js

寻找一种方法来加密节点中的数据(主要是字符串)并在android应用程序(java)中解密.

已成功地在每个中完成(在节点中加密/解密,在java中加密/解密)但似乎无法使它们在它们之间工作.

可能我不是以相同的方式加密/解密,但是每种语言中的每个库对于相同的事物都有不同的名称......

任何帮助赞赏.

这是一些代码:Node.js

var crypto = require('crypto')
var cipher = crypto.createCipher('aes-128-cbc','somepass')
var text = "uncle had a little farm"
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex')
//now crypted contains the hex representation of the ciphertext
Run Code Online (Sandbox Code Playgroud)

和java

private static String decrypt(byte[] raw, byte[] encrypted) throws Exception {
    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, skeySpec );
    byte[] decrypted = cipher.doFinal(encrypted);
    return new String(decrypted);
}
Run Code Online (Sandbox Code Playgroud)

原始密钥是这样创建的

private static byte[] getRawKey(String seed) throws Exception {
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
    byte[] seedBytes = seed.getBytes()
    sr.setSeed(seedBytes);
    kgen.init(128, sr); // 192 and 256 bits may not be available
    SecretKey skey = kgen.generateKey();
    byte[] raw = skey.getEncoded();
    return raw;
}
Run Code Online (Sandbox Code Playgroud)

而加密的十六进制字符串转换为这样的字节

public static byte[] toByte(String hexString) {
    int len = hexString.length()/2;
    byte[] result = new byte[len];
    for (int i = 0; i < len; i++)
        result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
    return result;
}
Run Code Online (Sandbox Code Playgroud)

Shh*_*Shh 11

感谢大家.你的回答和评论指出了我正确的方向,并通过一些更多的研究我设法得到一个工作原型(粘贴在下面).事实证明节点的加密使用MD5来对密钥进行散列,并且使用PKCS7Padding显然填充(使用试验和错误得到填充)

至于首先要做的原因:我有一个由三部分组成的应用程序:A.后端服务B.第三方数据存储C.作为客户端的Android应用程序.

后端服务准备数据并将其发布给第三方.Android应用程序获取和/或更新数据存储中的数据,服务可以对其进行操作.

加密的需要是将数据保密,即使是来自第三方提供商也是如此.

至于密钥管理 - 我想我可以让服务器在每个预先配置的时间段内创建一个新密钥,用旧密钥对其进行加密并将其发布到数据存储区,以便客户端解密并开始使用,但这有点过头了.我的需要.

我也可以创建一个密钥对,并使用它来每隔一段时间传输一个新的对称密钥,但这更是矫枉过正(更不用说工作了)

Anywho,这是代码:在Node.js上加密

var crypto = require('crypto')
var cipher = crypto.createCipher('aes-128-ecb','somepassword')
var text = "the big brown fox jumped over the fence"
var crypted = cipher.update(text,'utf-8','hex')
crypted += cipher.final('hex')
//now crypted contains the hex representation of the ciphertext
Run Code Online (Sandbox Code Playgroud)

解密Java:

public static String decrypt(String seed, String encrypted) throws Exception {
  byte[] keyb = seed.getBytes("UTF-8");
  MessageDigest md = MessageDigest.getInstance("MD5");
  byte[] thedigest = md.digest(keyb);
  SecretKeySpec skey = new SecretKeySpec(thedigest, "AES/ECB/PKCS7Padding");
  Cipher dcipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
  dcipher.init(Cipher.DECRYPT_MODE, skey);

  byte[] clearbyte = dcipher.doFinal(toByte(encrypted));
  return new String(clearbyte);
}

public static byte[] toByte(String hexString) {
  int len = hexString.length()/2;
  byte[] result = new byte[len];
  for (int i = 0; i < len; i++)
    result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
  return result;
}
Run Code Online (Sandbox Code Playgroud)


Nik*_*kov 8

显然,如果您传递密码短语,crypto.createCipher()则使用OpenSSL EVP_BytesToKey()来获取密钥.您可以传递原始字节缓冲区并使用它来初始化Java SecretKey,也可以EVP_BytesToKey()在Java代码中进行模拟.使用$ man EVP_BytesToKey更多的细节,但它本质上的密码多次使用MD5哈希并连接盐.

至于使用原始密钥,这样的东西应该让你使用原始密钥:

var c = crypto.createCipheriv("aes-128-ecb", new Buffer("00010203050607080a0b0c0d0f101112", "hex").toString("binary"), "");

请注意,由于您使用的是CBC,因此您需要使用相同的IV进行加密和解密(您可能希望将其附加到您的消息中等)

强制警告:自己实施加密协议很少是个好主意.即使你让它工作,你是否会对所有消息使用相同的密钥?多长时间?如果您决定旋转密钥,请如何管理.等等,等等.


Paŭ*_*ann 5

您需要确保正在使用

  • 相同的钥匙
  • 相同的算法,操作模式和填充。

在连接的两侧。

对于key,在Java方面,您正在使用大量工作从字符串派生密钥-在node.js方面没有做任何事情。在此使用标准的密钥派生算法(并且两侧使用相同的密钥)。

再看一遍

var cipher = crypto.createCipher('aes-128-cbc','somepass')
Run Code Online (Sandbox Code Playgroud)

确实确实进行了一些关键的派生,只是文档对它的确切作用保持沉默

crypto.createCipher(算法,密码)

使用给定的算法和密码创建并返回一个密码对象。

algorithm取决于OpenSSL,示例为'aes192',等等。在最新版本中,openssl list-cipher-algorithms 将显示可用的密码算法。password用于导出密钥和IV,后者必须是已'binary'编码的字符串(有关更多信息,请参见缓冲区)。

好的,这至少说明了如何对其进行编码,但没有说明在此做什么。因此,我们可以使用其他初始化方法crypto.createCipheriv(直接获取键和初始化向量,而无需进行任何修改即可使用它们),或者查看源代码。

createCipher将以某种方式调用node_crypto.cc中的C ++函数CipherInit。本质上,这使用EVP_BytesToKey函数从提供的字符串(带有MD5,空盐和1)中导出密钥,然后执行与CipherInitiv(由调用createCipheriv,并直接使用IV和密钥)相同的功能。

由于AES使用128位密钥和初始化向量,而MD5具有128位输出,因此实际上意味着

key = MD5(password)
iv = MD5(key + password)
Run Code Online (Sandbox Code Playgroud)

(其中+表示串联,而不是加法)。如果需要,可以使用MessageDigest类在Java中重新实现此密钥派生。

一个更好的主意是使用一些慢速密钥派生算法,特别是如果您的密码可以被人们记住的话。然后,使用pbkdf2函数在node.js端生成此密钥,并PBKDF2WithHmacSHA1在Java端使用PBEKeySpec和SecretKeyFactory(带有algorithm )一起生成。(选择一个迭代计数就不会让您的客户抱怨最常见的设备运行缓慢。)

对于密码算法,在Java方面,您说的是“使用AES算法以及此处的默认操作模式和默认填充模式”。不要这样做,因为它可能会因提供程序而异。

而是使用操作模式的显式指示(CBC在您的情况下为),以及填充模式的显式指示。一个示例可能是:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Run Code Online (Sandbox Code Playgroud)

请查看node.js文档,以了解如何在此处指示填充模式(或在Java端选择填充模式的默认模式)。(从OpenSSL EVP文档中,这里看起来也默认为PKCS5Padding。)

另外,可以考虑使用TLS进行传输加密,而不是自己实施加密。(当然,这仅在双方之间存在实时连接时才有效。)


小智 5

尝试使用Java SE时,之前答案的示例对我不起作用,因为Java 7抱怨不能使用"AES/ECB/PKCS7Padding".

然而这有效:

加密:

var crypto = require('crypto')
var cipher = crypto.createCipher('aes-128-ecb','somepassword')
var text = "the big brown fox jumped over the fence"
var crypted = cipher.update(text,'utf-8','hex')
crypted += cipher.final('hex')
//now crypted contains the hex representation of the ciphertext
Run Code Online (Sandbox Code Playgroud)

解密:

private static String decrypt(String seed, String encrypted) throws Exception {
    byte[] keyb = seed.getBytes("UTF-8");
    MessageDigest md = MessageDigest.getInstance("MD5");
    byte[] thedigest = md.digest(keyb);
    SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
    Cipher dcipher = Cipher.getInstance("AES");
    dcipher.init(Cipher.DECRYPT_MODE, skey);

    byte[] clearbyte = dcipher.doFinal(toByte(encrypted));
    return new String(clearbyte);
}

private static byte[] toByte(String hexString) {
    int len = hexString.length()/2;
    byte[] result = new byte[len];
    for (int i = 0; i < len; i++) {
        result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)