当出现 "Padding is invalid and cannot be removed" 错误时,如何将 C++ Rijndael Cryptography 转换为 C#?

How to convert C++ Rijndael Cryptography to C#, when there is an error saying "Padding is invalid and cannot be removed"?

我正在尝试将 c++ 源代码转换为使用 Rinjdael 密码术加密和解密文件的 c#。

但是c++源码和普通的en/decryptions有点不同。

而且我不太擅长c++,所以我很困惑。

我的一个客户的应用程序是用 VC++ 编写的,将其转换为 c# 是我工作的一部分。

而之前的 c++ 开发人员使用来自 http://www.codeproject.com/Articles/10657/A-Simple-Portable-Rinjdael-AES-Based-Stream-Cipher 的开源代码来操作 en/decryption。

这里是c++源代码。

int DCipher::DecryptFile(LPCTSTR szSrcFile, LPCTSTR szDestFile, const char* pwd, int head[19])
{
    if(CheckMemSize() != 0)
        return INSUFFICIENT_MEMORY;

    FileSize=CurPosition=0;

    _tcscpy(SrcFile, szSrcFile);
    _tcscpy(OutFile, szDestFile);
    //_tcscpy(OutFile, _T(".enc"));

    strcpy(password, pwd);

    for(int i=0; i<19; i++)
    {
        header[i] = head[i];
    }

    FILE *r, *w;
    GetFileLength();

    int nCheck = CheckIfEncrypted();

    if(nCheck != ENCRYPTED_FILE )
        return nCheck;  //either NORMAL_FILE or BAD_SIGNATURE


    if((r = _tfopen(SrcFile, _T("rb"))) == NULL)
        return ERROR_SRC_FILE;


    if((w = _tfopen(OutFile, _T("wb"))) == NULL)
    {
        fclose(r);
        return ERROR_DST_FILE;
    }

    char zzz[26];   //fixed invalid pointer - DKeesler
    fread(zzz, 25, 1, r); // Skip first 25 bytes of the file.

    int pad = header[19];
    pad *= 10;
    pad += header[20];

    // convert password to Rijndael key
    strcpy((char*)key, (const char*)CalcMD5FromString((const char*)password));

    /***************************************
    Decryption algorithm 
    ***************************************/
    int rval = NO_ERRORS_DONE;
    FileSize -= 25; 

    unsigned int BUFF_SIZE = liChunkSize;
    unsigned int WRITE_SIZE = liChunkSize;

    int nRound = FileSize / liChunkSize;
    unsigned int LAST_BLOCK = FileSize % liChunkSize;

    if(LAST_BLOCK >= 1)
        nRound++;

    const unsigned char* intext;
    unsigned char* output;

    intext = (const unsigned char*)malloc(BUFF_SIZE);
    output = (unsigned char*)malloc(BUFF_SIZE+16);

    if(intext == NULL || output == NULL)
    {
        fclose(r);
        fclose(w);
        return ALLOC_ERROR;
    }

    Rijndael rj;
    rj.init(Rijndael::CBC, Rijndael::Decrypt, key, Rijndael::Key32Bytes);

    for(int loop=1; loop <= nRound; loop++)
    {

        if(loop == nRound && LAST_BLOCK >= 1)
        {
            BUFF_SIZE = LAST_BLOCK;
            WRITE_SIZE = LAST_BLOCK - pad;
        }

        fread((void*)intext, sizeof(char), BUFF_SIZE, r); // read plaintext into intext[] buffer

        int bsize = BUFF_SIZE*8;
        int len = rj.blockDecrypt((const UINT8*)intext, bsize, (UINT8*)output);

        if(len >= 0)
        {
            fwrite((const void*)output, sizeof(char), WRITE_SIZE, w);
        }
        else
        {
            rval = READ_WRITE_ERROR;
            break;
        }
    }

    fclose(r);  //close input file
    fclose(w);  //close output file

    free((void*)intext);
    free((void*)output);

    //change these two lines if you want to leave backups or unencrypted copies...
    //that would sort of defeat the purpose of encryption in my mind, but it's your
    // app so write it like you want it.
    if(DECRYPTION_CANCEL == rval) { 
        _tremove(OutFile); 
    } 
    else { 
        //_tremove(SrcFile);    //remove input file
        //_trename(OutFile, SrcFile); //rename output file to input filename
    }

    return rval; //ZERO .. see defines for description of error codes.
}

