Skip to content

014: Implement 3des cbc

Shane DeSeranno edited this page Feb 15, 2017 · 4 revisions

Welcome back! We are going to be working on adding a supported cipher. And we are actually going to add two ciphers. One is going to be our default NoCipher, which will hold the default block size (8) and do nothing to the data. But first, let's create a new folder called Ciphers and in that folder create an interface ICipher and make it implement IAlgorithm.

    public interface ICipher : IAlgorithm
    {
        uint BlockSize { get; }
        uint KeySize { get; }
        byte[] Encrypt(byte[] data);
        byte[] Decrypt(byte[] data);
        void SetKey(byte[] key, byte[] iv);
    }

Here, we added a BlockSize property, which is used when we need to decrypt the first block of data. The KeySize property which is used during KEX to get the correct key sizes. Encrypt() and Decrypt() methods use the algorithm to encrypt and decrypt a block of data respectively. Finally SetKey() is used during KEX to initialize the algorithm's key values.

Now, let's create our NoCipher class and inherit from ICipher.

    public class NoCipher : ICipher
    {
        public uint BlockSize
        {
            get
            {
                return 8;
            }
        }

        public uint KeySize
        {
            get
            {
                return 0;
            }
        }

        public string Name
        {
            get
            {
                return "none";
            }
        }

        public byte[] Decrypt(byte[] data)
        {
            return data;
        }

        public byte[] Encrypt(byte[] data)
        {
            return data;
        }

        public void SetKey(byte[] key, byte[] iv)
        {
            // No key for this cipher
        }
    }

As you can see, it does very little. It does return the default block size (8) and the Encrypt() and Decrypt() just return the input data. SetKey() does nothing.

Now let's create a real cipher named TripleDESCBC and inherit ICipher.

    public class TripleDESCBC : ICipher
    {
        public uint BlockSize
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public uint KeySize
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public string Name
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public byte[] Decrypt(byte[] data)
        {
            throw new NotImplementedException();
        }

        public byte[] Encrypt(byte[] data)
        {
            throw new NotImplementedException();
        }

        public void SetKey(byte[] key, byte[] iv)
        {
            throw new NotImplementedException();
        }
    }

Now let's start implementing the properties and methods. Most of them are pretty easy, and encrypting and decrypting are almost identical, so I created a private helper method to share code:

    public class TripleDESCBC : ICipher
    {
        private TripleDES m_3DES = TripleDES.Create();
        private ICryptoTransform m_Decryptor;
        private ICryptoTransform m_Encryptor;

        public uint BlockSize
        {
            get
            {
                // According to https://msdn.microsoft.com/en-us/library/system.security.cryptography.symmetricalgorithm.blocksize(v=vs.110).aspx
                // TripleDES.BlockSize is the size of the block in bits, so we need to divide by 8
                // to convert from bits to bytes.
                return (uint)(m_3DES.BlockSize / 8);
            }
        }

        public uint KeySize
        {
            get
            {
                // https://msdn.microsoft.com/en-us/library/system.security.cryptography.symmetricalgorithm.keysize(v=vs.110).aspx
                // TripleDES.KeySize is the size of the key in bits, so we need to divide by 8
                // to convert from bits to bytes.
                return (uint)(m_3DES.KeySize / 8);
            }
        }

        public string Name
        {
            get
            {
                return "3des-cbc";
            }
        }

        public byte[] Decrypt(byte[] data)
        {
            return PerformTransform(m_Decryptor, data);
        }

        public byte[] Encrypt(byte[] data)
        {
            return PerformTransform(m_Encryptor, data);
        }

        public void SetKey(byte[] key, byte[] iv)
        {
            m_3DES.KeySize = 192;
            m_3DES.Key = key;
            m_3DES.IV = iv;
            m_3DES.Padding = PaddingMode.None;
            m_3DES.Mode = CipherMode.CBC;

            m_Decryptor = m_3DES.CreateDecryptor(key, iv);
            m_Encryptor = m_3DES.CreateEncryptor(key, iv);
        }

        private byte[] PerformTransform(ICryptoTransform transform, byte[] data)
        {
            if (transform == null)
                throw new InvalidOperationException("SetKey must be called before attempting to encrypt or decrypt data.");

            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
                {
                    cryptoStream.Write(data, 0, data.Length);
                    cryptoStream.FlushFinalBlock();
                    return memoryStream.ToArray();
                }
            }
        }
    }

Now, let's do like we did with the other algorithms and add it to the a SupportedCiphers list on the Server object.

    public class Server
    {
        ...
        public static IReadOnlyList<Type> SupportedCiphers { get; private set; } = new List<Type>()
        {
            typeof(TripleDESCBC)
        };
        ...
    }

Notice, we did NOT add the NoCipher as supported because we don't want to use this for communication. But it will come in handy when we are setting up our initial KEX. Now, go to the Client and add the list of supported ciphers to both the ClientToServer and the ServerToClient values.

    m_KexInitServerToClient.EncryptionAlgorithmsClientToServer.AddRange(Server.GetNames(Server.SupportedCiphers));
    m_KexInitServerToClient.EncryptionAlgorithmsServerToClient.AddRange(Server.GetNames(Server.SupportedCiphers));

And, we are done with this section. Again, the server output hasn't changed, but you can see the results in OpenSSH:

debug2: KEX algorithms: diffie-hellman-group14-sha1
debug2: host key algorithms: ssh-rsa
debug2: ciphers ctos: 3des-cbc
debug2: ciphers stoc: 3des-cbc
...
debug1: kex: algorithm: diffie-hellman-group14-sha1
debug1: kex: host key algorithm: ssh-rsa
Unable to negotiate with 127.0.0.1 port 22: no matching MAC found. Their offer:

It sees our new 3des-cbc algoritm for both Client to Server (ctos) and Server to Client (stoc). The code to this point can be seen at the tag Implement_3des-cbc. Now we need to agree on a MAC algorithm, and we will implement hmac-sha1.