15

これについて他の質問が寄せられたことは知っていますが、これまでのところ解決策を提供していないか、まさに私が抱えている問題です.

以下のクラスは、文字列の暗号化と復号化を処理します。渡されるキーとベクトルは常に同じです。

暗号化および復号化される文字列は常に数字であり、ほとんどの場合は機能しますが、復号化時に失敗することがあります (ただし、運用サーバーでのみ)。ローカル環境と運用環境の両方が Windows Server 2003 上の IIS6 にあり、クラスを使用するコードは .ashx ハンドラーにあることに注意してください。本番サーバーで失敗する例は「0000232668」

エラーメッセージは

System.Security.Cryptography.CryptographicException: パディングが無効であり、削除できません。System.Security.Cryptography.RijndaelManagedTransform.DecryptData (Byte[] inputBuffer、Int32 inputOffset、Int32 inputCount、Byte[]& outputBuffer、Int32 outputOffset、PaddingMode paddingMode、Boolean fLast) で

そして、コードのために

 public class Aes
    {
        private byte[] Key;
        private byte[] Vector;

        private ICryptoTransform EncryptorTransform, DecryptorTransform;
        private System.Text.UTF8Encoding UTFEncoder;

        public Aes(byte[] key, byte[] vector)
        {
            this.Key = key;
            this.Vector = vector;

            // our encyption method
            RijndaelManaged rm = new RijndaelManaged();

            rm.Padding = PaddingMode.PKCS7;

            // create an encryptor and decyptor using encryption method. key and vector
            EncryptorTransform = rm.CreateEncryptor(this.Key, this.Vector);
            DecryptorTransform = rm.CreateDecryptor(this.Key, this.Vector);

            // used to translate bytes to text and vice versa
            UTFEncoder = new System.Text.UTF8Encoding();
        }

        /// Encrypt some text and return a string suitable for passing in a URL. 
        public string EncryptToString(string TextValue)
        {
            return ByteArrToString(Encrypt(TextValue));
        }

        /// Encrypt some text and return an encrypted byte array. 
        public byte[] Encrypt(string TextValue)
        {
            //Translates our text value into a byte array. 
            Byte[] bytes = UTFEncoder.GetBytes(TextValue);
            Byte[] encrypted = null;

            //Used to stream the data in and out of the CryptoStream. 
            using (MemoryStream memoryStream = new MemoryStream())
            {                
                using (CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write))
                {
                    cs.Write(bytes, 0, bytes.Length);                    
                }

                encrypted = memoryStream.ToArray();                
            }

            return encrypted;
        }

        /// The other side: Decryption methods 
        public string DecryptString(string EncryptedString)
        {
            return Decrypt(StrToByteArray(EncryptedString));
        }

        /// Decryption when working with byte arrays.     
        public string Decrypt(byte[] EncryptedValue)
        {
            Byte[] decryptedBytes = null;

            using (MemoryStream encryptedStream = new MemoryStream())
            {
                using (CryptoStream decryptStream = new CryptoStream(encryptedStream, DecryptorTransform, CryptoStreamMode.Write))
                {
                    decryptStream.Write(EncryptedValue, 0, EncryptedValue.Length);
                }

                decryptedBytes = encryptedStream.ToArray();
            }

            return UTFEncoder.GetString(decryptedBytes);
        }

        /// Convert a string to a byte array.  NOTE: Normally we'd create a Byte Array from a string using an ASCII encoding (like so). 
        //      System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); 
        //      return encoding.GetBytes(str); 
        // However, this results in character values that cannot be passed in a URL.  So, instead, I just 
        // lay out all of the byte values in a long string of numbers (three per - must pad numbers less than 100). 
        public byte[] StrToByteArray(string str)
        {
            if (str.Length == 0)
                throw new Exception("Invalid string value in StrToByteArray");

            byte val;
            byte[] byteArr = new byte[str.Length / 3];
            int i = 0;
            int j = 0;
            do
            {
                val = byte.Parse(str.Substring(i, 3));
                byteArr[j++] = val;
                i += 3;
            }
            while (i < str.Length);
            return byteArr;
        }

        // Same comment as above.  Normally the conversion would use an ASCII encoding in the other direction: 
        //      System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); 
        //      return enc.GetString(byteArr);     
        public string ByteArrToString(byte[] byteArr)
        {
            byte val;
            string tempStr = "";
            for (int i = 0; i <= byteArr.GetUpperBound(0); i++)
            {
                val = byteArr[i];
                if (val < (byte)10)
                    tempStr += "00" + val.ToString();
                else if (val < (byte)100)
                    tempStr += "0" + val.ToString();
                else
                    tempStr += val.ToString();
            }
            return tempStr;
        }

