Search code examples
javascriptnode.jsencryptiondes

Why did I always get wrong des-ecb result in nodejs native cipher?


Here is my code:

const crypto = require('crypto')
let enterJS = 'h';
let enterStr = null;
enterStr = encrypt(enterJS, 'des-ecb').toUpperCase();
console.log("===============>>>> ENTER STR : " + enterStr);
function encrypt(plaintext, algorithm) {
    var keyStr = "imtestKey";
    var key = new Buffer(keyStr);
    var cipher = crypto.createCipher(algorithm, key);
    cipher.setAutoPadding(true);
    var ciph = cipher.update(plaintext, 'ascii');
    var ciphf = cipher.final();
    return ciph.toString('hex') + ciphf.toString('hex');
}

But the result I got is:

===============>>>> ENTER STR : 16CE7F2DEB9BB56D

which the right result I test on this web: http://tool.chacuo.net/cryptdes

des-mode:ecb

fill-mode:pkcs7padding

password:imtestKey

output:hex

The right result (the same with my java code) is

832e52ebd3fb9059

My node version is v8.9.0, how can I get the right result?

This is my java code:

import java.lang.StringBuilder;
import javax.crypto.Cipher;
import java.security.SecureRandom;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey;

public class Test {
    public static void main(String[] args) {
        String js = "h";
        try {
            byte[] bs = encrypt(js.getBytes(), "imtestKey".getBytes());
            System.out.println(byte2hex(bs));
        } catch(Exception ex) {

        }
    }

    public static byte[] encrypt(byte[] src, byte[] key) throws Exception {
        SecureRandom sr = new SecureRandom();
        DESKeySpec dks = new DESKeySpec(key);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey securekey = keyFactory.generateSecret(dks);
        Cipher cipher = Cipher.getInstance("DES");
        cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
        return cipher.doFinal(src);
    }


    public static String byte2hex(byte[] b) {
        StringBuilder sb = new StringBuilder();
        String stmp = "";

        for(int n = 0; b != null && n < b.length; ++n) {
            stmp = Integer.toHexString(b[n] & 255);
            if (stmp.length() == 1) {
                sb.append("0").append(stmp);
            } else {
                sb.append(stmp);
            }
        }

        return sb.toString().toUpperCase();
    }
}

Solution

  • Security aspects aside (as has been pointed out, DES and ECB, as well as no key derivation is insecure), you are using a deprecated crypto.createCipher() function which derives a key from the provided password.

    The implementation of crypto.createCipher() derives keys using the OpenSSL function EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt. The lack of salt allows dictionary attacks as the same password always creates the same key. The low iteration count and non-cryptographically secure hash algorithm allow passwords to be tested very rapidly.

    Use crypto.createCipheriv() instead, which uses the provided key as-is:

    const crypto = require('crypto')
    let enterJS = 'h';
    let enterStr = null;
    function encrypt(plaintext, algorithm) {
        var keyStr = "imtestKey";
        var key = Buffer.alloc(8, keyStr);
        var cipher = crypto.createCipheriv(algorithm, key, Buffer.alloc(0));
        cipher.setAutoPadding(true);
        var ciph = cipher.update(Buffer.from(plaintext));
        var ciphf = cipher.final();
        return Buffer.concat([ciph, ciphf]).toString('hex');
    }
    enterStr = encrypt(enterJS, 'des-ecb').toUpperCase();
    console.log("===============>>>> ENTER STR : " + enterStr);
    

    The createCipheriv API will reject your 9-byte long key, because DES requires an 8-byte key. I made a workaround to take the first 8 bytes from the provided password as the key and now it's printing your desired result.

    Output:

    ===============>>>> ENTER STR : 832E52EBD3FB9059