我在从内存流中提取字符串时遇到问题。内存流使用加密流进行修饰。看来,除非我清除加密流,否则我无法从内存流中读取任何内容。
我试图在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();
答案 0 :(得分:2)
基于我在评论中所写的内容的简单示例:
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
两次,否则您将在解密时遇到错误。为了解决这个问题,我正在为每个字符串重新创建encryptor
/ decryptor
。这将导致相同的字符串以相同的方式加密。因此,如果您对"Foo"
进行两次加密,则它们将与加密流中的XXXXXXXXXYYYYYYYY
相同。
点击率模式
正如我在评论中写的那样,最好的办法是采用CTR模式。 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);
}
注意与其他示例的区别:这里我们重用encryptor
/ decryptor
,因为这样每个“块”都在一个链中加密,即使相同的字符串重复两次,加密版本会有所不同。