使.txt文件无法阅读/编辑

37

我有一个程序,它会保存一个包含最高分的小型 .txt 文件:

 // Create a file to write to. 
string createHighscore = _higscore + Environment.NewLine;
File.WriteAllText(path, createText);

// Open the file to read from. 
string createHighscore = File.ReadAllText(path);

问题在于用户可以使用文本编辑器轻松地编辑文件。因此,我希望将文件设为不可读/不可编辑或加密

我的想法是将数据保存在资源文件中,但我能否在资源文件中写入数据?或者将其保存为.dll文件,进行加密/解密或查找MD5散列值。


8
如果在写入文件之前对数据进行加密,你就无法使文件不可读/不可编辑,但是可以让用户更难修改。请注意,在这种情况下使用 md5 是无用的,因为你将无法从 md5 哈希中解密数据。 - Andrey Korneyev
2
你可以使用备用数据流将它附加到文件上,在这里你可以找到一个C#实现,这样文件就不会在资源管理器中显示了。 - AntiHeadshot
5
为什么不直接将其写成整数的二进制表示,而是要写成字符串呢?这样你已经超越了随意编辑的范围,到了“必须使用十六进制编辑器”的地步。如果你的程序能读取它,其他程序也可以,再往下编辑就是徒劳无益的。 - Damien_The_Unbeliever
7
请给我们提供更多背景信息。如果您的程序已经连接到服务器(是否为MVC?WebForms?),则将其写入服务器即可解决问题。如果没有,而且您已经有了一个数据库,请将其写入数据库并锁定数据库。如果将其写入文件,则可以进行编辑。我不知道这个程序有多“重要”,高分数意味着什么(例如在游戏中是否转化为现金奖励?)-但人们总是会找到方法,除非它完全无法触及。 - user1017882
3
只需使用二进制序列化器,它很难被编辑。虽然不安全,但对普通用户而言不容易编辑。 - Alex
显示剩余9条评论
10个回答

60

无法阻止用户修改文件。这是他们的电脑,所以他们可以随心所欲地使用(这就是为什么整个数字版权管理问题很困难的原因)。

由于您说您正在使用该文件来保存高分数,因此您有几个替代方案。请注意,正如之前所述,没有任何方法可以阻止一个非常坚决的攻击者篡改价值:由于您的应用程序正在用户计算机上运行,因此他可以简单地反编译它,查看您如何保护价值(获取任何在该过程中使用的秘密),并采取相应行动。但是,如果您愿意反编译应用程序,找出所使用的保护方案,并想出一个脚本/补丁来避开它,只为了改变只有您能看到的数字,那么,请去做吧?

混淆内容

这将防止用户直接编辑文件,但一旦混淆算法被知道,它就无法阻止用户进行编辑。

var plaintext = Encoding.UTF8.GetBytes("Hello, world.");
var encodedtext = Convert.ToBase64String(plaintext);

将密文保存到文件中,并在读取文件时反转此过程。

对内容进行签名

这不会防止用户编辑文件或查看其内容(但您不需要担心,高分并不是秘密),但您将能够检测到用户是否篡改了它。

var key = Encoding.UTF8.GetBytes("My secret key");
using (var algorithm = new HMACSHA512(key))
{
    var payload = Encoding.UTF8.GetBytes("Hello, world.");
    var binaryHash = algorithm.ComputeHash(payload);
    var stringHash = Convert.ToBase64String(binaryHash);
}

将负载和哈希值都保存到文件中,然后在读取文件时检查保存的哈希值是否与新计算的哈希值匹配。您的密钥必须保密。

加密内容

利用.NET的加密库,在保存前对内容进行加密,读取文件时解密。

请谨慎使用以下示例,并花费适当时间理解每个步骤,再实现它(是的,您将为琐碎的原因使用它,但未来的您或其他人可能会用到它)。特别注意如何生成IV和密钥。

