Emulating Java’s PBEWithMD5AndDES Encryption with .NET

I was recently put on a new project and tasked with emulating Java’s PBEWithMD5AndDES encryption using Microsoft .NET.  It seemed like it might be a fairly daunting task at first, but luckily Bob Janova posted a great article doing this at The Code Project.  His code implements an algorithm by Michel Gallant posted here.  I’ve tweaked his code slightly, but it’s essentially the same thing.

I’m posting this just so there is another reference / resource available on the subject.  I’m not a mathematician and don’t have an in depth knowledge of cryptography, so I’m not going to attempt to explain what is going on.  You can figure most of it out from the code, and the bottom line is that works like a champ.  It’s been tested and confirmed.

DemoEncryption.cs:

namespace Demo.Cryptography
{
    #region

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

    #endregion

    /// <summary>
    ///     This class is used to perform username and password encryption in the Demo java system.
    /// </summary>
    public class DemoEncryption
    {
        /// <summary>
        /// Use this method to encrypt usernames and passwords in the Demo user table.  The password and salt are hardcoded
        ///     into this method.
        /// </summary>
        /// <param name="clearText">
        /// This is the clear text you wish to encrypt.
        /// </param>
        /// <returns>
        /// Returns the encrypted version of the clear text.
        /// </returns>
        public string EncryptUsernamePassword(string clearText)
        {
            // TODO: Parameterize the Password, Salt, and Iterations.  They should be encrypted with the machine key and stored in the registry
            if (string.IsNullOrEmpty(clearText))
            {
                return clearText;
            }

            byte[] salt = { 0xc7, 0x73, 0x21, 0x8c, 0x7e, 0xc8, 0xee, 0x99 };

            // NOTE: The keystring, salt, and iterations must be the same as what is used in the Demo java system.
            PKCSKeyGenerator crypto = new PKCSKeyGenerator("MyPassword", salt, 20, 1);

            ICryptoTransform cryptoTransform = crypto.Encryptor;
            var cipherBytes = cryptoTransform.TransformFinalBlock(Encoding.UTF8.GetBytes(clearText), 0, clearText.Length);
            return Convert.ToBase64String(cipherBytes);
        }

        /// <summary>
        /// Use this method to decrypt usernames and passwords in the Demo user table.  The password and salt are hardcoded
        ///     into this method.
        /// </summary>
        /// <param name="cipherText">
        /// The cipher Text.
        /// </param>
        /// <returns>
        /// Returns the decrypted version of the cipher text.
        /// </returns>
        public string DecryptUsernamePassword(string cipherText)
        {
            if (string.IsNullOrEmpty(cipherText))
            {
                return cipherText;
            }

            byte[] salt = { 0xc7, 0x73, 0x21, 0x8c, 0x7e, 0xc8, 0xee, 0x99 };

            // NOTE: The keystring, salt, and iterations must be the same as what is used in the Demo java system.
            PKCSKeyGenerator crypto = new PKCSKeyGenerator("MyPassword", salt, 20, 1);

            ICryptoTransform cryptoTransform = crypto.Decryptor;
            var cipherBytes = Convert.FromBase64String(cipherText);
            var clearBytes = cryptoTransform.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
            return Encoding.UTF8.GetString(clearBytes);
        }
    }
}

PKCSKeyGenerator.cs:

// *************************************************************************
// <copyright file="DemoEncryption.cs" company="Elegant Software Solutions, LLC">
//     Copyright (C) 2011 Elegant Software Solutions, LLC.  All rights reserved worldwide.
// </copyright>
// *

// PKCSKeyGenerator.cs
// Derive key material using PKCS #1 v1.5 algorithm with MD5 hash
//
// Portions Copyright (C) 2005.  Michel I. Gallant
// Portions copyright 2006 Richard Smith
// Adapted from http://www.jensign.com/JavaScience/dotnet/DeriveKeyM/index.html
//
// *************************************************************************
//
//  DeriveKeyM.cs
//
//  Derive a key from a pswd and Salt using MD5 and PKCS #5 v1.5 approach
//   see also:   http://www.openssl.org/docs/crypto/EVP_BytesToKey.html
//   see also:   http://java.sun.com/j2se/1.5.0/docs/guide/security/jce/JCERefGuide.html#PBE
//
// **************************************************************************

namespace Demo.Cryptography
{
    using System;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;

    /// <summary>
    /// This class is used to emulate the Java based PBEWithMD5AndDES functionality of the Demo system.
    /// </summary>
    public class PKCSKeyGenerator
    {
        /// <summary>
        /// Key used in the encryption algorythm.
        /// </summary>

