在循环中从 CryptoStream 获取字符串

Get string from CryptoStream in loop

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

我正在尝试在 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);
}

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

点击率模式

正如我在评论中所写,最好的办法是拥有点击率模式。 CTR模式的优点是加密流与非加密流长度相同,输入流一次可以encrypted/decrypted一个字节。使用这两个 "characteristics" 我们可以将 encryption/decryption 示例修改为 encrypt/decrypt 甚至字符串长度。请注意,在 AesCtr class 中,我根据 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);
} 

注意与另一个例子的区别:这里我们重用了encryptor/decryptor,因为这样每个"block"都在一个链中加密,即使相同字符串重复两次,加密后的版本会不一样