简体   繁体   English

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

[英]Encrypt with Node.js Crypto module and decrypt with Java (in Android app)

Looking for a way to encrypt data (mainly strings) in node and decrypt in an android app (java). 寻找一种方法来加密节点中的数据(主要是字符串)并在android应用程序(java)中解密。

Have successfully done so in each one (encrypt/decrypt in node, and encrypt/decrypt in java) but can't seem to get it to work between them. 已经成功地在每一个中完成(在节点中加密/解密,在java中加密/解密)但似乎无法使它们在它们之间工作。

Possibly I'm not encrypting/decrypting in the same way, but each library in each language has different names for same things... 可能我不是以相同的方式加密/解密,但是每种语言中的每个库对于相同的事物都有不同的名称......

Any help appreciated. 任何帮助赞赏。

here's some code: Node.js 这是一些代码: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

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

the raw key is created like this 原始密钥是这样创建的

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

while the encrypted hex string is converted to bytes like this 而加密的十六进制字符串转换为这样的字节

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

Thanks to all of you. 感谢大家。 your answers and comments pointed me in the right direction, and with some more research I managed to get a working prototype (pasted below). 你的回答和评论指出了我正确的方向,并通过一些更多的研究我设法得到一个工作原型(粘贴在下面)。 It turns out that node's crypto uses MD5 to hash the key, and padding is apparently (got that one with trial and error) done using PKCS7Padding 事实证明节点的加密使用MD5来对密钥进行散列,并且使用PKCS7Padding显然填充(使用试验和错误得到填充)

As for the reasons to do it at all in the first place: I have an application comprised of three parts: A. a backend service B. a third party data store C. an android app as a client. 至于首先要做的原因:我有一个由三部分组成的应用程序:A。后端服务B.第三方数据存储C.作为客户端的Android应用程序。

The backend service prepares the data and posts it to the third party. 后端服务准备数据并将其发布给第三方。 The android app gets and/or updates data in the data store, which the service may act upon. Android应用程序获取和/或更新数据存储中的数据,服务可以对其进行操作。

The need for encryption, is keeping the data private, even from the third party provider. 加密的需要是将数据保密,即使是来自第三方提供商也是如此。

As for key management - i guess i can have the server create a new key every preconfigured period of time, encrypt it with the old key and post it to the data store for the client to decrypt and start using, but it's kind of overkill for my needs. 至于密钥管理 - 我想我可以让服务器在每个预先配置的时间段内创建一个新密钥,用旧密钥对其进行加密并将其发布到数据存储区,以便客户端解密并开始使用,但这有点过头了。我的需要。

I can also create a key pair and use that to transfer the new symmetric key every once in a while, but that's even more overkill (not to mention work) 我也可以创建一个密钥对,并使用它来每隔一段时间传输一个新的对称密钥,但这更是矫枉过正(更不用说工作了)

Anywho, this is the code: Encrypt on Node.js 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

Decrypt on Java: 解密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;
}

Apparently if you pass a passphrase to crypto.createCipher() it uses OpenSSL's EVP_BytesToKey() to derive the key. 显然,如果您将密码传递给crypto.createCipher()它会使用OpenSSL的EVP_BytesToKey()来派生密钥。 You can either pass a raw byte buffer and use the same to initialize Java's SecretKey , or emulate EVP_BytesToKey() in your Java code. 您可以传递原始字节缓冲区并使用它来初始化Java的SecretKey ,或者在Java代码中模拟EVP_BytesToKey() Use $ man EVP_BytesToKey for more details, but essentially it hashes the passphrase multiple times with MD5 and concatenates a salt. 使用$ man EVP_BytesToKey获取更多详细信息,但实际上它使用MD5多次散列密码短语并连接一个盐。

As for using a raw key, something like this should let you use a raw key: 至于使用原始密钥,这样的东西应该让你使用原始密钥:

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

Note that since you are using CBC, you need to use the same IV for encryption and decryption (you might want to append it to your message, etc.) 请注意,由于您使用的是CBC,因此您需要使用相同的IV进行加密和解密(您可能希望将其附加到您的消息中等)

Mandatory warning: implementing a crypto protocol yourself is rarely a good idea. 强制警告:自己实施加密协议很少是个好主意。 Even if you get this to work, are you going to use the same key for all messages? 即使你让它工作,你是否会对所有消息使用相同的密钥? For how long? 多长时间? If you decide to rotate the key, how to you manage this. 如果您决定旋转密钥,请如何管理。 Etc, .etc. 等等,等等。

The example from previous answers did not work for me when trying on Java SE since Java 7 complains that "AES/ECB/PKCS7Padding" can not be used. 尝试使用Java SE时,之前答案中的示例对我不起作用,因为Java 7抱怨不能使用“AES / ECB / PKCS7Padding”。

This however worked: 然而这有效:

to encrypt: 加密:

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

to decrypt: 解密:

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

