javascriptnode.jsvb.netencryptionrijndael

Rijndael encryption VB to Javascript


I have a funny task to do for one of my client... I am suppose to use the account of one of this app on the app I am building for him. The previous app is writing in VB and use the following function for encrypt data for the password :

Public Function Encrypt(ByVal plainText As String) As String
   Dim passPhrase As String = "minePassPhrase"
   Dim saltValue As String = "mySaltValue"
   Dim hashAlgorithm As String = "SHA1"
   Dim passwordIterations As Integer = 2
   Dim initVector As String = "@1B2c3D4e5F6g7H8"
   Dim keySize As Integer = 256
   Dim initVectorBytes As Byte() = Encoding.ASCII.GetBytes(initVector)
   Dim saltValueBytes As Byte() = Encoding.ASCII.GetBytes(saltValue)
   Dim plainTextBytes As Byte() = Encoding.UTF8.GetBytes(plainText)
   Dim password As New PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations)
   Dim keyBytes As Byte() = password.GetBytes(keySize \ 8)
   Dim symmetricKey As New RijndaelManaged()
   symmetricKey.Mode = CipherMode.CBC
   Dim encryptor As ICryptoTransform = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes)
   Dim memoryStream As New MemoryStream()
   Dim cryptoStream As New CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)
   cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length)
   cryptoStream.FlushFinalBlock()
   Dim cipherTextBytes As Byte() = memoryStream.ToArray()
   memoryStream.Close()
   cryptoStream.Close()
   Dim cipherText As String = Convert.ToBase64String(cipherTextBytes)
   Return cipherText
End Function

I am not a pro with node cipher but after some research, I arrive at this :

const crypto = require('crypto');

const encrypt = function (data) {

    const passPhrase = "minePassPhrase";
    const hashAlgorithm = "SHA1";
    const passwordIterations = 2;
    const keySize = 256;

    const initVector = "@1B2c3D4e5F6g7H8";
    const saltValue = "mySaltValue";

    const initVectorBytes = Buffer.from(initVector.substring(0, 32),"binary");
    const saltValueBytes = Buffer.from(saltValue.substring(0, 32),"binary");

    const derivedBytes = crypto.pbkdf2Sync(passPhrase, saltValueBytes, passwordIterations, keySize, hashAlgorithm);
    const key = derivedBytes.slice(0, Math.floor( keySize / 8 ));

    const cipher = crypto.createCipheriv('aes-256-cbc', key, initVectorBytes);
    const output = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);

    return output.toString('base64');
}

For helping me, the client give me a string encode with the code from the VB encryption. With the VB : "Password" => "IMY4Jo6BkjDPYXR6DK+rhw=="

If I am trying with my javascript function : "Password" => "dkofR4Us7O8+rdeIRsg78w=="

I am apparently missing something for getting the same result as the VB function. But after few days of research, I am loosing my mind on it alone. Do you have any idea ?


Solution

  • The problem is caused by different key derivation functions. In the VB code PasswordDeriveBytes is used, which is based on PBKDF1. PBKDF1 is defined in RFC 2898, section 5.1. The length of the generated key corresponds to the length of the used digest, i.e. 20 bytes for SHA1. MS has extended the algorithm so that longer keys can also be generated, e.g. 32 bytes as in the posted code. However, this means that the MS implementation no longer corresponds to the standard, so that a counterpart is not always found across platforms. Fortunately this is different in the case of NodeJS, here there is an implementation, see e.g. jheys / derive-password-bytes .

    The following node implementation reproduces the result of the VB code:

    const derp = require('derive-password-bytes');
    const crypto = require('crypto');
    
    const encrypt = function (data) {
    
        const passPhrase = "minePassPhrase";
        const hashAlgorithm = "SHA1";
        const passwordIterations = 2;
        const keySize = 256;
        const saltValue = "mySaltValue";
        const key = derp(passPhrase, saltValue, passwordIterations, hashAlgorithm, 32);
    
        const initVector = "@1B2c3D4e5F6g7H8";
        const initVectorBytes = Buffer.from(initVector, "binary");
        
        const cipher = crypto.createCipheriv('aes-256-cbc', key, initVectorBytes);
        const output = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);
    
        return output.toString('base64');
    }
    
    const plaintext = "Password"
    var encrypted = encrypt(plaintext);
    
    console.log(encrypted);
    

    In new codes, PBKDF2 should be used instead of PBKDF1 (or PasswordDeriveBytes). PBKDF2 is also defined in RFC 2898, section 5.2. Here MS provides an implementation with Rfc2898DeriveBytes that conforms to the standard. In the posted NodeJS code PBKDF2 is used, which is the more secure implementation, but just not compatible with PBKDF1 used in the (presumably legacy) VB code. A detailed and enlightening discussion about both algorithms can be found in this MS Blog from 04/2004.