如何重新编码函数以使用字符串而不是文件流
How to recode functions to use Strings instad of FileStreams
我正在构建一个需要加密保存等的游戏。所以我正在制作一些函数来帮助我加密它们。然而,我目前唯一的功能是使用 FileStreams 进行输入和输出,但我想改用字符串。这些函数在文件上运行良好,但我无法从 FileStreams 交换到 MemoryStreams 再到字符串。
注意:我已经删除了不相关的代码。 OurCodeWorld.GenerateRandomSalt()
100% 有效,使用 FileStream 加密进行测试
完整代码:
Program.cs:
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
namespace Encryption_test
{
class Program
{
static public string encryptedExtension = ".aes";
static public string decryptedExtension = ".decrypted";
static void Main(string[] args)
{
string path = Environment.CurrentDirectory;
Log($"Current path: {path}");
string filePath = $"{path}/unencrypted.txt";
string message =
@"Hello world!
This is my message. 1234";
RunWhosebugString();
//Sleep forever...zzz
Thread.Sleep(10000);
Console.WriteLine();
float TicksToMs(long ticks)
{
return ticks / (float)Stopwatch.Frequency * 1000F;
}
void RunWhosebugString()
{
byte[] salt = OurCodeWorld.GenerateRandomSalt();
int iterations = 1024;
string password = "";
string enc = Whosebug.EncryptString(message, password, salt, iterations);
Log($"Enc: {enc}");
string dec = Whosebug.DecryptString(enc, password, salt, iterations);
Log($"Dec: {dec}");
}
private static void WriteFile(string path, string value)
{
FileStream _file = File.OpenWrite(path);
byte[] info = new UTF8Encoding(true).GetBytes(value);
_file.Write(info, 0, info.Length);
_file.Close();
}
private static string ReadFile(string filePath, long length = long.MaxValue)
{
FileStream _file = File.OpenRead(filePath);
if (length == long.MaxValue)
length = _file.Length;
byte[] b = new byte[length];
UTF8Encoding temp = new UTF8Encoding(true);
_file.Read(b, 0, b.Length);
_file.Close();
return temp.GetString(b);
}
private static void DeleteFile(string path)
{
File.Delete(path);
}
private static void CreateFile(string path)
{
if (File.Exists(path))
DeleteFile(path);
FileStream a = File.Open(path, FileMode.Create);
a.Close();
}
static void Log(string message)
{
Console.WriteLine(message);
}
}
}
Whosebug.cs:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class Whosebug
{
// Rfc2898DeriveBytes constants:
/// <summary>Decrypt a file.</summary>
/// <remarks>NB: "Padding is invalid and cannot be removed." is the Universal CryptoServices error. Make sure the password, salt and iterations are correct before getting nervous.</remarks>
/// <param name="sourceFilename">The full path and name of the file to be decrypted.</param>
/// <param name="destinationFilename">The full path and name of the file to be output.</param>
/// <param name="password">The password for the decryption.</param>
/// <param name="salt">The salt to be applied to the password.</param>
/// <param name="iterations">The number of iterations Rfc2898DeriveBytes should use before generating the key and initialization vector for the decryption.</param>
public static void DecryptFile(string sourceFilename, string destinationFilename, string password, byte[] salt, int iterations)
{
AesManaged aes = new AesManaged();
aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
aes.KeySize = aes.LegalKeySizes[0].MaxSize;
// NB: Rfc2898DeriveBytes initialization and subsequent calls to GetBytes must be eactly the same, including order, on both the encryption and decryption sides.
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
aes.Key = key.GetBytes(aes.KeySize / 8);
aes.IV = key.GetBytes(aes.BlockSize / 8);
aes.Mode = CipherMode.CBC;
ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);
using (FileStream destination = new FileStream(destinationFilename, FileMode.Create, FileAccess.Write, FileShare.None))
{
using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
{
try
{
using (FileStream source = new FileStream(sourceFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
source.CopyTo(cryptoStream);
}
}
catch (CryptographicException exception)
{
if (exception.Message == "Padding is invalid and cannot be removed.")
throw new ApplicationException("Universal Microsoft Cryptographic Exception (Not to be believed!)", exception);
else
throw;
}
}
}
}
/// <summary>Encrypt a file.</summary>
/// <param name="sourceFilename">The full path and name of the file to be encrypted.</param>
/// <param name="destinationFilename">The full path and name of the file to be output.</param>
/// <param name="password">The password for the encryption.</param>
/// <param name="salt">The salt to be applied to the password.</param>
/// <param name="iterations">The number of iterations Rfc2898DeriveBytes should use before generating the key and initialization vector for the decryption.</param>
public static void EncryptFile(string sourceFilename, string destinationFilename, string password, byte[] salt, int iterations)
{
AesManaged aes = new AesManaged();
aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
aes.KeySize = aes.LegalKeySizes[0].MaxSize;
// NB: Rfc2898DeriveBytes initialization and subsequent calls to GetBytes must be eactly the same, including order, on both the encryption and decryption sides.
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
aes.Key = key.GetBytes(aes.KeySize / 8);
aes.IV = key.GetBytes(aes.BlockSize / 8);
aes.Mode = CipherMode.CBC;
ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);
using (FileStream destination = new FileStream(destinationFilename, FileMode.Create, FileAccess.Write, FileShare.None))
{
using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
{
using (FileStream source = new FileStream(sourceFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
source.CopyTo(cryptoStream);
}
}
}
}
//THIS IS MY CODE
public static string EncryptString(string inputString, string password, byte[] salt, int iterations)
{
AesManaged aes = new AesManaged();
aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
aes.KeySize = aes.LegalKeySizes[0].MaxSize;
// NB: Rfc2898DeriveBytes initialization and subsequent calls to GetBytes must be eactly the same, including order, on both the encryption and decryption sides.
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
aes.Key = key.GetBytes(aes.KeySize / 8);
aes.IV = key.GetBytes(aes.BlockSize / 8);
aes.Mode = CipherMode.CBC;
ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);
var source = StringToStream(inputString);
var output = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(output, transform, CryptoStreamMode.Write);
source.CopyTo(cryptoStream);
return StreamToString(output);
}
public static string DecryptString(string inputString, string password, byte[] salt, int iterations)
{
AesManaged aes = new AesManaged();
aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
aes.KeySize = aes.LegalKeySizes[0].MaxSize;
// NB: Rfc2898DeriveBytes initialization and subsequent calls to GetBytes must be eactly the same, including order, on both the encryption and decryption sides.
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
aes.Key = key.GetBytes(aes.KeySize / 8);
aes.IV = key.GetBytes(aes.BlockSize / 8);
aes.Mode = CipherMode.CBC;
ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);
var source = StringToStream(inputString);
var output = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(output, transform, CryptoStreamMode.Write);
source.CopyTo(cryptoStream);
return StreamToString(output);
}
public static Stream StringToStream(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
public static string StreamToString(Stream s)
{
s.Position = 0;
byte[] buffer = new byte[s.Length];
s.Read(buffer, 0, (int)s.Length);
return Encoding.Default.GetString(buffer);
}
}
我已经测试了 StreamToString
和 StringToStream
方法,它们工作正常。我在运行DecryptString
的时候没有输出,函数returns乱码,经常看类似这样的Dec: ?K???
?@?????n?l?r????T?
字符串和二进制数据之间的转换是用andEncoding
完成的。编码决定了字符将如何转换为二进制数据。常见的编码是 Unicode、UTF8 或 ASCII。如果你想要可预测的结果,你需要对这两种转换使用特定的编码。
在 StringToStream
中,您根本没有指定编码,而在 StreamToString
中使用 Encoding.Default
。 Encoding.Default
不提供可预测的结果,因为它使用当前代码页(如果您在 Windows 系统上 运行)。所以你不能确定你在两种方法中使用相同的编码。
解决办法是在两端提供相同的编码:
public static Stream StringToStream(string s)
{
byte[] buffer = Encoding.UTF8.GetBytes(s);
return new MemoryStream(buffer);
}
public static string StreamToString(Stream s)
{
s.Position = 0;
byte[] buffer = new byte[s.Length];
s.Read(buffer, 0, (int)s.Length);
return Encoding.UTF8.GetString(buffer);
}
我测试了代码。有两个问题:
- 您获取加密字节并使用编码将它们转换为字符串。这个过程通常会失败,因为您不能获取任意字节并将它们转换为字符串。字节和字符串之间的映射不是1:1。很可能,您根本不应该在这里使用字符串。相反,以字节格式保存数据并将这些字节写入文件。方法签名应该像
byte[] Encrypt(byte[] input, ...)
。加密代码没有字符串转换业务。
EncryptString
必须使用 FlushFinalBlock
刷新加密流。如果不这样做,最后的数据将会丢失。
我正在构建一个需要加密保存等的游戏。所以我正在制作一些函数来帮助我加密它们。然而,我目前唯一的功能是使用 FileStreams 进行输入和输出,但我想改用字符串。这些函数在文件上运行良好,但我无法从 FileStreams 交换到 MemoryStreams 再到字符串。
注意:我已经删除了不相关的代码。 OurCodeWorld.GenerateRandomSalt()
100% 有效,使用 FileStream 加密进行测试
完整代码:
Program.cs:
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
namespace Encryption_test
{
class Program
{
static public string encryptedExtension = ".aes";
static public string decryptedExtension = ".decrypted";
static void Main(string[] args)
{
string path = Environment.CurrentDirectory;
Log($"Current path: {path}");
string filePath = $"{path}/unencrypted.txt";
string message =
@"Hello world!
This is my message. 1234";
RunWhosebugString();
//Sleep forever...zzz
Thread.Sleep(10000);
Console.WriteLine();
float TicksToMs(long ticks)
{
return ticks / (float)Stopwatch.Frequency * 1000F;
}
void RunWhosebugString()
{
byte[] salt = OurCodeWorld.GenerateRandomSalt();
int iterations = 1024;
string password = "";
string enc = Whosebug.EncryptString(message, password, salt, iterations);
Log($"Enc: {enc}");
string dec = Whosebug.DecryptString(enc, password, salt, iterations);
Log($"Dec: {dec}");
}
private static void WriteFile(string path, string value)
{
FileStream _file = File.OpenWrite(path);
byte[] info = new UTF8Encoding(true).GetBytes(value);
_file.Write(info, 0, info.Length);
_file.Close();
}
private static string ReadFile(string filePath, long length = long.MaxValue)
{
FileStream _file = File.OpenRead(filePath);
if (length == long.MaxValue)
length = _file.Length;
byte[] b = new byte[length];
UTF8Encoding temp = new UTF8Encoding(true);
_file.Read(b, 0, b.Length);
_file.Close();
return temp.GetString(b);
}
private static void DeleteFile(string path)
{
File.Delete(path);
}
private static void CreateFile(string path)
{
if (File.Exists(path))
DeleteFile(path);
FileStream a = File.Open(path, FileMode.Create);
a.Close();
}
static void Log(string message)
{
Console.WriteLine(message);
}
}
}
Whosebug.cs:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class Whosebug
{
// Rfc2898DeriveBytes constants:
/// <summary>Decrypt a file.</summary>
/// <remarks>NB: "Padding is invalid and cannot be removed." is the Universal CryptoServices error. Make sure the password, salt and iterations are correct before getting nervous.</remarks>
/// <param name="sourceFilename">The full path and name of the file to be decrypted.</param>
/// <param name="destinationFilename">The full path and name of the file to be output.</param>
/// <param name="password">The password for the decryption.</param>
/// <param name="salt">The salt to be applied to the password.</param>
/// <param name="iterations">The number of iterations Rfc2898DeriveBytes should use before generating the key and initialization vector for the decryption.</param>
public static void DecryptFile(string sourceFilename, string destinationFilename, string password, byte[] salt, int iterations)
{
AesManaged aes = new AesManaged();
aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
aes.KeySize = aes.LegalKeySizes[0].MaxSize;
// NB: Rfc2898DeriveBytes initialization and subsequent calls to GetBytes must be eactly the same, including order, on both the encryption and decryption sides.
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
aes.Key = key.GetBytes(aes.KeySize / 8);
aes.IV = key.GetBytes(aes.BlockSize / 8);
aes.Mode = CipherMode.CBC;
ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);
using (FileStream destination = new FileStream(destinationFilename, FileMode.Create, FileAccess.Write, FileShare.None))
{
using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
{
try
{
using (FileStream source = new FileStream(sourceFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
source.CopyTo(cryptoStream);
}
}
catch (CryptographicException exception)
{
if (exception.Message == "Padding is invalid and cannot be removed.")
throw new ApplicationException("Universal Microsoft Cryptographic Exception (Not to be believed!)", exception);
else
throw;
}
}
}
}
/// <summary>Encrypt a file.</summary>
/// <param name="sourceFilename">The full path and name of the file to be encrypted.</param>
/// <param name="destinationFilename">The full path and name of the file to be output.</param>
/// <param name="password">The password for the encryption.</param>
/// <param name="salt">The salt to be applied to the password.</param>
/// <param name="iterations">The number of iterations Rfc2898DeriveBytes should use before generating the key and initialization vector for the decryption.</param>
public static void EncryptFile(string sourceFilename, string destinationFilename, string password, byte[] salt, int iterations)
{
AesManaged aes = new AesManaged();
aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
aes.KeySize = aes.LegalKeySizes[0].MaxSize;
// NB: Rfc2898DeriveBytes initialization and subsequent calls to GetBytes must be eactly the same, including order, on both the encryption and decryption sides.
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
aes.Key = key.GetBytes(aes.KeySize / 8);
aes.IV = key.GetBytes(aes.BlockSize / 8);
aes.Mode = CipherMode.CBC;
ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);
using (FileStream destination = new FileStream(destinationFilename, FileMode.Create, FileAccess.Write, FileShare.None))
{
using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
{
using (FileStream source = new FileStream(sourceFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
source.CopyTo(cryptoStream);
}
}
}
}
//THIS IS MY CODE
public static string EncryptString(string inputString, string password, byte[] salt, int iterations)
{
AesManaged aes = new AesManaged();
aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
aes.KeySize = aes.LegalKeySizes[0].MaxSize;
// NB: Rfc2898DeriveBytes initialization and subsequent calls to GetBytes must be eactly the same, including order, on both the encryption and decryption sides.
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
aes.Key = key.GetBytes(aes.KeySize / 8);
aes.IV = key.GetBytes(aes.BlockSize / 8);
aes.Mode = CipherMode.CBC;
ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);
var source = StringToStream(inputString);
var output = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(output, transform, CryptoStreamMode.Write);
source.CopyTo(cryptoStream);
return StreamToString(output);
}
public static string DecryptString(string inputString, string password, byte[] salt, int iterations)
{
AesManaged aes = new AesManaged();
aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
aes.KeySize = aes.LegalKeySizes[0].MaxSize;
// NB: Rfc2898DeriveBytes initialization and subsequent calls to GetBytes must be eactly the same, including order, on both the encryption and decryption sides.
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
aes.Key = key.GetBytes(aes.KeySize / 8);
aes.IV = key.GetBytes(aes.BlockSize / 8);
aes.Mode = CipherMode.CBC;
ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);
var source = StringToStream(inputString);
var output = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(output, transform, CryptoStreamMode.Write);
source.CopyTo(cryptoStream);
return StreamToString(output);
}
public static Stream StringToStream(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
public static string StreamToString(Stream s)
{
s.Position = 0;
byte[] buffer = new byte[s.Length];
s.Read(buffer, 0, (int)s.Length);
return Encoding.Default.GetString(buffer);
}
}
我已经测试了 StreamToString
和 StringToStream
方法,它们工作正常。我在运行DecryptString
的时候没有输出,函数returns乱码,经常看类似这样的Dec: ?K???
?@?????n?l?r????T?
字符串和二进制数据之间的转换是用andEncoding
完成的。编码决定了字符将如何转换为二进制数据。常见的编码是 Unicode、UTF8 或 ASCII。如果你想要可预测的结果,你需要对这两种转换使用特定的编码。
在 StringToStream
中,您根本没有指定编码,而在 StreamToString
中使用 Encoding.Default
。 Encoding.Default
不提供可预测的结果,因为它使用当前代码页(如果您在 Windows 系统上 运行)。所以你不能确定你在两种方法中使用相同的编码。
解决办法是在两端提供相同的编码:
public static Stream StringToStream(string s)
{
byte[] buffer = Encoding.UTF8.GetBytes(s);
return new MemoryStream(buffer);
}
public static string StreamToString(Stream s)
{
s.Position = 0;
byte[] buffer = new byte[s.Length];
s.Read(buffer, 0, (int)s.Length);
return Encoding.UTF8.GetString(buffer);
}
我测试了代码。有两个问题:
- 您获取加密字节并使用编码将它们转换为字符串。这个过程通常会失败,因为您不能获取任意字节并将它们转换为字符串。字节和字符串之间的映射不是1:1。很可能,您根本不应该在这里使用字符串。相反,以字节格式保存数据并将这些字节写入文件。方法签名应该像
byte[] Encrypt(byte[] input, ...)
。加密代码没有字符串转换业务。 EncryptString
必须使用FlushFinalBlock
刷新加密流。如果不这样做,最后的数据将会丢失。