Шифрование NodeJS 3DES ECB не равно шифрованию C#

Я пытаюсь преобразовать код C# для шифрования текста с помощью 3DES ECB (его можно скопировать и вставить на https://dotnetfiddle.net/ для его запуска)

using System;
using System.Configuration;
using System.Security.Cryptography;
using System.Text;

public class Program
{
    public static void Main()
    {
        string toEncrypt = "testtext";
        string key = "testkey";
        bool useHashing = true;
        byte[] keyArray;
        byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt);

        System.Configuration.AppSettingsReader settingsReader =
                                                new AppSettingsReader();

        key = string.IsNullOrEmpty(key) ? (string)settingsReader.GetValue("SecurityKey", typeof(String)) : key;

        if (useHashing)
        {
            MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
            keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));

            hashmd5.Clear();
        }
        else 
        {
            keyArray = UTF8Encoding.UTF8.GetBytes(key);
        }

        TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
        key = Convert.ToBase64String(keyArray, 0, keyArray.Length);
        Console.WriteLine(key);
        tdes.Key = keyArray;
        tdes.Mode = CipherMode.ECB;
        tdes.Padding = PaddingMode.PKCS7;

        ICryptoTransform cTransform = tdes.CreateEncryptor();
        byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

        tdes.Clear();

        Console.Write(Convert.ToBase64String(resultArray, 0, resultArray.Length));
    }
}

ВЫХОД:

Ihs2jX9fWXhn9SWXHyj/dQ== <- md5 secret key
wHL9J7vhm9LZI2W5DQJGKw== <- encrypt result

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

const crypto = require('crypto');
const md5 = text => {
  return crypto
    .createHash('md5')
    .update(text)
    .digest('base64');
}

const encrypt = (text, secretKey) => {
  secretKey = md5(secretKey);
  console.log(secretKey);

  const cipher = crypto.createCipher('des-ede3', secretKey);
  const encrypted = cipher.update(text, 'utf8', 'base64');

  return encrypted + cipher.final('base64');
};
const encrypted = encrypt('testtext', 'testkey');

console.log(encrypted);

ВЫХОД:

Ihs2jX9fWXhn9SWXHyj/dQ== <- md5 secret key
VNa9fDYgPus5IMhUZRI+jQ== <- encrypt result

Я думаю, что проблема заключается в подходе C# и NodeJS Crypto к использованию 3DES ECB. Есть идеи, как воспроизвести поведение кода C# в NodeJS?


person sendy halim    schedule 23.02.2017    source источник


Ответы (3)


Тройной DES определен только для 192-битных ключей. Хэш MD5 обеспечивает только 128 бит. Существует несколько способов расширения потенциального 128-битного ключа до 192-битного ключа. Если предположить, что 128-битный ключ состоит из двух 64-битных вложенных ключей k1 и k2, то C# создаст 192-битный ключ, состоящий из k1< /em>, k2 и снова k1.

Вот код, который работает:

const crypto = require('crypto');
const md5 = text => {
  return crypto
    .createHash('md5')
    .update(text)
    .digest();
}

const encrypt = (text, secretKey) => {
  secretKey = md5(secretKey);
  console.log(secretKey.toString('base64'));
  secretKey = Buffer.concat([secretKey, secretKey.slice(0, 8)]); // properly expand 3DES key from 128 bit to 192 bit

  const cipher = crypto.createCipheriv('des-ede3', secretKey, '');
  const encrypted = cipher.update(text, 'utf8', 'base64');

  return encrypted + cipher.final('base64');
};
const encrypted = encrypt('testtext', 'testkey');

console.log(encrypted);

Другая проблема, с которой вы столкнулись, заключалась в использовании crypto#createCipher вместо crypto#createCipheriv. Первый имеет дополнительное хеширование «ключа», которое вам не нужно в этом случае.


Другие возможные проблемы:

  • Никогда не используйте режим ECB. Это детерминировано и, следовательно, не является семантически безопасным. Вы должны как минимум использовать рандомизированный режим, такой как CBC или CTR. Лучше аутентифицировать ваши зашифрованные тексты, чтобы такие атаки, как атака оракула заполнения, были невозможны. Это можно сделать с помощью режимов аутентификации, таких как GCM или EAX, или с помощью схемы зашифровать-затем-MAC.

  • В настоящее время не используйте Triple DES. Он обеспечивает в лучшем случае 112-битную безопасность, даже если вы используете самый большой размер ключа — 192-битный. Если используется более короткий размер ключа, то он обеспечивает только 56 или 57 бит безопасности. AES будет быстрее (процессоры имеют специальный набор инструкций AES-NI) и даже более безопасным с наименьшим размером ключа 128 бит. Существует также практическое ограничение на максимальный размер зашифрованного текста с 3DES. См. Сравнение безопасности 3DES и AES.

  • Вы никогда не должны использовать простую хеш-функцию для защиты паролей ваших пользователей. Вам нужно использовать надежную схему хеширования, такую ​​как PBKDF2, bcrypt, scrypt и Argon2. Обязательно используйте высокий коэффициент стоимости/количество итераций. Обычно стоимость выбирают так, чтобы одна итерация занимала не менее 100 мс. Подробнее: Как безопасно хэшировать пароли?

