재우니의 블로그

 

AES/CBC/PKCS5Padding Encryption/Decryption in C#

 

 

Zeb Sadiq | AES/CBC/PKCS5Padding Encryption/Decryption in C#

by zebsadiq 1. 7월 2013 13:33 I’ve recently been asked to write some code to decrypt some data using C# which was originally encrypted in Java. While I managed to get this to work, I had to piece information together from many different sources on the web.

blog.zebsadiq.com

 

C# 의 AES/CBC/PKCS5Padding 으로 암복호화 하기

 

더보기

I’ve recently been asked to write some code to decrypt some data using C# which was originally encrypted in Java. While I managed to get this to work, I had to piece information together from many different sources on the web. Therefore I have decided to simplify the procedure for the next person who comes looking by putting this into one place.

I was provided with the following piece of information by the Java developer (obviously the values here are created to be used for demo purposes):

 

 

저는 최근 Java로 암호화 된 것을 C# 으로 사용하여 일부 데이터를 복호화하는 코드를 개발하라는 요청을 받았습니다. 개발을 하는 동안 인터넷 검색을 통해 여러 다른 많은 소스 정보를 수집했습니다. 따라서 이것을 한 곳에 두어 찾는 사람을 위해 이런 복잡한 절차를 단순화하기로 결정했습니다. Java 개발자에 의해 다음 정보를 제공 받았습니다 (분명히 여기에 있는 값은 데모용으로 사용하기 위해 생성되었습니다).

 

Cyphered data (base64) r/w1xZb85PokABCycED5Tw==
Deciphered data I am Zeb
Encryption key (hex) 142eb4a7ab52dbfb971e18daed7056488446b4b2167cf61187f4bbc60fc9d96d
Initialisation Vector (hex) 26744a68b53dd87bb395584c00f7290a
Cipher method AES/CBC/PKCS5Padding

 

더보기

Though what needed to be done was straight forward, I faced the following difficulties when discovering how to code my solution:

  • Almost all examples I came across were discussing the use of the Rfc2898DeriveBytes class to salt a password which is very specific to the security around passwords.
  • No on example pointed out the difference between a Base32 encoding , a Hexadecimal string and a Base64 string.
  • When you are working with decryption, your result is either going to be right or wrong. If you algorithm doesn’t work, then you do not exactly what is going wrong. There doesn’t seem to be an online tool to ensure that the data that you are working with is valid.
  • Since my data was coming from Java, I was not sure whether Java has a slightly different algorithm for the cipher method used which was causing my decoding to fail. (Obviously this was just a doubt).

In our scenario, we will not be working to encrypt passwords. For that, I would recommend the use of Rfc2898DeriveBytes. There are plenty of examples of this on the web.

 

 

수행해야 할 작업은 간단했지만 솔루션을 코딩하는 방법을 발견 할 때 다음과 같은 어려움에 직면했습니다.

 

1. 내가 접한 거의 모든 예제는 Rfc2898DeriveBytes 클래스를 사용하여 암호 보안에 매우 특정한 암호를 salt 하는 방법에 대해 논의했습니다.

 

2. Base32 인코딩, 16 진수 문자열 및 Base64 문자열의 차이점을 설명하는 샘플이 없습니다.

 

3. 암호 해독 작업을 할 때 결과는 옳거나 틀릴 것입니다. 알고리즘이 작동하지 않으면 정확히 무엇이 잘못되었는지 알 수 없습니다. 작업중인 데이터가 유효한지 확인하는 온라인 도구가 없는 것 같습니다.

 

4. 데이터가 Java에서 왔기 때문에 Java가 사용 된 암호 방법에 대해 약간 다른 알고리즘을 사용하여 디코딩에 실패했는지 여부를 확신 할 수 없었습니다. (분명히 이것은 의심의 여지가 있습니다). 이 시나리오에서는 암호를 암호화하지 않습니다. 이를 위해 Rfc2898DeriveBytes 사용을 권장합니다. 웹상 검색하면 이에 대한 많은 예제가 있습니다.

 

 

Here is my code

 

MyCryptoClass.cs:

 

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

namespace CypherExample
{

    public sealed class MyCryptoClass
    {
        protected RijndaelManaged myRijndael;

        private static string encryptionKey = "142eb4a7ab52dbfb971e18daed7056488446b4b2167cf61187f4bbc60fc9d96d";
        private static string initialisationVector = "26744a68b53dd87bb395584c00f7290a";

        // Singleton pattern used here with ensured thread safety
        protected static readonly MyCryptoClass _instance = new MyCryptoClass();
        public static MyCryptoClass Instance
        {
            get { return _instance; }
        }

        public MyCryptoClass()
        {
            
        }

        public string DecryptText(string encryptedString)
        {
            using (myRijndael = new RijndaelManaged())
            {

                myRijndael.Key = HexStringToByte(encryptionKey);
                myRijndael.IV = HexStringToByte(initialisationVector);
                myRijndael.Mode = CipherMode.CBC;
                myRijndael.Padding = PaddingMode.PKCS7;

                Byte[] ourEnc = Convert.FromBase64String(encryptedString);
                string ourDec = DecryptStringFromBytes(ourEnc, myRijndael.Key, myRijndael.IV);

                return ourDec;
            }
        }


        public string EncryptText(string plainText)
        {
            using (myRijndael = new RijndaelManaged())
            {

                myRijndael.Key = HexStringToByte(encryptionKey);
                myRijndael.IV = HexStringToByte(initialisationVector);
                myRijndael.Mode = CipherMode.CBC;
                myRijndael.Padding = PaddingMode.PKCS7;

                byte[] encrypted = EncryptStringToBytes(plainText, myRijndael.Key, myRijndael.IV);
                string encString = Convert.ToBase64String(encrypted);

                return encString;
            }
        }