// The initialization vector MUST be changed every time a plaintext is encrypted.
// The initialization vector MUST NOT be reused a second time.
// The initialization vector CAN be saved along the ciphertext.
// See https://en.wikipedia.org/wiki/Initialization_vector for more information.
var iv = Convert.FromBase64String("9iAwvNddQvAAfLSJb+JG1A==");

// The encryption key CAN be the same for every encryption.
// The encryption key MUST NOT be saved along the ciphertext.
var key = Convert.FromBase64String("UN8/gxM+6fGD7CdAGLhgnrF0S35qQ88p+Sr9k1tzKpM=");

using (var algorithm = new AesManaged())
{
    algorithm.IV = iv;
    algorithm.Key = key;

    byte[] ciphertext;

    using (var memoryStream = new MemoryStream())
    {
        using (var encryptor = algorithm.CreateEncryptor())
        {
            using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
            {
                using (var streamWriter = new StreamWriter(cryptoStream))
                {
                    streamWriter.Write("MySuperSecretHighScore");
                }
            }
        }

        ciphertext = memoryStream.ToArray();
    }

    // Now you can serialize the ciphertext however you like.
    // Do remember to tag along the initialization vector,
    // otherwise you'll never be able to decrypt it.

    // In a real world implementation you should set algorithm.IV,
    // algorithm.Key and ciphertext, since this is an example we're
    // re-using the existing variables.
    using (var memoryStream = new MemoryStream(ciphertext))
    {
        using (var decryptor = algorithm.CreateDecryptor())
        {
            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
            {
                using (var streamReader = new StreamReader(cryptoStream))
                {
                    // You have your "MySuperSecretHighScore" back.
                    var plaintext = streamReader.ReadToEnd();
                }
            }
        }
    }
}

4
请勿使用“密文”来描述ToBase64String的输出。 "密文"是加密的输出,而Base 64编码不是。请注意保持原意并简明易懂。 - Damien_The_Unbeliever
3
好的,明白了。是的,稍微不那么平淡的文本更好吗? - Albireo
4
“编码文本”更好,因为那正是它的本质。 - alroc
4
当任何人可以提取你的密钥并签名/加密他们自己版本的文件时,所有这些签名/加密的东西都无法保护你。 - user2357112
4
@njzk2 OP不需要安全性(如他在评论中所述)。他只是想要一种快速的方法来防止用户调整自己的分数。序列化数据是防止99%用户这样做的好方法。即使是高级用户也必须非常关心才能弄清楚如何去反序列化数据(前提是他们无法访问源代码)......因此,这超出了大多数玩家的范围。如果有人非常在意通过所有麻烦来解决这个问题,那么任何安全计划都无法阻止他们。这是他们的电脑,他们可以随心所欲。 - SnakeDoc
显示剩余5条评论

12

如果您似乎寻求相对较低的安全性,我实际上会建议选择校验和。一些伪代码:

string toWrite = score + "|" + md5(score+"myKey") + Environment.NewLine

如果得分为100,那么就会变成

 

100|a6b6b0a8e56e42d8dac51a4812def434

为确保用户没有篡改文件,您可以使用以下方法:

string[] split = readString().split("|");
if (split[1] != md5(split[0]+"myKey")){
     alert("No messing with the scores!");
}else{
     alert("Your score is "+split[0]);
}

当然,一旦有人知道了你的密钥,他们可以随意更改它,但这超出了本问题的范围。任何加密/解密机制都存在同样的风险。

如下方的评论中提到的问题之一是,一旦有人通过暴力破解找到了你的密钥,他们可以分享它,每个人都可以轻松地更改他们的文件。解决这个问题的方法是向密钥添加计算机特定的内容。例如,登录用户的名称可以通过MD5运行后添加到密钥中。

string toWrite = score + "|" + md5(score+"myKey"+md5(System.username /**or so**/)) + Environment.NewLine

这将防止密钥被“简单共享”。