        private byte[] key = new byte[8];

        /// <summary>
        /// IV used in the encryption algorythm.
        /// </summary>
        private byte[] iv = new byte[8];

        /// <summary>
        /// DES Provider used in the encryption algorythm.
        /// </summary>
        private DESCryptoServiceProvider des = new DESCryptoServiceProvider();

        /// <summary>
        /// Initializes a new instance of the PKCSKeyGenerator class.
        /// </summary>
        public PKCSKeyGenerator()
        {
        }

        /// <summary>
        /// Initializes a new instance of the PKCSKeyGenerator class.
        /// </summary>
        /// <param name="keystring">This is the same as the "password" of the PBEWithMD5AndDES method.</param>
        /// <param name="salt">This is the salt used to provide extra security to the algorythim.</param>
        /// <param name="iterationsMd5">Fill out iterationsMd5 later.</param>
        /// <param name="segments">Fill out segments later.</param>
        public PKCSKeyGenerator(string keystring, byte[] salt, int iterationsMd5, int segments)
        {
            this.Generate(keystring, salt, iterationsMd5, segments);
        }

        /// <summary>
        /// Gets the asymetric Key used in the encryption algorythm.  Note that this is read only and is an empty byte array.
        /// </summary>
        public byte[] Key
        {
            get
            {
                return this.key;
            }
        }

        /// <summary>
        /// Gets the initialization vector used in in the encryption algorythm.  Note that this is read only and is an empty byte array.
        /// </summary>
        public byte[] IV
        {
            get
            {
                return this.iv;
            }
        }

        /// <summary>
        /// Gets an ICryptoTransform interface for encryption
        /// </summary>
        public ICryptoTransform Encryptor
        {
            get
            {
                return this.des.CreateEncryptor(this.key, this.iv);
            }
        }

        /// <summary>
        /// Gets an ICryptoTransform interface for decryption
        /// </summary>
        public ICryptoTransform Decryptor
        {
            get
            {
                return des.CreateDecryptor(key, iv);
            }
        }
        
        /// <summary>
        /// Returns the ICryptoTransform interface used to perform the encryption.
        /// </summary>
        /// <param name="keystring">This is the same as the "password" of the PBEWithMD5AndDES method.</param>
        /// <param name="salt">This is the salt used to provide extra security to the algorythim.</param>
        /// <param name="iterationsMd5">Fill out iterationsMd5 later.</param>
        /// <param name="segments">Fill out segments later.</param>
        /// <returns>ICryptoTransform interface used to perform the encryption.</returns>
        public ICryptoTransform Generate(string keystring, byte[] salt, int iterationsMd5, int segments)
        {
            // MD5 bytes
            int hashLength = 16;

            // to store contatenated Mi hashed results
            byte[] keyMaterial = new byte[hashLength * segments];

            // --- get secret password bytes ----
            byte[] passwordBytes;
            passwordBytes = Encoding.UTF8.GetBytes(keystring);

            // --- contatenate salt and pswd bytes into fixed data array ---
            byte[] data00 = new byte[passwordBytes.Length + salt.Length];

            // copy the pswd bytes
            Array.Copy(passwordBytes, data00, passwordBytes.Length);

            // concatenate the salt bytes
            Array.Copy(salt, 0, data00, passwordBytes.Length, salt.Length);

            // ---- do multi-hashing and contatenate results  D1, D2 ...  into keymaterial bytes ----
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] result = null;

            // fixed length initial hashtarget
            byte[] hashtarget = new byte[hashLength + data00.Length];

            for (int j = 0; j < segments; j++)
            {
                // ----  Now hash consecutively for iterationsMd5 times ------
                if (j == 0)
                {
                    // initialize
                    result = data00;
                }
                else
                {
                    Array.Copy(result, hashtarget, result.Length);
                    Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
                    result = hashtarget;
                }

                for (int i = 0; i < iterationsMd5; i++)
                {
                    result = md5.ComputeHash(result);
                }

                // contatenate to keymaterial
                Array.Copy(result, 0, keyMaterial, j * hashLength, result.Length);
            }

            Array.Copy(keyMaterial, 0, this.key, 0, 8);
            Array.Copy(keyMaterial, 8, this.iv, 0, 8);

            return this.Encryptor;
        }
    }
}

I hope this helps someone!

Happy coding,
Tom Hundley

One response to “Emulating Java’s PBEWithMD5AndDES Encryption with .NET

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s