3

C# (VS2012、.NET 4.5) でのテキストの暗号化と復号化に問題があります。具体的には、文字列を暗号化してから復号化すると、出力は入力と同じではありません。ただし、奇妙なことに、暗号化された出力をコピーして文字列リテラルとしてハードコーディングすると、復号化が機能します。次のコード サンプルは、問題を示しています。私は何を間違っていますか?

var key = new Rfc2898DeriveBytes("test password", Encoding.Unicode.GetBytes("test salt"));
var provider = new AesCryptoServiceProvider { Padding = PaddingMode.PKCS7, KeySize = 256 };
var keyBytes = key.GetBytes(provider.KeySize >> 3);
var ivBytes = key.GetBytes(provider.BlockSize >> 3);
var encryptor = provider.CreateEncryptor(keyBytes, ivBytes);
var decryptor = provider.CreateDecryptor(keyBytes, ivBytes);

var testStringBytes = Encoding.Unicode.GetBytes("test string");
var testStringEncrypted = Convert.ToBase64String(encryptor.TransformFinalBlock(testStringBytes, 0, testStringBytes.Length));

//Prove that the encryption has resulted in the following string
Debug.WriteLine(testStringEncrypted == "cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc="); //Result: True

//Decrypt the encrypted text from a hardcoded string literal
var encryptedBytes = Convert.FromBase64String("cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc=");
var testStringDecrypted = Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length));

//Decrypt the encrypted text from the string result of the encryption process
var encryptedBytes2 = Convert.FromBase64String(testStringEncrypted);
var testStringDecrypted2 = Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes2, 0, encryptedBytes2.Length));

//encryptedBytes and encryptedBytes2 should be identical, so they should result in the same decrypted text - but they don't: 
Debug.WriteLine(testStringDecrypted == "test string"); //Result: True
Debug.WriteLine(testStringDecrypted2 == "test string"); //Result: FALSE
//testStringDecrypted2 is now "૱﷜ୱᵪ㭈盐æing". Curiously, the last three letters are the same.
//WTF?
4

3 に答える 3

2

どちらの場合も同じ入力を使用していますが、問題は、decryptor.TransformFinalBlock() の動作が最初に呼び出された後に変化することです。値が文字列リテラルであるか変数であるかに違いはありません。このページは、デクリプターが最初の使用後に初期状態に「リセット」していることを示唆しているようです。

http://www.pcreview.co.uk/forums/icryptotransform-transformfinalblock-behavior-bug-t1233029.html

provider.CreateDecryptor(keyBytes, ivBytes)やりたい復号化ごとに新しい復号化子を取得するために再呼び出しすることで、これを回避できるようです。

        //Decrypt the encrypted text from a hardcoded string literal
        var encryptedBytes = Convert.FromBase64String("cc1zurZinx4yxeSB0XDzVziEUNJlFXsLzD2p9TWnxEc=");
        var testStringDecrypted = Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length));

        decryptor = provider.CreateDecryptor(keyBytes, ivBytes);

        //Decrypt the encrypted text from the string result of the encryption process
        var encryptedBytes2 = Convert.FromBase64String(testStringEncrypted);
        var testStringDecrypted2 = Encoding.Unicode.GetString(decryptor.TransformFinalBlock(encryptedBytes2, 0, encryptedBytes2.Length));
于 2013-01-05T20:11:26.827 に答える
1

コメントで述べたように、それはデクリプターの再利用の問題であり、おそらく最初の復号化からの最後のブロックがどこかの状態にあるため、最初から始めているわけではなく、奇妙な結果が得られていると思います。

先日、実際に AES 文字列の暗号化/復号化を作成する必要がありました。これは、ユニット テスト (Xunit が必要) と共に、ここに含めました。

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

public interface IStringEncryptor {
    string EncryptString(string plainText);
    string DecryptString(string encryptedText);
}

public class AESStringEncryptor : IStringEncryptor {
    private readonly Encoding _encoding;
    private readonly byte[] _key;
    private readonly Rfc2898DeriveBytes _passwordDeriveBytes;
    private readonly byte[] _salt;

    /// <summary>
    /// Overload of full constructor that uses UTF8Encoding as the default encoding.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="salt"></param>
    public AESStringEncryptor(string key, string salt)
        : this(key, salt, new UTF8Encoding()) {
    }

    public AESStringEncryptor(string key, string salt, Encoding encoding) {
        _encoding = encoding;
        _passwordDeriveBytes = new Rfc2898DeriveBytes(key, _encoding.GetBytes(salt));
        _key = _passwordDeriveBytes.GetBytes(32);
        _salt = _passwordDeriveBytes.GetBytes(16);
    }

