【问题标题】:Make .txt file unreadable / uneditable使 .txt 文件不可读/不可编辑
【发布时间】:2016-03-19 15:51:24
【问题描述】:

我有一个程序可以保存一个小 .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-sum/hash。

【问题讨论】:

  • 你不能让你的文件不可读/不可编辑,但是如果你在写入文件之前加密你的数据,你可以让用户更难改变。请注意,在这种情况下使用 md5 has 是没有用的,因为您将无法从 md5 哈希中解密数据。
  • 您可以将其附加到带有Alternate Data Streams 的文件,您可以在此处找到c# implementation,这样该文件在资源管理器中将不可见。
  • 与其写成字符串,为什么不直接写成int的二进制表示呢?你已经超越了随意的编辑到“必须使用十六进制编辑器”,除此之外的任何事情都是一场失败的战斗(如果你的程序可以读取它,其他程序也可以)。
  • 给我们更多的上下文。如果您的程序已经连接到服务器(是 MVC 吗?WebForms?),请将其写入服务器。问题解决了。如果没有,并且您已经有一个数据库,请将其写入数据库并锁定数据库。如果将其写入文件,则可以对其进行编辑。我不知道这个程序有多“重要”以及高分意味着什么(例如,它是否会转化为游戏中的现金奖励?)——但人们总会找到一种方法,除非它只是遥不可及。
  • 只使用二进制序列化器;很难编辑。不安全,但对于普通用户来说编辑并不容易。

标签: c# encryption md5sum


【解决方案1】:

您无法阻止用户修改文件。这是他们的电脑,所以他们可以做任何他们想做的事(这就是为什么整个 DRM 问题……很困难)。

既然您说您正在使用该文件来保存高分,那么您有几个选择。请注意,如前所述,没有任何方法可以阻止真正坚定的攻击者篡改该值:由于您的应用程序在用户计算机上运行,​​他可以简单地反编译它,看看您是如何保护该值的(获得对任何秘密的访问权限)过程中使用)并采取相应措施。但如果你愿意反编译一个应用程序,找出使用的保护方案并想出一个脚本/补丁来绕过它,只是为了改变一个只有你能看到的数字,好吧,去吧?

混淆内容

这将阻止用户直接编辑文件,但不会在混淆算法已知后立即阻止他们。

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();
                }
            }
        }
    }
}

【讨论】:

  • 请不要使用“密文”来描述ToBase64String的输出。密文是加密的输出,而Base 64编码不是。
  • 嗯嗯嗯! slightlyunplainertext 更好吗?
  • @Albireo “编码文本”更好,因为它就是这样。
  • 当然,当任何人都可以提取您的密钥并对他们自己的文件版本进行签名/加密时,任何这种签名/加密内容都不会拯救您。
  • @njzk2 OP 不需要安全性(如他的 cmets 所述)。他只是想要一种快速的方法来防止用户调整自己的分数。序列化数据是防止 99% 的用户这样做的好方法。即使是高级用户也必须非常关心如何反序列化该数据(假设他们无权访问源)......所以它让大多数玩家无法接触到。而且,如果有人非常关心要经历所有的麻烦来弄清楚这一切,那么任何安全计划都不会阻止他们。这是他们的电脑,他们可以为所欲为。
【解决方案2】:

由于您似乎在寻找相对较低的安全性,我实际上建议您使用校验和。一些伪代码:

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]);
}

当然,一旦有人知道您的密钥,他们就可以随心所欲地搞乱这个,但我认为这超出了这个问题的范围。同样的风险适用于任何加密/解密机制。

如下面的 cmets 所述,其中一个问题是,一旦有人(通过暴力破解)找出您的密钥,他们就可以共享它,每个人都可以非常轻松地更改他们的文件。解决此问题的一种方法是向密钥添加特定于计算机的内容。比如登录的用户名,通过 md5 运行。

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

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

【讨论】:

  • The same risk 不是真的,因为这里没有任何秘密。 md5 被立即识别,无需进一步研究即可修改文件。
  • @njzk2:必须先提取“盐”(这里应该真正称为键,因为that's what it's used as)。当然,在实践中,熟练的攻击者可以总是从可执行文件中提取密钥,因此安全性仍然有限,但它至少可以阻止大多数临时作弊者(直到有人发布密钥和散列算法在线,无论如何)。
  • 好点。另外,通常的彩虹桌似乎都没有这种组合,所以,好吧。
  • 并不能真正防止回火,只是确保在回火的情况下 100% 的数据丢失。盐必须保存在某个地方,这意味着它可以被提取,这意味着通过反复试验可以找到散列算法,从而最终可以调整数据。
  • @EugeneKrapivin 不过,对于所有可能的解决方案都是如此。所以你不妨选择一些容易实现的东西。
【解决方案3】:

您最好的选择可能是使用标准 NT 安全性保护整个文件,并以编程方式更改访问控制列表以保护整个文件不被不受欢迎的用户编辑(当然,冒充您自己的应用程序的除外)。

密码学在这里无济于事,因为该文件仍然可以使用常规文本编辑器(例如,notepad)进行编辑,并且最终用户只需添加一个额外的字符(或删除一个字符)就可以破坏该文件。

还有一种不涉及编程工作的替代方法...

告诉您的用户,一旦他们手动编辑了整个文本文件,他们就失去了您的支持。归根结底,如果您要存储此数据是因为您的应用程序需要它。损坏它或执行手动编辑它的危险任务可能会使您的应用程序产生错误。

另一种涉及编程工作的替代方法...

每当您从应用程序更改文件时,您都可以计算 MD5 或 SHA 哈希并存储在单独的文件中,一旦您想再次读取或写入它,您将检查整个文件是否生成在再次写入之前使用相同的哈希值。

这样,用户仍然可以手动编辑您的文件,但您会知道用户何时完成了这种意外行为(除非用户在文件更改时也手动计算哈希值...)。

【讨论】:

  • 文件安全可能无济于事,因为用户必须能够读取和写入文件才能让程序更新高分,除非程序将以其他用户身份运行或在另一台机器。
  • @PHeiberg 错误,因为应用程序可以使用具有更多权限的用户执行操作,并且整个用户的密码可以作为应用程序配置的一部分进行加密
  • 怎么回事?我特别写了“除非程序将作为另一个用户运行”。加密其他用户的密码与加密整个高分文件一样不安全。加密密钥必须以某种方式对程序可用。
  • @PHeiberg 嗯,不一样。使用加密密码,您可以在应用程序中模拟用户,然后,您可以使用标准 NT 安全性来有效保护文件不被编辑。
  • 如何使它更安全?无论您使用哪种方法,您都可以存储密码/加密密钥。对我来说,密码是给用户的还是加密密钥都没有关系。如果用户可以访问机器并且程序可以由用户直接安装,则没有办法保护文件,只有让更改变得更加困难和更加晦涩的方法。
【解决方案4】:

我还没有看到提到将高分存储在在线排行榜上。显然,这个解决方案需要更多的开发,但既然你在谈论一个游戏,你可能会利用第三方提供商,如 Steam、Origin、Uplay,......这具有排行榜的额外优势,而不仅仅是为了你的机器。

【讨论】:

  • a lot more development 没那么多。各种服务(例如解析)在几分钟内设置完毕
【解决方案5】:

您不能将数据保存在 dll 中,并且资源文件和 txt 文件都是可编辑的。听起来加密是您的唯一方法。您可以在将字符串保存到 txt 文件之前对其进行加密。看看这个线程: Encrypt and decrypt a string

【讨论】:

    【解决方案6】:

    您可以使用CryptoStream 对其进行序列化和反序列化:

    序列化文件:

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

    反序列化文件:

    • 以读取模式创建和打开 FileStream
    • 创建 Cryptostream 并传递您的文件流
    • 从 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);
    }
    

    【讨论】:

      【解决方案7】:

      简单的解决方案:
      为了减轻用户更改分数的黑客能力,我猜您可以将其编写为二进制文件。
      另一种解决方案:
      将数据写入 SQLite DB?

      【讨论】:

      • 据我所知(我可能弄错了),您可以使用密码保护数据库连接。
      • @njzk2 目标是阻止您的祖母四处闲逛,而不是让某些东西真正安全。
      【解决方案8】:

      您可以将文件命名为不暗示其中包含分数表的内容(例如 YourApp.dat)并加密内容。

      接受的答案here包含文本的加密和解密代码。

      更新
      我还建议使用一些Guid 作为加密密码。

      【讨论】:

        【解决方案9】:

        你不能写在Resources,更多信息在this answer

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

        • 您可以将 Read OnlyHidden 属性添加到您的文件中,使用 File.SetAttributes,但用户仍然可以删除属性 从 Windows 中编辑文件。

        一个例子:

        File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Hidden);
        
        • 我建议的另一种方法是将数据保存在一个文件中 奇怪的扩展,使用户无法将其视为可编辑或 重要文件。 ghf.ytr 之类的东西(想不出来 现在更奇怪了!
        • 我还建议制作一个带有.dll 扩展名的文本文件,并将其保存在system32 等Windows 文件夹之一中。这样用户将很难找到分数信息的去向!

        【讨论】:

          【解决方案10】:

          这是使文本文件不可编辑的代码。以同样的方式使用这种技术使其不可读等。

          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);
              }
          }
          

          【讨论】:

          • 我已经为我的文本文件尝试了这部分。但是,我仍然可以编辑文件。我错过了什么问题是什么?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-09-19
          • 1970-01-01
          • 2013-01-13
          • 1970-01-01
          • 1970-01-01
          • 2013-04-27
          相关资源
          最近更新 更多