使用Node.jsencryption模块进行encryption并使用Java解密(在Android应用程序中)

寻找一种方法来encryption节点中的数据(主要是string),并在android应用程序(java)中解密。

已经成功地在每一个(在节点中encryption/解密,并在Javaencryption/解密),但似乎无法让它在它们之间工作。

也许我不用相同的方式进行encryption/解密,但是每种语言中的每个库对于相同的东西都有不同的名称。

任何帮助赞赏。

这里有一些代码: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 

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

原始密钥是这样创build的

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

而encryption的hexstring转换为像这样的字节

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

显然,如果你传递一个密码给crypto.createCipher()它使用OpenSSL的EVP_BytesToKey()来派生密钥。 您可以传递一个原始字节缓冲区,并使用它来初始化Java的SecretKey ,或者在您的Java代码中模拟EVP_BytesToKey() 。 使用$ man EVP_BytesToKey获取更多细节,但实质上它使用MD5对密码短语进行多次散列并连接一个salt。

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

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

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

强制性警告:自己实施encryption协议很less是一个好主意。 即使你得到这个工作,你是否会使用相同的密钥的所有消息? 多长时间? 如果你决定旋转钥匙,如何pipe理这个。 等等。

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

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

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

对encryption的需求是保持数据私密性,甚至是来自第三方提供商。

至于密钥pipe理 – 我想我可以让服务器在每个预先configuration的时间段内创build一个新的密钥,使用旧的密钥对其进行encryption,并将其发布到数据存储以供客户端解密并开始使用,但这对于处理我的需求。

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

Anywho,这是代码:encryptionNode.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 

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

从以前的答案的例子不适合我在尝试Java SE时,因为Java 7抱怨“AES / ECB / PKCS7Padding”不能使用。

然而这工作:

encryption:

 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 

解密:

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

你需要确保你正在使用

  • 同样的钥匙
  • 相同的algorithm,操作模式和填充。

在连接的两边。

对于关键 ,在Java方面,您正在使用相当多的工作来从string派生一个密钥 – 在node.js端没有这样的事情。 这里使用一个标准的密钥导出algorithm(和两边都是同一个algorithm)。

再看一眼,就行了

 var cipher = crypto.createCipher('aes-128-cbc','somepass') 

确实有一些关键的推导,只是文档没有说明它究竟做了什么 :

crypto.createCipher(algorithm,密码)

用给定的algorithm和密码创build并返回一个密码对象。

algorithm依赖于OpenSSL,例如'aes192'等。在最近的版本中, openssl list-cipher-algorithms将显示可用的密码algorithm。 password用于派生密钥和IV,它必须是'binary'编码的string(请参阅缓冲区了解更多信息)。

好的,这至less说明了如何对它进行编码,而不是在这里做什么。 因此,我们可以使用其他初始化方法crypto.createCipheriv (它直接获取密钥和初始化向量,并且不加任何修改地使用它们),或者查看源代码。

createCipher将以某种方式调用node_crypto.cc中的C ++函数CipherInit。 这本质上使用了EVP_BytesToKey函数来从提供的string(使用MD5,空盐和计数1)派生密钥,然后执行与CipherInitiv (由createCipheriv ,并直接使用IV和密钥)相同的操作。

由于AES使用128位密钥和初始化vector,而MD5有128位输出,所以这实际上意味着

 key = MD5(password) iv = MD5(key + password) 

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

一个更好的主意是使用一些慢密钥推导algorithm,特别是如果你的密码是人类可以记住的东西。 然后使用pbkdf2函数在node.js端生成此密钥,在Java端使用PBKDF2WithHmacSHA1和SecretKeyFactory(使用algorithmPBKDF2WithHmacSHA1 )。 (select一个不会让客户抱怨最常见设备缓慢的迭代计数。)

对于你的密码algorithm ,在Java方面,你说的是“使用AESalgorithm,无论是默认的操作模式还是默认的填充模式”。 不要这样做,因为它可能会从提供者变为提供者。

相反,使用操作模式( CBC ,在你的情况下)的明确指示,并明确指示填充模式。 一个例子可能是:

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 

查看node.js文档,看看如何在那里指定填充模式(或者哪一个是默认的,在Java端select相同的模式)。 (从OpenSSL EVP文档看来,这里也是默认的PKCS5Padding。)

另外,不要自己实施encryption,而应考虑使用TLS进行传输encryption。 (当然,这只有在双方实时连接的情况下才有效。)