而c#源代码来自https://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndael(v=vs.110).aspx.

并且我修改了一些代码。

这是 C# 代码。

public int DecryptFile(string SourceFilePath, string DestFilePath, string Password, string Signature)
{
    try
    {
        FileSize        = CurPosition = 0;

        FileInfo _fi    = new FileInfo(SourceFilePath);
        FileSize        = _fi.Length;

        // copy the signature to _header
        for(int i = 0; i < 19; i++)
        {
            _header[i] = (byte)Signature[i];
        }

        /*
            * check if the file is valid encrypted file.
            */
        int nCheck  = this.CheckIfEncrypted(SourceFilePath);
        switch (nCheck)
        {
            case ENCRYPTED_FILE:
                // The file is an encrypted file.
                break;
            case NORMAL_FILE:
                throw new ArgumentException("The file is a normal file.");
            case BAD_SIGNATURE:
                throw new ArgumentException("User signature doesn't match.");
        }

        int pad = _header[19];
        pad     *= 10;
        pad     += _header[20];

        // Rijndael session key
        byte[] session_key  = this.CalcMD5FromString(Password);

        byte[] _restFileBytes   = new byte[_fi.Length - 25];
        using (FileStream _fs = new FileStream(SourceFilePath, FileMode.Open, FileAccess.Read))
        {
            _fs.Read(_restFileBytes, 0, _restFileBytes.Length);
        }

        int rval        = NO_ERRORS_DONE;
        FileSize        -= 25; 

        int BUFF_SIZE   = liChunkSize;
        int WRITE_SIZE  = liChunkSize;

        int nRound      = (int)FileSize / liChunkSize;
        int LAST_BLOCK  = (int)FileSize % liChunkSize;

        if(LAST_BLOCK >= 1)
            nRound++;

        byte[] intext   = new byte[BUFF_SIZE];
        byte[] output   = new byte[BUFF_SIZE + 16];

        if (intext.Length == 0 || output.Length == 0)
        {
            return ALLOC_ERROR;
        }

        for (int loop = 1; loop <= nRound; loop++)
        {
            if (loop == nRound && LAST_BLOCK >= 1)
            {
                BUFF_SIZE   = LAST_BLOCK;
                WRITE_SIZE  = LAST_BLOCK - pad;
            }

            intext  = new byte[BUFF_SIZE];

            System.Buffer.BlockCopy(_restFileBytes, (loop - 1) * this.liChunkSize, intext, 0, BUFF_SIZE);

            int bsize   = BUFF_SIZE * 8;    // -> I still couldn't figure out what this bsize does on Rijndael decryption.
            using (RijndaelManaged myRijndael = new RijndaelManaged())
            {
                myRijndael.Key          = session_key;
                //myRijndael.BlockSize  = bsize;
                //myRijndael.Padding        = PaddingMode.None;
                myRijndael.GenerateIV();

                using (Rijndael rijAlg = Rijndael.Create())
                {
                    rijAlg.Key  = myRijndael.Key;
                    rijAlg.IV   = myRijndael.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(intext))
                    {
                        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.
                            //  //string s = srDecrypt.ReadToEnd();
                            //}
                            byte[] rettt    = msDecrypt.ToArray();
                        } // --> Padding is invalid and cannot be removed error occurs here and msDecrypt byte array is just same as intext. So, it's not decrypted at all.
                    }

                }
            }
        }


        return rval;
    }
    catch
    {
        throw;
    }
}

根据 Keesler(codeproject.com 的 c++ 源代码的作者),前 25 个字节填充了用户数据(签名、填充和文件状态)。因此,我跳过了前 25 个字节并将其余字节保存到 _restFileBytes varialbes(字节数组)。

而Keesler有一个叫做chunk size的变量,它将文件字节分成chunk size(只要我理解)。

无论如何,我想我几乎转换为 c#,但在 c# 中部署 CryptoStream 时我仍然收到此错误消息 "Padding is invalid and cannot be removed"。

谁能给我一些修复此错误的指南?

None 应该用作填充模式。看来您的同事和原文章的作者制定了自己的填充方案。

此外,所有密文都应该从文件中流式传输(确保您读取了所有字节)。目前您正在为每个块重新使用 IV 加密,这不好,IV 应该只在密文的开头使用。

打印出 C++ 和 C# 的十六进制密钥,并在开始之前进行比较。

请注意,Read 方法与 C++ 中的 fread 方法略有不同。