        protected byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
        {
            // Check arguments. 
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");
            byte[] encrypted;
            // Create an RijndaelManaged object 
            // with the specified key and IV. 
            using (RijndaelManaged rijAlg = new RijndaelManaged())
            {
                rijAlg.Key = Key;
                rijAlg.IV = IV;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);

                // Create the streams used for encryption. 
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {

                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }


            // Return the encrypted bytes from the memory stream. 
            return encrypted;

        }

        protected string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments. 
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");

            // Declare the string used to hold 
            // the decrypted text. 
            string plaintext = null;

            // Create an RijndaelManaged object 
            // with the specified key and IV. 
            using (RijndaelManaged rijAlg = new RijndaelManaged())
            {
                rijAlg.Key = Key;
                rijAlg.IV = IV;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);

                // Create the streams used for decryption. 
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream 
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }

            }

            return plaintext;

        }

        public static void GenerateKeyAndIV()
        {
            // This code is only here for an example
            RijndaelManaged myRijndaelManaged = new RijndaelManaged();
            myRijndaelManaged.Mode = CipherMode.CBC;
            myRijndaelManaged.Padding = PaddingMode.PKCS7;

            myRijndaelManaged.GenerateIV();
            myRijndaelManaged.GenerateKey();
            string newKey = ByteArrayToHexString(myRijndaelManaged.Key);
            string newinitVector = ByteArrayToHexString(myRijndaelManaged.IV);
        }

        protected static byte[] HexStringToByte(string hexString)
        {
            try
            {
                int bytesCount = (hexString.Length) / 2;
                byte[] bytes = new byte[bytesCount];
                for (int x = 0; x < bytesCount; ++x)
                {
                    bytes[x] = Convert.ToByte(hexString.Substring(x * 2, 2), 16);
                }
                return bytes;
            }
            catch
            {
                throw;
            }
        }

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

 

더보기

To be able to test our Cipher, the following is a Console Application which would output the results of our Encryption / Decryption.

 

다음은 암호화 / 복호화 결과를 출력하는 콘솔 애플리케이션입니다.

 

Program.cs:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CypherExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("********************Encryption Example******************");
            MyCryptoClass crypto = MyCryptoClass.Instance;
            string inputText = "I am Zeb";
            Console.WriteLine("Plain text is: '{0}'", inputText);
            string encryptedText = crypto.EncryptText(inputText);
            Console.WriteLine("Encrypted text is '{0}'", encryptedText);

            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("********************Decryption Example******************");
            string inputEncryptedText = "r/w1xZb85PokABCycED5Tw==";
            Console.WriteLine("Input Encrypted text is '{0}'", inputEncryptedText);
            string decryptedText = crypto.DecryptText(inputEncryptedText);
            Console.WriteLine("Decrypted text is: '{0}'", decryptedText);

            


            Console.ReadLine();
        }
    }
}

 

결과물

 

********************Encryption Example******************
Plain text is: 'I am Zeb'
Encrypted text is 'r/w1xZb85PokABCycED5Tw=='


********************Decryption Example******************
Input Encrypted text is 'r/w1xZb85PokABCycED5Tw=='
Decrypted text is: 'I am Zeb'

 

더보기

Key notes about the code above:

  • The cypher data is a Base64 string. This is why we use the Convert.FromBase64String() method on line 38.
  • Method HexStringToByte() converts hex strings to bytes. Remember that these strings are different to Base64 strings.
  • Method ByteArrayToHexString() on line 182 is the reverse process for HexStringToByte().
  • Notice, line 33 to 36 in MyCryptoClass.cs. This is where i am defining the Cipher method.
  • Notice that the encrypted data is base64 and the key and initialisation vector is a hex value. If you get these mixed up, your solution will not work.
  • PaddingMode.PKCS7 accommodates PKCS5Padding.

 

 

위 코드에 대한 주요 참고 사항 :

 

1. cypher 데이터는 Base64 문자열입니다. 그래서 Convert.FromBase64String () 메서드를 사용하는 이유입니다.

2. HexStringToByte () 메서드는 16 진수 문자열을 바이트로 변환합니다. 이러한 문자열은 Base64 문자열과 다릅니다.

3. ByteArrayToHexString () 메서드는 HexStringToByte ()의 reverse 한 프로세스입니다.

4. MyCryptoClass.cs 의 33-36 행에 유의하십시오. 이것이 Cipher 메서드를 정의하는 곳입니다.

5. 암호화 된 데이터는 base64이고 keyinitialisation vector 는 16 진수 값입니다.

6. PaddingMode.PKCS7 은 PKCS5Padding 을 제공합니다.


 

만약에 key 나 iv 값이 변동되어 오류가 string 값을 byte 로 변환하는 메소드에 오류나면 아래와 같이 변경하시면 됩니다.

 

protected static byte[] HexStringToByte(string hexString)
        {
            try
            {
                //int bytesCount = (hexString.Length) / 2;
                //byte[] bytes = new byte[bytesCount];
                //for (int x = 0; x < bytesCount; ++x)
                //{
                //    bytes[x] = Convert.ToByte(hexString.Substring(x * 2, 2), 16);
                //}

                byte[] bytes = Encoding.UTF8.GetBytes(hexString);

                return bytes;
            }
            catch
            {
                throw;
            }
        }