我创建了一些使用 StreamWriter 将数据导出到文本文件的小程序,然后我使用 StreamReader 将它们读回。这很好用,可以做我需要做的事情,但我想知道是否有一种方法可以保存这些信息,而用户无法有意或无意地访问或修改它。我在文本文件中的一个例子是,如果勾选了一个复选框,当你勾选它时,它会将“勾选”输出到文本文件,当程序重新打开时,我知道表单处于什么状态关门了。我显然不想继续使用文本文件。有没有人知道如何在用户无法修改的情况下轻松存储这些信息?非常感谢你。
9 回答
最简单的方法是对这个文本进行 Base-64 编码/解码。这并不安全,但会阻止临时用户修改数据。
static public string EncodeTo64(string toEncode)
{
byte[] toEncodeAsBytes
= System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode);
string returnValue
= System.Convert.ToBase64String(toEncodeAsBytes);
return returnValue;
}
static public string DecodeFrom64(string encodedData)
{
byte[] encodedDataAsBytes
= System.Convert.FromBase64String(encodedData);
string returnValue =
System.Text.ASCIIEncoding.ASCII.GetString(encodedDataAsBytes);
return returnValue;
}
编辑:真正的加密
#region Encryption
string passPhrase = "Pasword"; // can be any string
string saltValue = "sALtValue"; // can be any string
string hashAlgorithm = "SHA1"; // can be "MD5"
int passwordIterations = 7; // can be any number
string initVector = "~1B2c3D4e5F6g7H8"; // must be 16 bytes
int keySize = 256; // can be 192 or 128
private string Encrypt(string data)
{
byte[] bytes = Encoding.ASCII.GetBytes(this.initVector);
byte[] rgbSalt = Encoding.ASCII.GetBytes(this.saltValue);
byte[] buffer = Encoding.UTF8.GetBytes(data);
byte[] rgbKey = new PasswordDeriveBytes(this.passPhrase, rgbSalt, this.hashAlgorithm, this.passwordIterations).GetBytes(this.keySize / 8);
RijndaelManaged managed = new RijndaelManaged();
managed.Mode = CipherMode.CBC;
ICryptoTransform transform = managed.CreateEncryptor(rgbKey, bytes);
MemoryStream stream = new MemoryStream();
CryptoStream stream2 = new CryptoStream(stream, transform, CryptoStreamMode.Write);
stream2.Write(buffer, 0, buffer.Length);
stream2.FlushFinalBlock();
byte[] inArray = stream.ToArray();
stream.Close();
stream2.Close();
return Convert.ToBase64String(inArray);
}
private string Decrypt(string data)
{
byte[] bytes = Encoding.ASCII.GetBytes(this.initVector);
byte[] rgbSalt = Encoding.ASCII.GetBytes(this.saltValue);
byte[] buffer = Convert.FromBase64String(data);
byte[] rgbKey = new PasswordDeriveBytes(this.passPhrase, rgbSalt, this.hashAlgorithm, this.passwordIterations).GetBytes(this.keySize / 8);
RijndaelManaged managed = new RijndaelManaged();
managed.Mode = CipherMode.CBC;
ICryptoTransform transform = managed.CreateDecryptor(rgbKey, bytes);
MemoryStream stream = new MemoryStream(buffer);
CryptoStream stream2 = new CryptoStream(stream, transform, CryptoStreamMode.Read);
byte[] buffer5 = new byte[buffer.Length];
int count = stream2.Read(buffer5, 0, buffer5.Length);
stream.Close();
stream2.Close();
return Encoding.UTF8.GetString(buffer5, 0, count);
}
#endregion
您应该调用ProtectedData.Protect
以使用每用户密钥加密数据。
请注意,熟练的用户解密和修改数据并不难。
您的程序在用户机器上所做的任何事情都可以由用户完成。
您可以使用Ionic zip 库来压缩这些文本文件。如有必要,您还可以使用 Ionic zip 的功能,例如密码保护和加密。而且您仍然可以使用最初创建文件时使用的相同设置手动打开文件(使用诸如 7zip 之类的压缩应用程序)。
如果程序可以访问信息,用户通常也可以。但是,您可以生成用户不会立即理解的数据。
我将首先创建一个包含您要保存的所有状态信息的类,以隔离问题。巧合的是,BinaryFormatter
该类将允许您轻松地将此类保存和加载到文件中/从文件中加载。我不知道它的结果是否“不够可读” - 如果不是,请像 Leon 提到的那样应用 Base64 编码。
虽然您可以按照已经建议的方式对您的配置数据进行 base64 编码甚至完全加密(使用 SHA1 或 MD5),但我认为好的做法是使用处理配置数据的框架类(Configuration
在System.Configuration
命名空间下),并且它内置了加密功能数据(通过类的ProtectSection
方法ConfigurationSection
)。
首先,您应该声明并初始化一个实例:
using System.Configuration;
...
static void Main(string[] args)
{
Configuration config;
config = ConfigurationManager.OpenExeConfiguration(/*path to config file*/); //Use ConfigurationManager.OpenMachineConfiguration(/*path to config file*/) when opening machine configuration
...
之后,您需要定义一个自定义配置部分来定义您的配置(msdn 示例)
完成后,您只需要初始化自定义配置部分的实例并使用以下代码将其添加到配置文件中:
isTicked = config.Sections.Add("isTicked", customSection);
要加密您刚刚添加的部分,请使用此代码(在此处找到 VB.NET 和 C# 中的更多示例):
config.Sections["isTicked"].SectionInformation.ProtectSection("protection provider");
默认情况下内置“DPAPIProtectedConfigurationProvider”和“RSAProtectedConfigurationProvider”。
一旦要解密该部分,请使用以下代码:
config.Sections["isTicked"].SectionInformation.UnprotectSection();
强调一点-加密和解密都需要保存配置文件才能生效
要保存文件,请使用以下代码:
config.Save(); //config.SaveAs("string") is also available
可以在 msdn 中找到有关相关类和方法的更多信息,从Configuration
上面链接的类页面开始。
试试这个代码来加密和解密你的文本!我认为这很容易也很强大......
public static class Crypto
{
private static readonly byte[] IVa = new byte[] { 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 };
public static string Encrypt(this string text, string salt)
{
try
{
using (Aes aes = new AesManaged())
{
Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(Encoding.UTF8.GetString(IVa, 0, IVa.Length), Encoding.UTF8.GetBytes(salt));
aes.Key = deriveBytes.GetBytes(128 / 8);
aes.IV = aes.Key;
using (MemoryStream encryptionStream = new MemoryStream())
{
using (CryptoStream encrypt = new CryptoStream(encryptionStream, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
byte[] cleanText = Encoding.UTF8.GetBytes(text);
System.Diagnostics.Debug.WriteLine(String.Concat("Before encryption text data size: ", text.Length.ToString()));
System.Diagnostics.Debug.WriteLine(String.Concat("Before encryption byte data size: ", cleanText.Length.ToString()));
encrypt.Write(cleanText, 0, cleanText.Length);
encrypt.FlushFinalBlock();
}
byte[] encryptedData = encryptionStream.ToArray();
string encryptedText = Convert.ToBase64String(encryptedData);
System.Diagnostics.Debug.WriteLine(String.Concat("Encrypted text data size: ", encryptedText.Length.ToString()));
System.Diagnostics.Debug.WriteLine(String.Concat("Encrypted byte data size: ", encryptedData.Length.ToString()));
return encryptedText;
}
}
}
catch(Exception e)
{
return String.Empty;
}
}
public static string Decrypt(this string text, string salt)
{
try
{
using (Aes aes = new AesManaged())
{
Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(Encoding.UTF8.GetString(IVa, 0, IVa.Length), Encoding.UTF8.GetBytes(salt));
aes.Key = deriveBytes.GetBytes(128 / 8);
aes.IV = aes.Key;
using (MemoryStream decryptionStream = new MemoryStream())
{
using (CryptoStream decrypt = new CryptoStream(decryptionStream, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
byte[] encryptedData = Convert.FromBase64String(text);
System.Diagnostics.Debug.WriteLine(String.Concat("Encrypted text data size: ", text.Length.ToString()));
System.Diagnostics.Debug.WriteLine(String.Concat("Encrypted byte data size: ", encryptedData.Length.ToString()));
decrypt.Write(encryptedData, 0, encryptedData.Length);
decrypt.Flush();
}
byte[] decryptedData = decryptionStream.ToArray();
string decryptedText = Encoding.UTF8.GetString(decryptedData, 0, decryptedData.Length);
System.Diagnostics.Debug.WriteLine(String.Concat("After decryption text data size: ", decryptedText.Length.ToString()));
System.Diagnostics.Debug.WriteLine(String.Concat("After decryption byte data size: ", decryptedData.Length.ToString()));
return decryptedText;
}
}
}
catch(Exception e)
{
return String.Empty;
}
}
}
只是为了添加 Leon 答案的另一个实现,并遵循 Microsoft 文档
这是一个加密和解密字符串的类示例
public static class EncryptionExample
{
#region internal consts
internal const string passPhrase = "pass";
internal const string saltValue = "salt";
internal const string hashAlgorithm = "MD5";
internal const int passwordIterations = 3; // can be any number
internal const string initVector = "0123456789abcdf"; // must be 16 bytes
internal const int keySize = 64; // can be 192 or 256
#endregion
#region public static Methods
public static string Encrypt(string data)
{
string res = string.Empty;
try
{
byte[] bytes = Encoding.ASCII.GetBytes(initVector);
byte[] rgbSalt = Encoding.ASCII.GetBytes(saltValue);
byte[] buffer = Encoding.UTF8.GetBytes(data);
byte[] rgbKey = new PasswordDeriveBytes(passPhrase, rgbSalt, hashAlgorithm, passwordIterations).GetBytes(keySize / 8);
RijndaelManaged managed = new RijndaelManaged();
managed.Mode = CipherMode.CBC;
ICryptoTransform transform = managed.CreateEncryptor(rgbKey, bytes);
byte[] inArray = null;
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, transform, CryptoStreamMode.Write))
{
csEncrypt.Write(buffer, 0, buffer.Length);
csEncrypt.FlushFinalBlock();
inArray = msEncrypt.ToArray();
res = Convert.ToBase64String(inArray);
}
}
}
catch (Exception ex)
{
Console.WriteLine("Encrypt " + ex);
}
return res;
}
public static string Decrypt(string data)
{
string res = string.Empty;
try
{
byte[] bytes = Encoding.ASCII.GetBytes(initVector);
byte[] rgbSalt = Encoding.ASCII.GetBytes(saltValue);
byte[] buffer = Convert.FromBase64String(data);
byte[] rgbKey = new PasswordDeriveBytes(passPhrase, rgbSalt, hashAlgorithm, passwordIterations).GetBytes(keySize / 8);
RijndaelManaged managed = new RijndaelManaged();
managed.Mode = CipherMode.CBC;
ICryptoTransform transform = managed.CreateDecryptor(rgbKey, bytes);
using (MemoryStream msEncrypt = new MemoryStream(buffer))
{
using (CryptoStream csDecrypt = new CryptoStream(msEncrypt, transform, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
res = srDecrypt.ReadToEnd();
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Decrypt " + ex);
}
return res;
}
}
顺便说一句,这是我用谷歌搜索的“盐值”定义。
盐值
如果攻击者不知道密码,并试图通过蛮力攻击来猜测它,那么他尝试的每个密码都必须使用每个盐值进行尝试。因此,对于一位盐(0 或 1),这使得以这种方式破解加密的难度增加了一倍。
如this answer中所指出的,可以使用校验和来防止无意的字符串修改。
但是,生成这样的校验和非常容易,因为它们不是很多广泛使用的算法。
因此,这并不能保护您免受故意修改。
为了防止这种情况,人们使用数字签名。这允许任何人验证您的数据没有被篡改,但只有您(私人秘密的所有者)可以生成签名。
这是 C# 中的示例。
但是,正如其他人指出的那样,您需要将您的私钥嵌入到您的二进制文件中的某个位置,并且(不是那么)熟练的程序员将能够检索它,即使您混淆了您的 .net dll 或者您在单独的本机中制作它过程。
不过,这足以解决大多数问题。
如果您真的担心安全性,那么您需要在云上移动,并在您拥有的机器上执行代码。