简体   繁体   中英

C++/CLI AES 256-bit Encryption

Most examples and questions I've found so far is for C# only, however I'm trying to reproduce the following C# code into C++/CLI:

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

public byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
    byte[] encryptedBytes = null;

    // Set your salt here, change it to meet your flavor:
    // The salt bytes must be at least 8 bytes.
    byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

    using (MemoryStream ms = new MemoryStream())
    {
        using (RijndaelManaged AES = new RijndaelManaged())
        {
            AES.KeySize = 256;
            AES.BlockSize = 128;

            var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);

            AES.Mode = CipherMode.CBC;

            using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
            {
                cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                cs.Close();
            }
            encryptedBytes = ms.ToArray();
        }
    }

    return encryptedBytes;
}

public byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
    byte[] decryptedBytes = null;

    // Set your salt here, change it to meet your flavor:
    // The salt bytes must be at least 8 bytes.
    byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

    using (MemoryStream ms = new MemoryStream())
    {
        using (RijndaelManaged AES = new RijndaelManaged())
        {
            AES.KeySize = 256;
            AES.BlockSize = 128;

            var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);

            AES.Mode = CipherMode.CBC;

            using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
            {
                cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
                cs.Close();
            }
            decryptedBytes = ms.ToArray();
        }
    }

    return decryptedBytes;
}

//Encrypt String
public string EncryptText(string input, string password)
{
    // Get the bytes of the string
    byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input);
    byte[] passwordBytes = Encoding.UTF8.GetBytes(password);

    // Hash the password with SHA256
    passwordBytes = SHA256.Create().ComputeHash(passwordBytes);

    byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);

    string result = Convert.ToBase64String(bytesEncrypted);

    return result;
}

//Decrypt String
public string DecryptText(string input, string password)
{
    // Get the bytes of the string
    byte[] bytesToBeDecrypted = Convert.FromBase64String(input);
    byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
    passwordBytes = SHA256.Create().ComputeHash(passwordBytes);

    byte[] bytesDecrypted = AES_Decrypt(bytesToBeDecrypted, passwordBytes);

    string result = Encoding.UTF8.GetString(bytesDecrypted);

    return result;
}

This is what I got so far:

using namespace System::Security::Cryptography;
using namespace System::IO;

    private: array<unsigned char>^ AES_Encrypt(array<unsigned char>^ bytesToBeEncrypted, array<unsigned char>^ passwordBytes) {
        array<unsigned char>^ encryptedBytes = nullptr;

        // Set your salt here, change it to meet your flavor:
        // The salt bytes must be at least 8 bytes.
        array<unsigned char>^ saltBytes = gcnew array<unsigned char>(8) { 1, 2, 3, 4, 5, 6, 7, 8 };

        MemoryStream^ ms = gcnew MemoryStream();
        RijndaelManaged^ AES = gcnew RijndaelManaged();
        auto cs = gcnew CryptoStream(ms, AES->CreateEncryptor(), CryptoStreamMode::Write);

        try {
            try {
                AES->KeySize = 256;
                AES->BlockSize = 128;
                AES->Padding = System::Security::Cryptography::PaddingMode::Zeros;

                auto key = gcnew Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                AES->Key = key->GetBytes(AES->KeySize / 8);
                AES->IV = key->GetBytes(AES->BlockSize / 8);

                AES->Mode = CipherMode::CBC;

                try {
                    cs->Write(bytesToBeEncrypted, 0, bytesToBeEncrypted->Length);
                    cs->Close();
                }
                finally {
                    if (cs != nullptr) delete cs;
                }

                encryptedBytes = ms->ToArray();
            }
            finally {
                if (AES != nullptr) delete AES;
            }
        }
        finally {
         if (ms != nullptr) delete ms;
        }

        return encryptedBytes;
    }

    private: array<unsigned char>^ AES_Decrypt(array<unsigned char>^ bytesToBeDecrypted, array<unsigned char>^ passwordBytes) {
        array<unsigned char>^ decryptedBytes = nullptr;

        // Set your salt here, change it to meet your flavor:
        // The salt bytes must be at least 8 bytes.
        array<unsigned char>^ saltBytes = gcnew array<unsigned char>(8) { 1, 2, 3, 4, 5, 6, 7, 8 };

        MemoryStream^ ms = gcnew MemoryStream();
        RijndaelManaged^ AES = gcnew RijndaelManaged();
        auto cs = gcnew CryptoStream(ms, AES->CreateDecryptor(), CryptoStreamMode::Write);

        try {
            try {
                AES->KeySize = 256;
                AES->BlockSize = 128;
                AES->Padding = System::Security::Cryptography::PaddingMode::Zeros;

                auto key = gcnew Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                AES->Key = key->GetBytes(AES->KeySize / 8);
                AES->IV = key->GetBytes(AES->BlockSize / 8);

                AES->Mode = CipherMode::CBC;

                try {
                    cs->Write(bytesToBeDecrypted, 0, bytesToBeDecrypted->Length);
                    cs->Close();
                }
                finally {
                    if (cs != nullptr) delete cs;
                }

                decryptedBytes = ms->ToArray();
            }
            finally {
                if (AES != nullptr) delete AES;
            }
        }
        finally {
         if (ms != nullptr) delete ms;
        }

        return decryptedBytes;
    }

    //Encrypt String
    private: System::String^ EncryptText(System::String^ input, System::String^ password) {
        // Get the bytes of the string
        array<unsigned char>^ bytesToBeEncrypted = System::Text::Encoding::UTF8->GetBytes(input);
        array<unsigned char>^ passwordBytes = System::Text::Encoding::UTF8->GetBytes(password);

        // Hash the password with SHA256
        passwordBytes = SHA256::Create()->ComputeHash(passwordBytes);

        array<unsigned char>^ bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);

        System::String^ result = Convert::ToBase64String(bytesEncrypted);

        return result;
    }

    //Decrypt String
    private: System::String^ DecryptText(System::String^ input, System::String^ password) {
        // Get the bytes of the string
        array<unsigned char>^ bytesToBeDecrypted = Convert::FromBase64String(input);
        array<unsigned char>^ passwordBytes = System::Text::Encoding::Encoding::UTF8->GetBytes(password);
        passwordBytes = SHA256::Create()->ComputeHash(passwordBytes);

        array<unsigned char>^ bytesDecrypted = AES_Decrypt(bytesToBeDecrypted, passwordBytes);

        System::String^ result = System::Text::Encoding::Encoding::UTF8->GetString(bytesDecrypted);

        return result;
    }

The Encryption is working fine, I did a simple test to update it into a label:

private: System::Void Button1_Click(System::Object^ sender, System::EventArgs^ e) {
        System::String^ temp = EncryptText(this->textBox1->Text, "batman");
        this->label1->Text = temp;
        this->label2->Text = DecryptText(temp, "batman");
    }

However I'm getting some issues when Decrypting it, I managed to catch the CryptographicException :

'EncryptionTest.exe' (Win32): Loaded 'C:\Windows\Microsoft.NET\Framework\v4.0.30319\diasymreader.dll'. 
System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed.
   at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast)
   at System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
   at System.Security.Cryptography.CryptoStream.FlushFinalBlock()
   at System.Security.Cryptography.CryptoStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at EncryptionTest.MyForm.AES_Decrypt(Byte[] bytesToBeDecrypted, Byte[] passwordBytes) in C:\Users\[username]\source\repos\EncryptionTest\EncryptionTest\MyForm.h:line 198

Line 198 refers to the last try block: cs->Close();

I tried all kind of available paddings though, such as ANSIX923 , ISO10126 , PKCS7 , but none helped. I'd appreciate any help you're able to provide.

The AES parameters are set too late in the C++/CLI code, that is, after the creation of encryptor and decryptor . Therefore, an automatically generated random key and IV and other default values (PKCS7, CBC, etc.) of the RijndaelManaged instance are used to create encryptor and decryptor (instead of the key and IV generated with the Rfc2898DeriveBytes instance and the rest of the specified values). Because of the randomness, key and IV are different for encryption and decryption and thus decryption fails. Therefore, the AES parameters must be set before encryptor and decryptor are created, ie the correct order is (using the example of encryption):

AES->KeySize = 256;
AES->BlockSize = 128;
AES->Mode = CipherMode::CBC;
AES->Padding = System::Security::Cryptography::PaddingMode::Zeros;
auto key = gcnew Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES->Key = key->GetBytes(AES->KeySize / 8);
AES->IV = key->GetBytes(AES->BlockSize / 8);
auto cs = gcnew CryptoStream(ms, AES->CreateEncryptor(), CryptoStreamMode::Write);

By the way, the values for key size, block size and mode correspond to the default values. The default value for padding is PKCS7, which is the more reliable padding compared to zero padding, as already noted in the comments.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM