Make .txt file unreadable / uneditable
Asked Answered
S

10

37

I have a program which saves a little .txt file with a highscore in it:

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

The problem is that the user can edit the file as simple as possible – with a texteditor. So I want to make the file unreadable / uneditable or encrypt it.

My thinking was that I could save the data in a resource file, but can I write in a resource file? Or save it as .dll, encrypt/decrypt it or look for a MD5-sum/hash.

Stinson answered 14/12, 2015 at 8:20 Comment(14)
An opinion; https://mcmap.net/q/425907/-c-file-encoding-and-decoding-unreadableHairdresser
You can't make your file unreadable/uneditable, but you can make it harder for user to change if you'll encrypt your data before writing to file. Notice that using of md5 has is useless in this case since you will not be able to decrypt data back from md5 hash.Phillida
Refer this if you want to play hard; support.microsoft.com/en-us/kb/307010Hairdresser
You could attatch it to a file with Alternate Data Streams here you can find a c# implementation this way the file won't be visible in the explorer.Playreader
Instead of writing it as a string, why not just write it as the binary representation of an int? You'd already be moving beyond casual editing to "has to use a hex editor" and anything beyond that is a losing battle (if your program can read it, so can other programs).Tabb
Give us more context. If your program is already connecting to a server (is it MVC? WebForms?), write it to the server. Problem solved. If not, and you already have a database in place, write it to the DB and lock the DB down. If you write it to a file, it can be edited. I don't know how 'critical' this program is and what the highscore would mean (does it translate to cash prizes in a game somehow, for example?) - but people will always find a way unless it's just simply out of reach.Belk
try encryption just have a look at this - hastebin.com/okaxipogit.avrasmFitz
Just use binary serializer; it's hard to edit. Not secure, but not trivial to edit for an ordinary user.Cliftonclim
As the answers show, this is not trivial. However, there is a solution available to you: Stop caring. A user cheats. So what?Broadloom
You want to store a highscore as ... a DLL???Garish
@JayMee: The i don't need hard encryption - it is just a file where the Highscore will be saved like "Best Round: 2 Minutes". Damien_The_Unbeliever, i will check that out! Thanks!Stinson
Serializing your Score object to disk is probably your fastest way to go, and all but the most advanced users will be locked out of it.Postmortem
Don't. What's the harm in a user changing their own files? Maybe they are showing off to a friend but meh. I find games to be more fun in replaying when I can cheat a little anyway.Urticaceous
@AdamD.Ruppe Well, this Game is for a School Project and it's pretty massive. It's a Click-Counter - Sounds Simple, but it has Highscores, Eastereggs, Sounds, Jokes, Clicks-per-Second (working on it atm) and Achievements. And because it is a School-Project i don't want guys to cheat with it - unless you will become a little gift as the Winner ;) If you wan't to, i can share the Source with you ;)Stinson
E
61

You can't prevent the user from modifying the file. It's their computer, so they can do whatever they want (that's why the whole DRM issue is… difficult).

Since you said you're using the file to save an high-score, you have a couple of alternatives. Do note that as previously said no method will stop a really determined attacker from tampering with the value: since your application is running on the user computer he can simply decompile it, look at how you're protecting the value (gaining access to any secret used in the process) and act accordingly. But if you're willing to decompile an application, find out the protection scheme used and come up with a script/patch to get around it only to change a number only you can see, well, go for it?

Obfuscate the content

This will prevent the user from editing the file directly, but it won't stop them as soon as the obfuscation algorithm is known.

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

Save the ciphertext to the file, and reverse the process when reading the file.

Sign the content

This will not prevent the user from editing the file or seeing its content (but you don't care, an high-score is not secret) but you'll be able to detect if the user tampered with it.

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

Save both the payload and the hash in the file, then when reading the file check if the saved hash matches a newly computed one. Your key must be kept secret.

Encrypt the content

Leverage .NET's cryptographic libraries to encrypt the content before saving it and decrypt it when reading the file.

Please take the following example with a grain of salt and spend due time to understand what everything does before implementing it (yes, you'll be using it for a trivial reason, but future you — or someone else — may not). Pay special attention on how you generate the IV and the key.

// 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();
                }
            }
        }
    }
}
Eurythermal answered 14/12, 2015 at 8:50 Comment(10)
Please don't use "ciphertext" to describe the output of ToBase64String. ciphertext is the output of encryption and Base 64 encoding isn't that.Tabb
Aye aye! Is slightlyunplainertext better?Eurythermal
@Eurythermal "encoded text" is better, because that's exactly what it is.Investigate
Of course, any of this signing/encryption stuff won't save you when anyone can just extract your keys and sign/encrypt their own version of the file.Acre
Or, don't write to a text file at all! Serialize your Score object to disk, and all but the most advanced users (with quite a bit of time on their hands) will be prevented from making any changes.Postmortem
Your key must be kept secret. good luck with that. one way would be to store it on a server, and only get it via a secure connection to sign the file. The key would be accessible by some means, but that requires some work. (also, signing and encrypting are basically the same in that regard.)Boiling
@Postmortem that's en.wikipedia.org/wiki/Security_through_obscurity and that's not very good.Boiling
@Boiling OP doesn't need Security (as stated in his comments). He just wants a quick way to prevent users from adjusting their own score. Serializing the data is a good way to prevent 99% of users from doing so. Even advanced users would have to care an awful lot to figure out how to go about deserializing that data (given they don't have access to the source)... so it puts it out of reach for most players. And, if someone cares so much to go through all the trouble of figuring this all out, then no security plan will stop them. It's their PC, they can do what they want.Postmortem
Another option is to store the content in the cloudCasillas
@Postmortem Any kind of decompiler — e.g. dotPeek — makes snooping around an application like sifting through the original source code. Unless you're using an obfuscator, which will make things only slightly more inconvenient. But since OP only wants to protect a local high score, anything kind of measure will stop nearly all attempts (and if you're willing to decompile an application to tamper with a number only you can see, well, go for it).Eurythermal
I
12

As you seem to look for relatively low security, I'd actually recommend going for a checksum. Some pseudo-code:

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

If the score would be 100, this would become

100|a6b6b0a8e56e42d8dac51a4812def434

To make sure the user didn't temper with the file, you can then use:

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

Now of course as soon as someone gets to know your key they can mess with this whatever they want, but I'd consider that beyond the scope of this question. The same risk applies to any encryption/decryption mechanism.

One of the problems, as mentioned in the comments down below, is that once someone figures out your key (through brute-forcing), they could share it and everybody will be able to very easily change their files. A way to resolve this would be to add something computer-specific to the key. For instance, the name of the user who logged in, ran through md5.

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

This will prevent the key from being "simply shared".

Irisation answered 14/12, 2015 at 13:6 Comment(5)
The same risk not really, since here there isn't really any secrecy. md5 is instantly recognized, and the file can be modified without any further research.Boiling
@njzk2: Not without first extracting the "salt" (which should really be called a key, here, since that's what it's used as). Of course, in practice, a skilled attacker can always extract the key from the executable, so the security is still limited, but it does at least deter most casual cheaters (until somebody posts the key and the hashing algorithm online, anyway).Ypres
good point. Plus, none of the usual rainbow table seems to have that combination in store, so, ok.Boiling
Doesn't really prevents tempering, just insures 100% data loss in case of tempering. The salt must be saved somewhere meaning it could be extracted, meaning with trial and error the hashing algorithm could be found, thus data could eventually be tempered with.Hogweed
@EugeneKrapivin That's true for every possible solution, though. So you might as well go with something easy to implement.Farmer
L
9

Probably your best bet is securing the whole file using standard NT security and programmatically change the access control list to protect the whole file from being edited by unwanted users (excepting the one impersonating your own application, of course).

Cryptography can't help here because the file could be still editable using a regular text editor (for example, notepad) and the end user can corrupt the file just adding an extra character (or dropping one too).

There's an alternate approach which doesn't involve programming effort...

Tell your users that once they've manually edited the whole text file they've lost your support. At the end of the day, if you're storing this data is because it's required by your application. Corrupting it or doing the risky task of manually editing it can make your application produce errors.

Another alternate approach which involves programming effort...

Whenever you change the file from your application, you can compute a MD5 or SHA hash and store in a separate file, and once you want to read or write it again, you're going to check that the whole file produces the same hash before writing on it again.

This way, the user can still edit your file manually, but you'll know when this unexpected behavior was done by the user (unless the user also manually computes the hash whenever the file is changed...).

Latta answered 14/12, 2015 at 8:31 Comment(8)
File security is probably not going to help, since the user must be able to read and write the file in order for the program to update the highscore, unless the program will be run as another user or on another machine.Embitter
@Embitter Wrong, because the application can do actions using an user with more privileges and the password of the whole user can be encrypted as part of application's configurationNovanovaculite
How is it wrong? I specifically wrote "unless the program will be run as another user ". Encrypting the password for the other user is just as insecure as encrypting the whole highscore file. The encryption key must be available to the program somehow.Embitter
@Embitter Well, it's not the same. With the encrypted password you're impersonating an user in your application and then, you can use standard NT security to effectively protect the file from being edited.Novanovaculite
How would that make it any more secure? You store a password/encryption key no matter which method you use. To me it doesn't matter if the password is for the user or is an encryption key. There is no way to protect the file if the user has access to the machine and the program is directly installable by the user, only ways to make it harder and more obscure how to change it.Embitter
@Embitter Only administrators can change ACLs. You might argue: basically all Windows users are logging in using an administrative user. Right, BTW, only power users/advanced users know how to work with NT security. That is, you're preventing file editing to nearly the 90% of possible users of your application.Novanovaculite
@Embitter As I said in my answer, there's no silver bullet here. If you encrypt the file itself, user can still edit it and corrupt it by accident...Novanovaculite
Cryptography can't help here. I disagree completely. If the user does edit the file, then their highscore simply gets reset. Bad luck. Cryptography is probably one of the better solutions here...Continuity
W
6

Something I have not yet seen mentioned is storing the high score on an online leader board. Obviously this solution requires a lot more development, but since you are talking about a game, you could probably make use of a third party provider like Steam, Origin, Uplay, ... This has the added advantage of leader boards not just being for your machine.

Whittier answered 14/12, 2015 at 13:30 Comment(1)
a lot more development not that much. various services (e.g. parse) are set up in a matter of minutesBoiling
C
5

You cannot save data in a dll, and both Resource file and txt file are editable. It sounds like encryption is the only way for you. You can encrypt the string before saving it to a txt file. Take a look at this thread: Encrypt and decrypt a string

Chaliapin answered 14/12, 2015 at 8:44 Comment(0)
H
2

Simple solution:
To mitigate the hackish user ability to change the score, you can write it as a binary I guess.
Another solution:
Write the data in a SQLite DB?

Hogweed answered 14/12, 2015 at 8:33 Comment(2)
As far as I know (and I could be mistaken) you can password protect the db connection.Hogweed
@Boiling The goal is to deter your grandma from poking around, not to make something actually secure.Farmer
P
2

You can serialize it and deserialize with encryption with CryptoStream :

Serialize file :

  • Create and open FileStream in write mode
  • Create Cryptostream and pass your filestream
  • Write contents to Cryptostream (encrypt)

Deserialize file :

  • Create and open FileStream in read mode
  • Create Cryptostream and pass your filestream
  • Read from Cryptostream (decrypt)

You can find examples and more information here :

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

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

Example :

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);
}
Prodigious answered 14/12, 2015 at 9:9 Comment(0)
B
1

You can't write in Resources, more information exists in this answer

The reason that you can't change a resource string at runtime, is because the resource is compiled into your executable. If you reverse engineer the compiled *.exe or *.dll file, you can actually see your string in the code. Editing an already compiled executable file is never a good idea (unless you're trying to hack it), but when you try to do it from the executables code, it just plain impossible, as the file is locked during execution.

  • You can add Read Only or Hidden attributes to your files using File.SetAttributes, But still user can remove the attributes from windows and edit the file.

An example:

File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Hidden);
  • Another way I could suggest is to save the data in a file with some weird extensions so that the user can't think of it as an editable or important file. somthing like ghf.ytr (Can't think of somthing more weird right now!)
  • I'd also suggest making a text file with .dll extension and saving it in one of windows folders like system32. This way user will have a really hard time trying to find out where does the score information go!
Blanks answered 14/12, 2015 at 8:35 Comment(0)
U
1

You can name your file as something that doesn't suggest it has a score table in it (e.g. YourApp.dat) and encrypt the contents.

The accepted answer here contains the code for encryption and decryption of text.

Update
I also suggest using some Guid as a password for the encryption.

Unreserved answered 14/12, 2015 at 8:43 Comment(0)
Y
0

Here is a code to make an text file not editable. in the same way you use this technique to make it not readable etc.

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);
    }
}
Ynes answered 29/4, 2016 at 11:17 Comment(1)
I have tried this part for my text file. but, still I can able to edit the file. What's the problem did I miss anything?Sordid

© 2022 - 2024 — McMap. All rights reserved.