You need to make sure you are using 你需要确保你正在使用

  • the same key 同样的关键
  • the same algorithm, mode of operation and padding. 相同的算法,操作模式和填充。

on both sides of the connection. 在连接的两侧。

For the key , on the Java side you are using quite some work to derive a key from a string - no such thing is done on the node.js side. 对于密钥 ,在Java方面,您正在使用相当多的工作来从字符串派生密钥 - 在node.js端没有这样做。 Use a standard key derivation algorithm here (and the same one on both sides). 在这里使用标准密钥推导算法(两侧都使用相同的算法)。

Looking again, the line 再看,行

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

does indeed some key derivation, just the documentation is silent about what it does exactly : 确实是一些关键的推导,只是文档没有提到它究竟做了什么

crypto.createCipher(algorithm, password) crypto.createCipher(算法,密码)

Creates and returns a cipher object, with the given algorithm and password. 使用给定的算法和密码创建并返回密码对象。

algorithm is dependent on OpenSSL, examples are 'aes192' , etc. On recent releases, openssl list-cipher-algorithms will display the available cipher algorithms. algorithm依赖于OpenSSL,例子是'aes192'等。在最近的版本中, openssl list-cipher-algorithms将显示可用的密码算法。 password is used to derive key and IV, which must be 'binary' encoded string (See the Buffers for more information). password用于派生密钥和IV,它必须是'binary'编码的字符串(有关更多信息,请参阅缓冲区 )。

Okay, this at least says how to encode it, but not what is done here. 好吧,这至少说明了如何编码,但不是这里做的。 So, we either can use the other initialization method crypto.createCipheriv (which takes key and initialization vector directly, and uses them without any modification), or look at the source. 因此,我们要么可以使用另一个初始化方法crypto.createCipheriv (它直接获取密钥和初始化向量,并在不做任何修改的情况下使用它们),或者查看源代码。

createCipher will somehow invoke the C++ function CipherInit in node_crypto.cc. createCipher将以某种方式调用node_crypto.cc中的C ++函数CipherInit。 This uses in essence the EVP_BytesToKey function to derive the key from the provided string (with MD5, empty salt and count 1), and then do the same as CipherInitiv (which is called by createCipheriv , and uses IV and key directly.) 这本质上使用EVP_BytesToKey函数从提供的字符串(使用MD5,空盐和计数1)派生密钥,然后执行与CipherInitiv相同的CipherInitiv (由createCipheriv ,并直接使用IV和密钥。)

As AES uses 128 bits of key and initialization vector and MD5 has 128 bits of output, this in effect means 由于AES使用128位密钥和初始化向量而MD5具有128位输出,因此这实际上意味着

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

(where + denotes concatenation, not addition). (其中+表示连接,而不是加法)。 You can re-implement this key-derivation in Java using the MessageDigest class, if needed. 如果需要,您可以使用MessageDigest类在Java中重新实现此密钥派生。

A better idea would be to use some slow key derivation algorithm, specially if your password is something what a human can memorize. 更好的想法是使用一些慢键推导算法,特别是如果你的密码是人类可以记住的东西。 Then use the pbkdf2 function to generate this key on the node.js side, and PBEKeySpec together with a SecretKeyFactory (with algorithm PBKDF2WithHmacSHA1 ) on the Java side. 然后使用pbkdf2函数在node.js端生成此密钥,并在Java端使用SecretKeyFactory(带有算法PBKDF2WithHmacSHA1PBKDF2WithHmacSHA1 (Choose an iteration count which just does not make your customers complain about slowness on the most common devices.) (选择一个迭代计数,这不会让您的客户抱怨最常见设备的缓慢。)

For your cipher algorithm , on the Java side you are saying "use the AES algorithm with whatever is the default mode of operation and the default padding mode here". 对于您的密码算法 ,在Java方面,您说“使用AES算法,无论是默认的操作模式还是默认的填充模式”。 Don't do this, as it might change from provider to provider. 不要这样做,因为它可能会从提供商变为提供商。

Instead, use explicit indications of the mode of operation ( CBC , in your case), and explicit indication of the padding mode. 相反,使用操作模式的明确指示(在您的情况下为CBC ),以及填充模式的明确指示。 One example might be: 一个例子可能是:

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

Have a look at the node.js documentation to see how to indicate a padding mode there (or which one is the default, to select the same on the Java side). 看一下node.js文档,看看如何在那里指示填充模式(或者哪一个是默认的,在Java端选择相同的)。 (From the OpenSSL EVP documentation , it looks like the default is PKCS5Padding here, too.) (从OpenSSL EVP文档来看,默认情况下也是PKCS5Padding。)

Also, instead of implementing the encryption yourself, consider using TLS for transport encryption. 此外,不要自己实施加密,而应考虑使用TLS进行传输加密。 (Of course, this only works if you have a real-time connection between both sides.) (当然,这只有在双方之间有实时连接时才有效。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM