简体   繁体   English

NodeJS 3DES ECB 加密不等于 C# 加密

[英]NodeJS 3DES ECB encryption does not equal C# encryption

I'm trying to convert the C# code to encrypt text using 3DES ECB (You can copy and paste it on https://dotnetfiddle.net/ to run it)我正在尝试使用 3DES ECB 将 C# 代码转换为加密文本(您可以将其复制并粘贴到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));
    }
}

OUTPUT:输出:

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

So I rewrite the above code in NodeJS to use crypto所以我在NodeJS中重写了上面的代码来使用crypto

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

OUTPUT:输出:

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

I think the problem lies in C# and NodeJS Crypto approach in using 3DES ECB.我认为问题在于使用 3DES ECB 的 C# 和 NodeJS 加密方法。 Any idea how to replicate the C# code behaviour in NodeJS?知道如何在 NodeJS 中复制 C# 代码行为吗?

Triple DES is only defined for 192 bit keys.三重 DES 仅针对 192 位密钥定义。 An MD5 hash only provides 128 bit. MD5 哈希仅提供 128 位。 There are multiple ways of expanding a potential 128 bit key into a 192 bit key.有多种方法可以将潜在的 128 位密钥扩展为 192 位密钥。 If we assume that the 128 bit key is made up of two 64 bit sub keys k1 and k2 , then C# will create a 192 bit key consisting of k1 , k2 and k1 again.如果我们假设 128 位密钥由两个 64 位子密钥k1k2 组成,那么 C# 将再次创建一个由k1k2k1组成的 192 位密钥。

Here is the code that works:这是有效的代码:

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

The other issue that you had was using crypto#createCipher instead of crypto#createCipheriv .您遇到的另一个问题是使用crypto#createCipher而不是crypto#createCipheriv The former has an additional hashing of the "key" which you don't want in this case.前者有一个额外的“密钥”散列,在这种情况下你不需要。


Other potential problems:其他潜在问题:

  • Never use ECB mode .永远不要使用ECB 模式 It's deterministic and therefore not semantically secure.它是确定性的,因此在语义上不安全。 You should at the very least use a randomized mode like CBC or CTR .您至少应该使用像CBCCTR这样的随机模式。 It is better to authenticate your ciphertexts so that attacks like a padding oracle attack are not possible.最好验证您的密文,以便像填充预言机攻击这样的攻击是不可能的。 This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC scheme.这可以通过 GCM 或 EAX 等身份验证模式或使用encrypt-then-MAC方案来完成。

  • Don't use Triple DES nowadays.现在不要使用三重 DES。 It only provides at best 112 bit of security even if you use the largest key size of 192 bit.即使您使用最大的 192 位密钥,它也最多只能提供 112 位的安全性。 If a shorter key size is used, then it only provides 56 or 57 bits of security.如果使用较短的密钥大小,则它仅提供 56 或 57 位的安全性。 AES would be faster (processors have a special AES-NI instruction set) and even more secure with the lowest key size of 128 bit. AES 会更快(处理器有一个特殊的 AES-NI 指令集),并且使用 128 位的最低密钥大小甚至更安全。 There is also a practical limit on the maximum ciphertext size with 3DES. 3DES 的最大密文大小也有实际限制。 See Security comparison of 3DES and AES .请参阅3DES 和 AES 的安全性比较

  • You should never use a simple hash function to protect your user's passwords.永远不要使用简单的哈希函数来保护用户的密码。 You need to use a strong hashing scheme like PBKDF2, bcrypt, scrypt and Argon2.您需要使用强大的散列方案,如 PBKDF2、bcrypt、scrypt 和 Argon2。 Be sure to use a high cost factor/iteration count.确保使用高成本因子/迭代次数。 It is common to choose the cost so that a single iteration takes at least 100ms.通常选择成本使得单次迭代至少需要 100 毫秒。 See more: How to securely hash passwords?查看更多:如何安全地散列密码?

Ok, just use https://www.npmjs.com/package/nod3des to replicate the same behavior as C#.好的,只需使用https://www.npmjs.com/package/nod3des来复制与 C# 相同的行为。 In case you're wondering how it works如果你想知道它是如何工作的

https://github.com/4y0/nod3des/blob/master/index.js#L30 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()) );

}

I had a different requirement (CBC) and I thought of adding it here in case it helps anyone looking for another solution.我有一个不同的要求(CBC),我想在这里添加它,以防万一它有助于寻找其他解决方案的人。 The code is below, but if needs more details on the context, check this gist: gist代码如下,但如果需要有关上下文的更多详细信息,请检查此要点: gist

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

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

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