node.jsdelphicryptographylockbox-3node-crypto

LockBox / Node Crypto compatibility


I'm trying (and failing) to decipher in Delphi usung LockBox 3 a message that was encrypted using Node.js' crypto library.

node.js code:

var crypto = require('crypto');
var cipher = crypto.createCipher('aes-256-ctr', 'my password');
var crypted = cipher.update('hello world', 'utf8', 'base64');
crypted += cipher.final(output_encoding);
console.log(crypted);

The result from that is

oyC1KRVx3JZBLlI=

Delphi code:

var
  Codec: TCodec;
  CipherText: AnsiString;
begin
  Codec := TCodec.Create(nil);
  try
    Codec.CryptoLibrary := TCryptographicLibrary.Create(Codec);
    //
    Codec.StreamCipherId = 'native.StreamToBlock';
    Codec.BlockCipherId  = 'native.AES-256';
    Codec.ChainModeId    = 'native.CTR';
    //
    Codec.Password := 'my password';
    Codec.DecryptAnsiString(CipherText, 'oyC1KRVx3JZBLlI=');
    //
    Result := string(CipherText);
  finally
    Codec.Free;
  end;
end;

What am I missing?


Solution

  • What is the problem?

    The problem is that both libraries use different keys and initialization vectors (IVs) internally.

    Remember that AES cipher does not work with passwords, but keys and IVs.

    When you provide a password to the cryptographic library it uses some internal mechanism to automatically derive a key and IV.

    This mechanism is not obvious, but is usually described in documentation of cryptographic libraries.

    Documentation of crypto module in node.js says it is using the EVP_BytesToKey function of OpenSSL to derive the key and IV:

    crypto.createCipher(algorithm, password) - Creates and returns a Cipher object that uses the given algorithm and password.

    ...

    The password is used to derive the cipher key and initialization vector (IV). The value must be either a 'binary' encoded string or a [Buffer[].

    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.

    Quote from Node.js v5.6.0 Documentation.

    How to solve the problem?

    The proper solution is to use a cryptographically secure hash algorithm to derive key from the password, and then manually provide the keys and IVs to the cryptographic library, whichever it is.

    A quick and dirty (and highly insecure) solution is to find a Delphi routine that is equivalent to EVP_BytesToKey and just use that to make it work.

    Remember to also check that you are using the same padding scheme. TCodec should allow you to choose PaddingScheme of padPKCS, which should be compatible with the one used by the crypto module in node.js. If it does not work try other options too.


    Another option is to use OpenSSL in Delphi, which should already be compatible with what is used in node.js.


    Also, see this question with similar problem to yours: