c# - 循环中从 CryptoStream 获取字符串

标签 c# encryption random cryptography

我在从内存流中提取字符串时遇到问题。内存流用加密流装饰。看起来,除非我刷新加密流,否则我无法从内存流中读取任何内容。

我试图在 for 循环中生成多个字符串(然后将其解析为数值)。到目前为止,当加密流仍然处于事件状态时,我无法从内存流中读取数据。

正如人们所看到的,我正在尝试测量运行时间,但内存流的长度似乎始终为零。我还需要找到一种有效的方法来从我从内存流中提取的加密字节数组中获取字符串。

Stopwatch watch = new Stopwatch();

MemoryStream ms = new MemoryStream();
ICryptoTransform encryptor = aesInstance.CreateEncryptor(aesInstance.Key, aesInstance.IV);
CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
UTF8Encoding encoder = new UTF8Encoding();

int counter = (int)numericUpDown2.Value;
byte[] text;
byte[] num;

watch.Start();
for (int k = 0; k < rounds; k++) {

    text = encoder.GetBytes(counter.ToString());
    cs.Write(text, 0, text.Length);
    cs.FlushFinalBlock(); 
    num = new byte[ms.Length];
    ms.Read(num, 0, (int)num.Length);

    ms.Flush();
    counter++;
}
watch.Stop();

最佳答案

基于我在评论中所写内容的简单示例:

var lst = new List<string> {
    "Foo",
    "Bar",
    "FooBarFooBarFooBarFooBar",
    "FooBar",
};

MemoryStream ms = new MemoryStream();

var aesInstance = Aes.Create();

foreach (var str in lst)
{
    ICryptoTransform encryptor = aesInstance.CreateEncryptor(aesInstance.Key, aesInstance.IV);

    byte[] bytes = Encoding.UTF8.GetBytes(str);
    byte[] encrypted = encryptor.TransformFinalBlock(bytes, 0, bytes.Length);

    byte[] length = BitConverter.GetBytes(encrypted.Length);
    ms.Write(length, 0, length.Length);
    ms.Write(encrypted, 0, encrypted.Length);
}

ms.Position = 0;

while (ms.Position < ms.Length)
{
    ICryptoTransform decryptor = aesInstance.CreateDecryptor(aesInstance.Key, aesInstance.IV);

    byte[] length = new byte[4];
    int read = ms.Read(length, 0, length.Length);

    if (read < length.Length)
    {
        throw new Exception();
    }

    int length2 = BitConverter.ToInt32(length, 0);

    byte[] encrypted = new byte[length2];
    read = ms.Read(encrypted, 0, encrypted.Length);

    if (read < encrypted.Length)
    {
        throw new Exception();
    }

    byte[] decrypted = decryptor.TransformFinalBlock(encrypted, 0, encrypted.Length);

    string str = Encoding.UTF8.GetString(decrypted);

    Console.WriteLine("Encrypted: {0} bytes, value: {1}", encrypted.Length, str);
}

每个加密“数据包”的长度都以 Int32 形式添加到数据包前面。如果观察输出,您会发现对于给定的字符串,数据包的长度始终为 16 或 32。对于较长的字符串,它将一次增加 16(48、64、80、96...) 。请注意,CBC 模式存在错误,因此您不能正确执行两次 TransformFinalBlock ,否则解密时会出现错误。为了解决这个问题,我为每个字符串重新创建加密器/解密器。这将导致相同的字符串以相同的方式加密。因此,如果您对 "Foo" 加密两次,它们在加密流中将是相同的 XXXXXXXXXYYYYYYYY

点击率模式

正如我在评论中所写,最好的办法是有点击率模式。 CTR模式的优点是加密流与非加密流具有相同的长度,并且可以一次对输入流进行一个字节的加密/解密。利用这两个“特征”,我们可以修改加密/解密样本,甚至可以对字符串长度进行加密/解密。请注意,在 AesCtr 类中,我添加了一些基于 http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf 中的向量的测试。 ,所以实现应该是正确的。

public class AesManagedCtr : Aes
{
    private AesManaged Aes;

    public AesManagedCtr()
    {
        Aes = new AesManaged();
        Aes.Mode = CipherMode.ECB;
        Aes.Padding = PaddingMode.None;
    }

    public override byte[] IV
    {
        get
        {
            return Aes.IV;
        }
        set
        {
            Aes.IV = value;
        }
    }

    public override byte[] Key
    {
        get
        {
            return Aes.Key;
        }
        set
        {
            Aes.Key = value;
        }
    }

    public override int KeySize
    {
        get
        {
            return Aes.KeySize;
        }
        set
        {
            Aes.KeySize = value;
        }
    }

    public override CipherMode Mode
    {
        get
        {
            return Aes.Mode;
        }
        set
        {
            if (value != CipherMode.ECB)
            {
                throw new CryptographicException();
            }
        }
    }

    public override PaddingMode Padding
    {
        get
        {
            return Aes.Padding;
        }
        set
        {
            if (value != PaddingMode.None)
            {
                throw new CryptographicException();
            }
        }
    }

    public override int BlockSize
    {
        get
        {
            return 8;
        }
        set
        {
            if (value != 8)
            {
                throw new CryptographicException();
            }
        }
    }

    public override KeySizes[] LegalBlockSizes
    {
        get
        {
            return new[] { new KeySizes(BlockSize, BlockSize, 0) };
        }
    }

    public override int FeedbackSize
    {
        get
        {
            return Aes.FeedbackSize;
        }
        set
        {
            if (FeedbackSize != Aes.FeedbackSize)
            {
                throw new CryptographicException();
            }
        }
    }

    public override ICryptoTransform CreateDecryptor()
    {
        // Note that we always use the Aes.CreateEncryptor, even for
        // decrypting, because we only have to "rebuild" the encrypted
        // CTR nonce.
        return CreateEncryptor();
    }

    public override ICryptoTransform CreateDecryptor(byte[] key, byte[] iv)
    {
        // Note that we always use the Aes.CreateEncryptor, even for
        // decrypting, because we only have to "rebuild" the encrypted
        // CTR nonce.
        return CreateEncryptor(key, iv);
    }

    public override ICryptoTransform CreateEncryptor()
    {
        return new StreamCipher(Aes.CreateEncryptor(), IV);
    }

    public override ICryptoTransform CreateEncryptor(byte[] key, byte[] iv)
    {
        if (key == null)
        {
            throw new ArgumentNullException("key");
        }

        if (!ValidKeySize(key.Length * 8))
        {
            throw new ArgumentException("key");
        }

        if (iv == null)
        {
            throw new ArgumentNullException("iv");
        }

        if (iv.Length * 8 != BlockSizeValue)
        {
            throw new ArgumentException("iv");
        }

        return new StreamCipher(Aes.CreateEncryptor(key, iv), iv);
    }

    public override void GenerateIV()
    {
        Aes.GenerateIV();
    }

    public override void GenerateKey()
    {
        Aes.GenerateKey();
    }

    protected override void Dispose(bool disposing)
    {
        try
        {
            if (disposing)
            {
                Aes.Dispose();
            }
        }
        finally
        {
            base.Dispose(disposing);
        }
    }

    protected sealed class StreamCipher : ICryptoTransform
    {
        private ICryptoTransform Transform;

        private byte[] IV;
        private byte[] EncryptedIV = new byte[16];
        private int EncryptedIVOffset = 0;

        public StreamCipher(ICryptoTransform transform, byte[] iv)
        {
            Transform = transform;

            // Note that in this implementation the IV/Nonce and the 
            // Counter described by http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29
            // are additioned together in a single IV, that then is
            // incremented by 1 in a "big-endian" mode.
            IV = (byte[])iv.Clone();
            Transform.TransformBlock(IV, 0, IV.Length, EncryptedIV, 0);
        }

        public bool CanReuseTransform
        {
            get { return true; }
        }

        public bool CanTransformMultipleBlocks
        {
            get { return true; }
        }

        public int InputBlockSize
        {
            get { return 1; }
        }

        public int OutputBlockSize
        {
            get { return 1; }
        }

        public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
        {
            int count = Math.Min(inputCount, outputBuffer.Length - outputOffset);

            for (int i = 0; i < count; i++)
            {
                if (EncryptedIVOffset == EncryptedIV.Length)
                {
                    IncrementNonceAndResetOffset();
                }

                outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ EncryptedIV[EncryptedIVOffset]);
                EncryptedIVOffset++;
            }

            return count;
        }

        public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            // This method can be reused. There is no "final block" in
            // CTR mode, because characters are encrypted one by one
            byte[] outputBuffer = new byte[inputCount];
            TransformBlock(inputBuffer, inputOffset, inputCount, outputBuffer, 0);
            return outputBuffer;
        }

        public void Dispose()
        {
            if (Transform != null)
            {
                Transform.Dispose();
                Transform = null;
                IV = null;
                EncryptedIV = null;
            }

            GC.SuppressFinalize(this);
        }

        private void IncrementNonceAndResetOffset()
        {
            int i = IV.Length - 1;

            do
            {
                unchecked
                {
                    IV[i]++;
                }

                if (IV[i] != 0 || i == 0)
                {
                    break;
                }

                i--;
            }
            while (true);

            Transform.TransformBlock(IV, 0, IV.Length, EncryptedIV, 0);
            EncryptedIVOffset = 0;
        }
    }

    // A simple string-to-byte[] converter
    private static byte[] GetBytes(string str)
    {
        if (str.Length % 2 != 0)
        {
            throw new ArgumentException();
        }

        byte[] bytes = new byte[str.Length / 2];

        for (int i = 0; i < bytes.Length; i++)
        {
            bytes[i] = byte.Parse(str.Substring(i * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
        }

        return bytes;
    }

    public static void Test()
    {
        // Taken from http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
        // F.5.1 CTR-AES128.Encrypt
        // F.5.2 CTR-AES128.Decrypt 
        // F.5.3 CTR-AES192.Encrypt 
        // F.5.4 CTR-AES192.Decrypt 
        // F.5.5 CTR-AES256.Encrypt
        // F.5.6 CTR-AES256.Decrypt  

        string[] keys = new[]
        {
            // 128 bits
            "2b7e151628aed2a6abf7158809cf4f3c",
            // 192 bits
            "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
            // 256 bits
            "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
        };

        string iv = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";

        string[] plains = new[]
        {
            "6bc1bee22e409f96e93d7e117393172a",
            "ae2d8a571e03ac9c9eb76fac45af8e51",
            "30c81c46a35ce411e5fbc1191a0a52ef",
            "f69f2445df4f9b17ad2b417be66c3710",
        };

        string[][] encrypteds = new[]
        {
            // 128 bits
            new[]
            {
                "874d6191b620e3261bef6864990db6ce",
                "9806f66b7970fdff8617187bb9fffdff",
                "5ae4df3edbd5d35e5b4f09020db03eab",
                "1e031dda2fbe03d1792170a0f3009cee",
            },
            // 192 bits
            new[]
            {
                "1abc932417521ca24f2b0459fe7e6e0b",
                "090339ec0aa6faefd5ccc2c6f4ce8e94",
                "1e36b26bd1ebc670d1bd1d665620abf7",
                "4f78a7f6d29809585a97daec58c6b050",
            },
            // 256 bits
            new[]
            {
                "601ec313775789a5b7a7f504bbf3d228",
                "f443e3ca4d62b59aca84e990cacaf5c5",
                "2b0930daa23de94ce87017ba2d84988d",
                "dfc9c58db67aada613c2dd08457941a6",
            },
        };

        for (int i = 0; i < keys.Length; i++)
        {
            var aes = new AesManagedCtr();
            aes.Key = GetBytes(keys[i]);
            aes.IV = GetBytes(iv);

            Console.WriteLine("{0} bits", aes.KeySize);

            {
                Console.WriteLine("Encrypt");

                ICryptoTransform encryptor = aes.CreateEncryptor();

                var cipher = new byte[16];

                for (int j = 0; j < plains.Length; j++)
                {
                    byte[] plain = GetBytes(plains[j]);
                    encryptor.TransformBlock(plain, 0, plain.Length, cipher, 0);

                    string cipherHex = BitConverter.ToString(cipher).Replace("-", string.Empty).ToLowerInvariant();

                    if (cipherHex != encrypteds[i][j])
                    {
                        throw new Exception("Error encrypting " + j);
                    }

                    Console.WriteLine(cipherHex);
                }
            }

            Console.WriteLine();

            {
                Console.WriteLine("Decrypt");

                ICryptoTransform decryptor = aes.CreateDecryptor();

                var plain = new byte[16];

                for (int j = 0; j < encrypteds[i].Length; j++)
                {
                    byte[] encrypted = GetBytes(encrypteds[i][j]);
                    decryptor.TransformBlock(encrypted, 0, encrypted.Length, plain, 0);

                    string plainHex = BitConverter.ToString(plain).Replace("-", string.Empty).ToLowerInvariant();

                    if (plainHex != plains[j])
                    {
                        throw new Exception("Error decrypting " + j);
                    }

                    Console.WriteLine(plainHex);
                }
            }

            Console.WriteLine();
        }
    }
}

然后

var lst = new List<string> {
    "Foo",
    "Bar",
    "FooBarFooBarFooBarFooBar",
    "FooBar",
};

MemoryStream ms = new MemoryStream();

var aesInstance = new AesManagedCtr();

ICryptoTransform encryptor = aesInstance.CreateEncryptor(aesInstance.Key, aesInstance.IV);

foreach (var str in lst)
{
    byte[] bytes = Encoding.UTF8.GetBytes(str);
    byte[] length = BitConverter.GetBytes(bytes.Length);
    byte[] encryptedLength = encryptor.TransformFinalBlock(length, 0, length.Length);
    byte[] encryptedBytes = encryptor.TransformFinalBlock(bytes, 0, bytes.Length);

    ms.Write(encryptedLength, 0, encryptedLength.Length);
    ms.Write(encryptedBytes, 0, encryptedBytes.Length);
}

ms.Position = 0;

ICryptoTransform decryptor = aesInstance.CreateDecryptor(aesInstance.Key, aesInstance.IV);

while (ms.Position < ms.Length)
{
    byte[] encryptedLength = new byte[4];
    int read = ms.Read(encryptedLength, 0, encryptedLength.Length);

    if (read < encryptedLength.Length)
    {
        throw new Exception();
    }

    byte[] length = decryptor.TransformFinalBlock(encryptedLength, 0, encryptedLength.Length);

    int length2 = BitConverter.ToInt32(length, 0);

    byte[] encryptedBytes = new byte[length2];
    read = ms.Read(encryptedBytes, 0, encryptedBytes.Length);

    if (read < encryptedBytes.Length)
    {
        throw new Exception();
    }

    byte[] bytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);

    string str = Encoding.UTF8.GetString(bytes);

    Console.WriteLine("Encrypted: {0} bytes, value: {1}", encryptedBytes.Length, str);
} 

请注意与其他示例的差异:这里我们重用加密器/解密器,因为这样每个“ block ”都会在链中加密,即使相同的字符串重复两次,加密版本会不同。

关于c# - 循环中从 CryptoStream 获取字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29561944/

相关文章:

c# - 来自 Mono 的 TypeLoadException

C#,TextBox '&' char 更改为 amp;

c# - 工作流基础多个跟踪参与者不能一致工作

c++ - crypto++ 因文件中的字符串而失败?

c# - 使用相同的 System.Random 算法生成线性伪随机数

c# - 阻止 Visual Studio 在 switch-case 语句中添加换行符

java - 如何在不使用 keytool 进行 AES 加密的情况下添加、删除或显示 keystore 中的 key ?

iphone - iOS:在我的代码中硬编码 AES key 是否安全?

javascript - 如果 xml 中的问题超过 204,则随机不起作用

java - 为什么此代码不生成随机代码?