Search code examples
c#des

Why is only one half of my encrypted string not decrypting properly?


Can't seem to figure this one out... I am using DESCryptoServiceProvider to do a quick little two way encryption (not security related, and security is not the purpose of this question).

Anyways it's weird because the string that goes in and then comes back out is only decrypting properly for one half of the string. I can't seem to notice the bug so maybe someone will have some fun with this...

enter image description here

I am combining the two strings with a colon as the separator so 'abc12345:xyz56789' is the input. Then notice in the output only the first part of the string is getting screwed up, not the second part. I would expect that if I was doing it totally wrong then the whole thing wouldn't decrypt properly.

Here is all the code:

class Program
{
    static void Main(string[] args)
    {
        var userId = "abc12345";
        var appId = "xyz56789";

        Console.WriteLine($"UserId: {userId}, AppId: {appId}");

        var code = QuickEncode(userId, appId);

        Console.WriteLine(code);

        var result = QuickDecode(code);

        var uId = result.Item1;
        var aId = result.Item2;

        Console.WriteLine($"UserId: {uId}, AppId: {aId}");

        Console.ReadKey();
    }

    private static string QuickEncode(string userId, string appId)
    {
        DESCryptoServiceProvider des = new DESCryptoServiceProvider();

        var desKey = StringToByteArray("437459133faf42cb");

        des.Key = desKey;

        ICryptoTransform encryptor = des.CreateEncryptor();

        var encryptMe = $"{userId}:{appId}";

        Console.WriteLine($"Input String: {encryptMe}");

        byte[] stringBytes = System.Text.Encoding.UTF8.GetBytes(encryptMe);

        byte[] enc = encryptor.TransformFinalBlock(stringBytes, 0, stringBytes.Length);

        var encryptedBytesString = Convert.ToBase64String(enc);

        return encryptedBytesString;
    }

    private static Tuple<string, string> QuickDecode(string code)
    {
        DESCryptoServiceProvider des = new DESCryptoServiceProvider();

        var desKey = StringToByteArray("437459133faf42cb");

        des.Key = desKey;

        ICryptoTransform decryptor = des.CreateDecryptor();

        var codeBytes = Convert.FromBase64String(code);

        byte[] originalAgain = decryptor.TransformFinalBlock(codeBytes, 0, codeBytes.Length);

        var decryptMe = System.Text.Encoding.UTF8.GetString(originalAgain);

        Console.WriteLine($"Output String: {decryptMe}");

        var ids = decryptMe.Split(':');

        return new Tuple<string, string>(ids[0], ids[1]);
    }

    public static string ByteArrayToString(byte[] ba)
    {
        StringBuilder hex = new StringBuilder(ba.Length * 2);
        foreach (byte b in ba)
            hex.AppendFormat("{0:x2}", b);
        return hex.ToString();
    }

    public static byte[] StringToByteArray(String hex)
    {
        int NumberChars = hex.Length;
        byte[] bytes = new byte[NumberChars / 2];
        for (int i = 0; i < NumberChars; i += 2)
            bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
        return bytes;
    }
}

Solution

  • You must set initialization vector (IV) to the same value for encryption as well as for decryption. Because new IV is automatically generated for each new instance of DESCryptoServiceProvider, your IV differs and decryption is not successfull.

    The reason that half of the message is decrypted correctly results from usage of CBC mode (which is default mode), which has one really nasty property, that only first block of encrypted message actually depends on value of IV, so potential attacker can decode all message, except first block, without knowing correct IV (of course, correct Key is still needed). So it is not recommended to use this mode. See Block cipher mode of operation for more info about this.

    So solution is easy - store somewhere IV used for encryption and use the same IV for decryption. If possible, use another cypher mode too. Somthing like this:

    using System;
    using System.Security.Cryptography;
    using System.Text;
    
    class Program
    {
        static void Main(string[] args)
        {
            var userId = "abc12345";
            var appId = "xyz56789";
    
            Console.WriteLine($"UserId: {userId}, AppId: {appId}");
    
            byte[] IV;
            var code = QuickEncode(userId, appId, out IV);
    
            Console.WriteLine(code);
    
            var result = QuickDecode(code, IV);
    
            var uId = result.Item1;
            var aId = result.Item2;
    
            Console.WriteLine($"UserId: {uId}, AppId: {aId}");
    
            Console.ReadKey();
        }
    
        private static string QuickEncode(string userId, string appId, out byte[] IV)
        {
            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
    
            var desKey = StringToByteArray("437459133faf42cb");
    
            des.Key = desKey;
            des.GenerateIV();
            IV = des.IV;
    
            ICryptoTransform encryptor = des.CreateEncryptor();
    
            var encryptMe = $"{userId}:{appId}";
    
            Console.WriteLine($"Input String: {encryptMe}");
    
            byte[] stringBytes = System.Text.Encoding.UTF8.GetBytes(encryptMe);
    
            byte[] enc = encryptor.TransformFinalBlock(stringBytes, 0, stringBytes.Length);
    
            var encryptedBytesString = Convert.ToBase64String(enc);
    
            return encryptedBytesString;
        }
    
        private static Tuple<string, string> QuickDecode(string code, byte[] IV)
        {
            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
    
            var desKey = StringToByteArray("437459133faf42cb");
    
            des.Key = desKey;
            des.IV = IV;
    
            ICryptoTransform decryptor = des.CreateDecryptor();
    
            var codeBytes = Convert.FromBase64String(code);
    
            byte[] originalAgain = decryptor.TransformFinalBlock(codeBytes, 0, codeBytes.Length);
    
            var decryptMe = System.Text.Encoding.UTF8.GetString(originalAgain);
    
            Console.WriteLine($"Output String: {decryptMe}");
    
            var ids = decryptMe.Split(':');
    
            return new Tuple<string, string>(ids[0], ids[1]);
        }
    
        public static string ByteArrayToString(byte[] ba)
        {
            StringBuilder hex = new StringBuilder(ba.Length * 2);
            foreach (byte b in ba)
                hex.AppendFormat("{0:x2}", b);
            return hex.ToString();
        }
    
        public static byte[] StringToByteArray(String hex)
        {
            int NumberChars = hex.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            return bytes;
        }
    }