“同样的风险”并不完全相同,因为这里实际上没有任何保密性。MD5可以立即识别,文件可以在不进行进一步研究的情况下进行修改。 - njzk2
1
@njzk2:不提取“盐”(这里实际上应该称为密钥,因为它的用途就是如此)是不行的。当然,在实践中,熟练的攻击者总是可以从可执行文件中提取密钥,因此安全性仍然有限,但至少可以阻止大多数偶然作弊者(直到有人在网上发布密钥和哈希算法为止)。 - Ilmari Karonen
好的,而且通常的彩虹表中似乎没有存储那个组合,所以没问题。 - njzk2
并不能真正防止篡改,只是确保一旦被篡改就会百分之百丢失数据。盐值必须保存在某个地方,这意味着它可能会被提取出来,也就是说通过反复试错可以找到哈希算法,因此数据最终仍然可以被篡改。 - Eugene Krapivin
@EugeneKrapivin 这对于任何可能的解决方案都是正确的。因此,您最好选择一些易于实现的东西。 - user253751

9
您最好的选择是使用标准NT安全性保护整个文件,并通过编程方式更改访问控制列表,以防止不需要的用户编辑整个文件(当然,除了冒充您自己的应用程序的用户)。
加密在这里无法起到帮助作用,因为文件仍然可以使用常规文本编辑器(例如notepad)进行编辑,最终用户只需添加一个额外字符(或删除一个字符),就可以破坏该文件。
有一种替代方法不涉及编程工作...
告诉用户,一旦他们手动编辑了整个文本文件,他们就失去了您的支持。归根结底,如果您存储这些数据,那么是因为您的应用程序需要它。破坏它或手动编辑它的风险可能会导致您的应用程序产生错误。
另一种涉及编程工作的替代方法...
每当您从应用程序更改文件时,您可以计算MD5或SHA哈希并存储在单独的文件中,然后在再次读取或写入它之前,您将检查整个文件是否生成相同的哈希值。
这样,用户仍然可以手动编辑您的文件,但是您将知道此不期望的行为是由用户完成的(除非用户还手动计算每次更改文件时的哈希值...)。

文件安全可能不会起到帮助作用,因为用户必须能够读写文件才能使程序更新高分记录,除非该程序将作为另一个用户或在另一台机器上运行。 - PHeiberg
@PHeiberg 嗯,这不一样。通过加密密码,您可以在应用程序中冒充一个用户,然后可以使用标准NT安全性来有效地保护文件免受编辑。 - Matías Fidemraizer
那样做会使它更安全吗?无论使用哪种方法,您都需要存储密码/加密密钥。对我来说,无论密码是用于用户还是作为加密密钥,都没有关系。如果用户可以访问计算机并且程序可直接由用户安装,则无法保护文件,只有使其更难以更改和更加模糊的方法。 - PHeiberg
@PHeiberg 只有管理员可以更改 ACL。你可能会说:基本上所有的 Windows 用户都是使用管理员用户登录的。没错,顺便说一句,只有高级用户/专业用户知道如何使用 NT 安全性。也就是说,你正在阻止将近 90% 的可能用户编辑文件。 - Matías Fidemraizer
加密在这里无济于事,我完全不同意。如果用户编辑了文件,则他们的高分将被重置。真是倒霉。在这种情况下,加密可能是更好的解决方案之一... - Luke Joshua Park
显示剩余3条评论

6

我还没有看到提到将高分存储在在线排行榜上的内容。显然,这种解决方案需要更多的开发工作,但是由于你在谈论游戏,你可能可以利用第三方提供商,如Steam、Origin、Uplay等。这样做的额外好处是排行榜不仅仅局限于你的机器。


很多更多的开发,不是那么多。各种服务(例如解析)可以在几分钟内设置。 - njzk2

5

您无法在dll中保存数据,而资源文件和txt文件都是可编辑的。看起来加密是您唯一的选择。您可以在保存到txt文件之前对字符串进行加密。请查看此线程: 加密和解密字符串


2
您可以使用CryptoStream对其进行序列化和加密解密:

序列化文件:

  • 创建并打开写模式的FileStream
  • 创建Cryptostream并传递您的filestream
  • 将内容写入Cryptostream(加密)

反序列化文件:

  • 创建并打开读模式的FileStream
  • 创建Cryptostream并传递您的filestream
  • 从Cryptostream中读取(解密)

您可以在这里找到示例和更多信息:

msdn.microsoft.com/en-us/library/system.security.cryptography.cryptostream.aspx

http://www.codeproject.com/Articles/6465/Using-CryptoStream-in-C

示例:

byte[] key = { 1, 2, 3, 4, 5, 6, 7, 8 }; // Where to store these keys is the tricky part, 
byte[] iv = { 1, 2, 3, 4, 5, 6, 7, 8 };
string path = @"C:\path\to.file";

DESCryptoServiceProvider des = new DESCryptoServiceProvider();

// Encryption and serialization
using (var fStream = new FileStream(path, FileMode.Create, FileAccess.Write))
using (var cryptoStream = new CryptoStream(fStream , des.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
    BinaryFormatter serializer = new BinaryFormatter();

    // This is where you serialize your data
    serializer.Serialize(cryptoStream, yourData);
}



// Decryption
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
using (var cryptoStream = new CryptoStream(fs, des.CreateDecryptor(key, iv), CryptoStreamMode.Read))
{
    BinaryFormatter serializer = new BinaryFormatter();

    // Deserialize your data from file
    yourDataType yourData = (yourDataType)serializer.Deserialize(cryptoStream);
}

2

简单解决方案:
为了避免用户恶意更改分数,您可以将其写成二进制吧。
另一个解决方案:
将数据写入SQLite数据库?


据我所知(但我可能错了),您可以对数据库连接进行密码保护。 - Eugene Krapivin
@njzk2 的目标是阻止你的奶奶乱搞,而不是真正实现安全。 - user253751

1

您无法在“资源”中编写内容,更多信息请参见this answer

不能在运行时更改资源字符串的原因是该资源已编译到可执行文件中。如果您反向工程编译的 *.exe 或 *.dll 文件,实际上可以在代码中看到您的字符串。编辑已编译的可执行文件从来都不是一个好主意(除非您想要黑客攻击它),但当您尝试从可执行文件的代码中进行编辑时,这根本是不可能的,因为在执行期间该文件被锁定。

  • 您可以使用File.SetAttributes将“只读”或“隐藏”属性添加到文件中,但用户仍然可以从 Windows 中删除属性并编辑文件。

例如:

File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Hidden);
  • 我可以建议的另一种方法是将数据保存在带有一些奇怪扩展名的文件中,以便用户无法将其视为可编辑或重要文件。例如ghf.ytr目前想不到更奇怪的东西!
  • 我还建议创建一个带有.dll扩展名的文本文件,并将其保存在Windows文件夹之一,例如system32。这样,用户将很难找到分数信息的位置!

1
你可以将文件命名为不暗示其中有得分表的名称(例如YourApp.dat),并加密内容。
接受的答案在这里包含文本加密和解密的代码。 更新
我还建议使用一些Guid作为加密密码。

0
这里有一段代码可以使文本文件不可编辑。同样的方法也可以用来使其不可读等。
string pathfile = @"C:\Users\Public\Documents\Filepath.txt";

if (File.Exists(pathfile))
{
    File.Delete(pathfile);
}
if (!File.Exists(pathfile))
{
    using (FileStream fs = File.Create(pathfile))
    {
        Byte[] info = new UTF8Encoding(true).GetBytes("your text to be written to the file place here");

        FileSecurity fsec = File.GetAccessControl(pathfile);
        fsec.AddAccessRule(new FileSystemAccessRule("Everyone",
        FileSystemRights.WriteData, AccessControlType.Deny));
        File.SetAccessControl(pathfile, fsec);
    }
}

我已经尝试了这部分针对我的文本文件。但是,我仍然可以编辑该文件。我错过了什么问题吗? - Ismayil S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接