person Artjom B.    schedule 23.02.2017
comment
Привет, спасибо за ответ, да, что касается режима ECB, это требование моего делового партнера-работодателя. Я сказал им использовать AES, но они предпочитают использовать ECB по причине, которую я не могу здесь указать. - person sendy halim; 24.02.2017
comment
как теперь расшифровать? - person Jay seen; 26.11.2019
comment
@JaikeySarraf В основном тот же код, просто изменение createCipheriv на createDecipheriv, но, как я уже сказал, это будет работать только в режиме ECB без IV, что довольно небезопасно. - person Artjom B.; 27.11.2019
comment
правильно, мы меняемся, но мы не хотим сообщать нашему клиенту об изменении секретных данных или повторном вводе, поэтому сначала мы расшифровываем все, а затем будем использовать PKBDF2, спасибо - person Jay seen; 27.11.2019

Хорошо, просто используйте https://www.npmjs.com/package/nod3des для репликации то же поведение, что и C#. Если вам интересно, как это работает

https://github.com/4y0/nod3des/blob/master/index.js#L30

var CryptoJS = require('crypto-js');
var forge    = require('node-forge');
var utf8     = require('utf8');

...

_3DES.encrypt = function (key, text){

    key          = CryptoJS.MD5(utf8.encode(key)).toString(CryptoJS.enc.Latin1);
    key          = key + key.substring(0, 8); 
    var cipher   = forge.cipher.createCipher('3DES-ECB', forge.util.createBuffer(key));
    cipher.start({iv:''});
    cipher.update(forge.util.createBuffer(text, 'utf-8'));
    cipher.finish();
    var encrypted = cipher.output; 
    return ( forge.util.encode64(encrypted.getBytes()) );

}
person sendy halim    schedule 23.02.2017
comment
Итак, вы смогли выполнить свою задачу, но этот пост ответит на вопрос, который вы на самом деле задали? - person AakashM; 23.02.2017
comment
@AakashM да, это так, но я не могу принять свой ответ в течение 2 дней с даты публикации. Я думаю, что вопрос в том, как воспроизвести поведение C # в NodeJS, а не в том, как преобразовать код C # в NodeJS, я обновлю вопрос. - person sendy halim; 23.02.2017
comment
Пожалуйста, также опубликуйте функцию расшифровки. - person S.M_Emamian; 22.11.2018

У меня было другое требование (CBC), и я подумал добавить его сюда на случай, если это поможет любому, кто ищет другое решение. Код приведен ниже, но если вам нужны дополнительные сведения о контексте, проверьте это:

import * as crypto from 'crypto';

/**
 * This class is an implementation to encrypt/decrypt 3DES encrypted from .NET
 */
export class TripleDESCryptoHelper {
  // Encryption algorithm
  private static readonly algorithm = 'des-ede-cbc';
  /**
   * Decrypts a value encrypted using 3DES Algorithm.
   *
   * @param encryptionKey Key used for encryption
   * @param encryptedValue Value to be decrypted
   * @returns string containing the value (ascii)
   */
  static decrypt(encryptionKey: string, encryptedValue: string): string {
    const keyHash = crypto
      .createHash('md5')
      .update(encryptionKey)
      .digest();
    const iv = keyHash.slice(0, 8);

    const encrypted = Buffer.from(encryptedValue, 'base64');
    const decipher = crypto.createDecipheriv(TripleDESCryptoHelper.algorithm, keyHash, iv);
    const decoded = decipher.update(encrypted, undefined, 'ascii') + decipher.final('ascii');

    return decoded;
  }

  /**
   * Encrypts a value using 3DES Algorithm.
   *
   * @param encryptionKey Key used for encryption
   * @param encryptedText The text to be encrypted
   */
  static encrypt(encryptionKey: string, encryptedText: string): string {
    const keyHash = crypto
      .createHash('md5')
      .update(encryptionKey)
      .digest();
    const iv = keyHash.slice(0, 8);

    const encrypted = Buffer.from(encryptedText);

    const cipher = crypto.createCipheriv(TripleDESCryptoHelper.algorithm, keyHash, iv);
    const encoded = Buffer.concat([cipher.update(encrypted), cipher.final()]);
    const encodedAsBase64 = encoded.toString('base64');

    return encodedAsBase64;
  }
}
person Tzn    schedule 26.01.2020