开发者

XOR ciphering works *most* of the time

I've been tasked with password-protecting a Java app with minimal concern for real security. So it seemed sensible to store username/password pairs in a text file and then encrypt it. For the encryption, it seemed appropriate to use XOR ciphers because they're easy and fast (remember--it just has to discourage the casual user, not be bulletproof).

I wrote all the appropriate Java, and then realized I needed a way to encrypt the config file. I wrote an additional method, but it was clunky to use more than once or twice (and seemed to only work for some inputs), so I decided it was best to write something in Python, to be played with at a REPL.

This is what I ended up with:

from itertools import izip, cycle

KEY = "stackoverflow"

def encrypt(text):
    return ''.join(chr(ord(x) ^ ord(y)) for (x,y) in izip(text,cycle(KEY)))

def decrypt(text):
    return encrypt(text)

def export(users, file):
    with open(file, "w") 开发者_Python百科as f:
        for user, password in users.items():
            f.write(encrypt('"%s" "%s"'%(user, password)) + "\n")

def import_data(file):
    with open(file) as f:
        return [decrypt(i) for i in f.readlines()]

On the surface, it works:

>>> x = encrypt("Hello world!")
>>> x
';\x11\r\x0f\x04O\x01\n\x00\n\x08N'
>>> decrypt(x)
'Hello world!'

But then things start to fall apart:

>>> export({"foo" : "bar", "baz" : "quux", "spam" : "eggs"}, "users.dat")
>>> import_data("users.dat")
['"foo" "bar"e', '"baz" "quux"}', '"spam" "eggs"y']

And here's how vim reads it -

XOR ciphering works *most* of the time

And then:

>>> export({"what" : "not", "this" : "that", "admin_istrator" : "quux"}, "users2.dat")
>>> import_data("users2.dat")
['"thi', "k97$ma{~'l", '"what" "not"}', '"admin_istrator" "quux', '7~']

Vim:

XOR ciphering works *most* of the time

It occurred to me that I might be having a problem with a character's encrypted form being a newline, but as far as I see that doesn't explain the wacky behavior in the first example or all of the wacky behavior in the second one.

Regarding the newlines, my Plan B is to encrypt the entire file--newlines and all--and then slurp it back up, decrypt it, split it on "\n", and proceed with my line-based parsing.

Thanks in advance.


Update: Here's my implementation of Plan B (described two paragraphs ago).

def import2(file):
    with open(file) as f:
        return decrypt(f.read())

and then:

>>> export({"foo" : "bar", "this" : "that", "admin_istrator" : "letmein"}, "users2.dat")
>>> import2("users2.dat")
'"this" "that"y%smg&91uux!}"admin_istrator" "letmein"y'

Update Two: Binary.

[Code is the same as above, except that all opens are open(file, "rb") or open(file, "wb").]

>>> export({"foo" : "bar", "this" : "that", "admin_istrator" : "letmein"}, "users2.dat")
>>> import2("users2.dat")
'"this" "that"y%smg&91uux!}"admin_istrator" "letmein"y'
>>> import_data("users2.dat")
['"t', "k97$ma{~'", '"foo" "bar"', '"admin_istrator" "letmein"']

Final update: Base 64, other shenanigans.

def import2(file):
    with open(file, "rb") as f:
        return filter(str.strip, [decrypt(i) for i in f.readlines()])

where encrypt and decrypt encode in/decode base 64.


You are trying to store binary in text mode file. Use open(file, "wb") for writing and open(file, "rb") for reading to open file in binary mode and fix an issue.

In text mode every "\r", "\n" and "\r\n" sequences are treated as newlines so they are converted into your local OS line ending convention ("\r\n" for Windows, "\n" for Unix, "\r" for old Macs). In case you read them from text file, you will always get "\n" instead, in case you write them, I don't remember actual behavior, but you will surely also get mess instead of your data :)

And with XOR encryption it's very likely you'll run into this kind of stuff :)

If you are forced not to use binary files, try base64 encoding (e.g. "some\0te\n\nxt with bi\x01naries".encode('base64')). To decode use .decode (thanks, Captain Obvious!).


The problem is you are not reading the same data you codified (you add a '\n' after the encryption), just do a rstrip() of the data you read:

  def import_data(file):
    with open(file) as f:
       return [decrypt(i.rstrip()) for i in f.readlines()]


You can fix it by encrypting the newlines and not resetting the key between lines

from itertools import izip, cycle

KEY = "stackoverflow"

def encrypt(text):
    return ''.join(chr(ord(x) ^ ord(y)) for (x,y) in izip(text,key))

def decrypt(text):
    return encrypt(text)

def export(users, file):
    with open(file, "w") as f:
        for user, password in users.items():
            f.write(encrypt('"%s" "%s"\n'%(user, password)))

def import_data(file):
    with open(file) as f:
        return [decrypt(i) for i in f]


key = cycle(KEY)
export({"foo" : "bar", "baz" : "quux", "spam" : "eggs"}, "users.dat")

key = cycle(KEY)
for row in import_data("users.dat"):
    print row

This should be turned into a class, and key would be an instance variable instead of a global as it is here

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