編集:ご協力いただきありがとうございますが、あなたの回答では問題が明らかにならず、愚かなほど単純なものであることが判明しました。あるサーバーで暗号化された文字列を生成し、それを別のサーバーのハンドラーに渡して復号化と処理を行っていましたが、異なるサーバーで実行すると暗号化の結果が異なることが判明したため、受信サーバーはそれを復号化できませんでした。答えの 1 つが偶然このヒントに出くわしたので、それを受け入れました。

4

3 に答える 3

14

私は、CryptoStreamを閉じる前に、 FlushFinalBlockメソッドを明示的に呼び出す傾向があります。これは、暗号化メソッドで次のことを行うことを意味します。

using (CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write))
{
    cs.Write(bytes, 0, bytes.Length);
    cs.FlushFinalBlock();        
}

これを行わないと、暗号化されたデータが切り捨てられている可能性があります。これにより、「無効なパディング」シナリオが発生します。PKCS7 を使用する場合、暗号化されるデータが暗号のブロック長に揃えられている場合でも、パディングは常に存在します。

于 2010-01-22T11:53:56.373 に答える
12

何らかの理由で暗号化と復号化で同じキーまたは初期化ベクトルが使用されていない場合、無効なパディングに関するメッセージが表示されることがあります。パディングは、平文の末尾に追加されるバイト数であり、暗号化が機能するブロックの完全な数になります。PKCS7 パディングでは、各バイトは追加されたバイト数に等しいため、復号化後にいつでも削除できます。復号化により、最後のnバイトが最後のバイトの値nと等しくない文字列が生成されました(その文が意味をなすことを願っています)。だから私はあなたのすべての鍵を再確認します。

RijndaelManagedTransformまたは、あなたの場合、暗号化および復号化操作ごとにインスタンスを作成して破棄し、キーとベクトルで初期化することをお勧めします。この問題は、この変換オブジェクトを再利用することによって引き起こされる可能性が非常に高く、つまり、最初に使用した後は、正しい初期状態ではなくなっていることを意味します。

于 2010-01-22T10:40:06.513 に答える
2

これにより、URLで渡すことができない文字値が生成されます

Base64StrToByteArrayエンコーディングの代わりに独自のエンコーディングを使用している理由はありますか?

これらの変更を行う場合:

public string EncryptToString(string TextValue)
{
  return Convert.ToBase64String(Encrypt(TextValue));
}

public string DecryptToString(string TextValue)
{
  return Decrypt(Convert.FromBase64String(TextValue));
}

そうすれば、物事はずっとうまくいくはずです。

編集:
ToBase64StringとQueryStringの問題について:
独自のQueryString解析を行う場合は、最初の=-記号でのみ分割するようにする必要があります。

var myURL = "http://somewhere.com/default.aspx?encryptedID=s9W/h7Sls98sqw==&someKey=someValue";
var myQS = myURL.SubString(myURL.IndexOf("?") + 1);
var myKVPs = myQS.Split("&");
foreach (var kvp in myKVPs) {
  // It is important you specify a maximum number of 2 elements
  // since the Base64 encoded string might contain =-signs.
  var keyValue = kvp.Split("=", 2);
  var key = keyValue[0];
  var value = keyValue[1];
  if (key == "encryptedID")
    var decryptedID = myAES.DecryptToString(value);
}

このように、Base64でエンコードされている場合、QueryString内の文字を置き換える必要はありません。

于 2010-01-22T10:49:54.643 に答える