    /// <summary>
    /// Encrypts any string to a Base64 string
    /// </summary>
    /// <param name="plainText"></param>
    /// <exception cref="ArgumentNullException">String to encrypt cannot be null or empty.</exception>
    /// <returns>A Base64 string representing the encrypted version of the plainText</returns>
    public string EncryptString(string plainText) {
        if (string.IsNullOrEmpty(plainText)) {
            throw new ArgumentNullException("plainText");
        }

        using (var alg = new RijndaelManaged { BlockSize = 128, FeedbackSize = 128, Key = _key, IV = _salt })
        using (var ms = new MemoryStream())
        using (var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write)) {
            var plainTextBytes = _encoding.GetBytes(plainText);

            cs.Write(plainTextBytes, 0, plainTextBytes.Length);
            cs.FlushFinalBlock();

            return Convert.ToBase64String(ms.ToArray());
        }
    }

    /// <summary>
    /// Decrypts a Base64 string to the original plainText in the given Encoding
    /// </summary>
    /// <param name="encryptedText">A Base64 string representing the encrypted version of the plainText</param>
    /// <exception cref="ArgumentNullException">String to decrypt cannot be null or empty.</exception>
    /// <exception cref="CryptographicException">Thrown if password, salt, or encoding is different from original encryption.</exception>
    /// <returns>A string encoded</returns>
    public string DecryptString(string encryptedText) {
        if (string.IsNullOrEmpty(encryptedText)) {
            throw new ArgumentNullException("encryptedText");
        }

        using (var alg = new RijndaelManaged { BlockSize = 128, FeedbackSize = 128, Key = _key, IV = _salt })
        using (var ms = new MemoryStream())
        using (var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write)) {
            var encryptedTextBytes = Convert.FromBase64String(encryptedText);

            cs.Write(encryptedTextBytes, 0, encryptedTextBytes.Length);
            cs.FlushFinalBlock();

            return _encoding.GetString(ms.ToArray());
        }
    }
}

public class AESStringEncryptorTest {
    private const string Password = "TestPassword";
    private const string Salt = "TestSalt";

    private const string Plaintext = "This is a test";

    [Fact]
    public void EncryptionAndDecryptionWorkCorrectly() {
        var aesStringEncryptor = new AESStringEncryptor(Password, Salt);

        string encryptedText = aesStringEncryptor.EncryptString(Plaintext);

        Assert.NotEqual(Plaintext, encryptedText);

        var aesStringDecryptor = new AESStringEncryptor(Password, Salt);

        string decryptedText = aesStringDecryptor.DecryptString(encryptedText);

        Assert.Equal(Plaintext, decryptedText);
    }

    [Fact]
    public void EncodingsWorkWhenSame()
    {
        var aesStringEncryptor = new AESStringEncryptor(Password, Salt, Encoding.ASCII);

        string encryptedText = aesStringEncryptor.EncryptString(Plaintext);

        Assert.NotEqual(Plaintext, encryptedText);

        var aesStringDecryptor = new AESStringEncryptor(Password, Salt, Encoding.ASCII);

        string decryptedText = aesStringDecryptor.DecryptString(encryptedText);

        Assert.Equal(Plaintext, decryptedText);
    }

    [Fact]
    public void EncodingsFailWhenDifferent() {
        var aesStringEncryptor = new AESStringEncryptor(Password, Salt, Encoding.UTF32);

        string encryptedText = aesStringEncryptor.EncryptString(Plaintext);

        Assert.NotEqual(Plaintext, encryptedText);

        var aesStringDecryptor = new AESStringEncryptor(Password, Salt, Encoding.UTF8);

        Assert.Throws<CryptographicException>(() => aesStringDecryptor.DecryptString(encryptedText));
    }

    [Fact]
    public void EncryptionAndDecryptionWithWrongPasswordFails()
    {
        var aes = new AESStringEncryptor(Password, Salt);

        string encryptedText = aes.EncryptString(Plaintext);

        Assert.NotEqual(Plaintext, encryptedText);

        var badAes = new AESStringEncryptor(Password.ToLowerInvariant(), Salt);

        Assert.Throws<CryptographicException>(() => badAes.DecryptString(encryptedText));
    }

    [Fact]
    public void EncryptionAndDecryptionWithWrongSaltFails()
    {
        var aes = new AESStringEncryptor(Password, Salt);

        string encryptedText = aes.EncryptString(Plaintext);

        Assert.NotEqual(Plaintext, encryptedText);

        var badAes = new AESStringEncryptor(Password, Salt.ToLowerInvariant());

        Assert.Throws<CryptographicException>(() => badAes.DecryptString(encryptedText));
    }
}
于 2013-01-05T20:57:08.443 に答える