Шифрование с помощью модуля Node.js Crypto и расшифровка с помощью Java (в приложении для Android)

Ищете способ шифрования данных (в основном, строк) в узле и дешифрования в приложении 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 

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

Исходный ключ создается таким образом

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

В то время как зашифрованная шестнадцатеричная строка преобразуется в байты как это

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

Solutions Collecting From Web of "Шифрование с помощью модуля Node.js Crypto и расшифровка с помощью Java (в приложении для Android)"

По-видимому, если вы передаете кодовую фразу crypto.createCipher() она использует EVP_BytesToKey() OpenSSL для вывода ключа. Вы можете передать необработанный буфер байта и использовать его для инициализации EVP_BytesToKey() Java или эмулировать EVP_BytesToKey() в вашем Java-коде. $ man EVP_BytesToKey получения дополнительной информации используйте $ man EVP_BytesToKey , но по существу он несколько раз хэширует кодовую фразу с помощью MD5 и объединяет соль.

Что касается использования необработанного ключа, что-то вроде этого должно позволить вам использовать необработанный ключ:

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

Обратите внимание, что, поскольку вы используете CBC, вам нужно использовать тот же IV для шифрования и дешифрования (вы можете добавить его к своему сообщению и т. Д.),

Обязательное предупреждение: реализация криптового протокола сама по себе редко является хорошей идеей. Даже если вы его заработаете, собираетесь ли вы использовать один и тот же ключ для всех сообщений? Как долго? Если вы решите повернуть ключ, как вам это сделать. Etc, &et;

Всем спасибо. Ваши ответы и комментарии указали мне в правильном направлении, и с некоторыми дополнительными исследованиями мне удалось получить рабочий прототип (вставленный ниже). Оказывается, криптография узла использует MD5 для хеширования ключа, и заполнение, по-видимому, (полученное с пробной версией и ошибкой) выполняется с использованием PKCS7Padding

Что касается причин сделать это вообще в первую очередь: у меня есть приложение, состоящее из трех частей: A. бэкэнд-сервис B. стороннее хранилище данных C. приложение android в качестве клиента.

Бэкэнд-сервис готовит данные и отправляет их третьему лицу. Приложение android получает и / или обновляет данные в хранилище данных, на которые служба может действовать.

Необходимость шифрования заключается в том, что данные конфиденциальны, даже от стороннего поставщика.

Что касается управления ключами – я думаю, я могу заставить сервер создать новый ключ за каждый предварительно сконфигурированный период времени, зашифровать его старым ключом и отправить его в хранилище данных, чтобы клиент расшифровывал и начинал использовать, но это своего рода излишний мои потребности.

Я также могу создать пару ключей и использовать их для переноса нового симметричного ключа каждый раз в то время, но это еще более переборщило (не говоря уже о работе)

Anywho, это код: Encrypt on 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 

Расшифровка на 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 вы используете довольно определенную работу для получения ключа из строки – на стороне node.js такая вещь не выполняется. Здесь используется стандартный алгоритм вывода ключей (и тот же самый с обеих сторон).

Глядя снова, линия

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

Действительно, некоторые ключевые выводы, только документация умалчивает о том, что именно она делает :

Crypto.createCipher (алгоритм, пароль)

Создает и возвращает объект шифрования с заданным алгоритмом и паролем.

algorithm зависит от OpenSSL, примеры – 'aes192' и т. Д. В последних выпусках openssl list-cipher-algorithms будут отображать доступные алгоритмы шифрования. password используется для вывода ключа и IV, который должен быть 'binary' кодированной строкой (см. Буферы для получения дополнительной информации).

Хорошо, это, по крайней мере, говорит, как его кодировать, но не то, что здесь делается. Таким образом, мы либо можем использовать другой метод инициализации crypto.createCipheriv (который берет ключ и инициализационный вектор напрямую, и использует их без каких-либо изменений), либо посмотрите на источник.

createCipher каким-то образом вызовет Cipher- функцию C ++ в node_crypto.cc. Это использует по существу функцию EVP_BytesToKey для получения ключа из предоставленной строки (с MD5, пустой солью и счетчиком 1), а затем выполняет то же самое, что и CipherInitiv (которое вызывается createCipheriv и напрямую использует IV и ключ).

Поскольку AES использует 128 бит ключа и вектора инициализации, а MD5 имеет 128 бит вывода, это фактически означает

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

(Где + обозначает конкатенацию, а не добавление). Вы можете повторно реализовать этот ключ-деривацию в Java, используя класс MessageDigest, если это необходимо.

Лучше всего было бы использовать алгоритм медленного генерации ключей, особенно если ваш пароль – это то, что человек может запомнить. Затем используйте функцию pbkdf2 для генерации этого ключа на стороне node.js и PBEKeySpec вместе с SecretKeyFactory (с алгоритмом PBKDF2WithHmacSHA1 ) на стороне Java. (Выберите счетчик итераций, который просто не заставит ваших клиентов жаловаться на медлительность на наиболее распространенных устройствах.)

Для вашего алгоритма шифрования на стороне Java вы говорите: «Используйте алгоритм AES с любым режимом работы по умолчанию и режимом заполнения по умолчанию здесь». Не делайте этого, поскольку это может измениться от поставщика к провайдеру.

Вместо этого используйте явные указания о режиме работы ( CBC в вашем случае) и явную индикацию режима заполнения. Одним из примеров может быть:

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

Посмотрите документацию node.js, чтобы увидеть, как указывать там режим заполнения (или какой из них по умолчанию, чтобы выбрать то же самое на стороне Java). (Из документации OpenSSL EVP , похоже, по умолчанию здесь также находится PKCS5Padding.)

Кроме того, вместо того, чтобы самостоятельно внедрять шифрование, рассмотрите возможность использования TLS для транспортного шифрования. (Конечно, это работает только в том случае, если у вас есть связь в реальном времени между обеими сторонами.)

Пример из предыдущих ответов не работал для меня при попытке 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 

Для расшифровки:

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