1.11M Members

Text Encryption/Decryption with XOR (Python)

 
2
 

Crypting with xor allows one to write one function that can encrypt and decrypt a file. In this example a text file is used, but it could be an image file as well. Once you have created the encrypted file you simply send it through the same function again to decrypt it. The password is looped against the file, but you can get tricky and spell forward then backward, odd/even, or every odd character twice and every even character once. This will make it harder for grandma to decipher your secret files.

# testing a simple xor encryption/decryption
# tested with Python24      vegaseat    02oct2005

import StringIO
import operator


def cryptXOR(filename, pw):
    """
    cryptXOR(filename, pw) takes the file in filename and xor encrypts/decrypts it against the password pw,
    if the file extension is .txt indicating a normal text file, then an encrypted file with extension .txp
    will be written, if the file extension indicates an encrypted file .txp then a decrypted normal text
    file with extension .txt will be written
    """
    f = open(filename, "rb")  # binary required
    str2 = f.read()
    f.close()
    # create two streams in memory the size of the string str2
    # one stream to read from and the other to write the XOR crypted character to
    sr = StringIO.StringIO(str2)
    sw = StringIO.StringIO(str2)
    # make sure we start both streams at position zero (beginning)
    sr.seek(0)
    sw.seek(0)
    n = 0
    #str3 = ""  # test
    for k in range(len(str2)):
        # loop through password start to end and repeat
        if n >= len(pw) - 1:
            n = 0
        p = ord(pw[n])
        n += 1
        
        # read one character from stream sr
        c = sr.read(1)
        b = ord(c)
        # xor byte with password byte
        t = operator.xor(b, p)
        z = chr(t)
        # advance position to k in stream sw then write one character
        sw.seek(k)
        sw.write(z)
        #str3 += z  # test
    # reset stream sw to beginning
    sw.seek(0)
    # if filename was a normal text file, stream sw now contains the encrypted text
    # and is written (binary required) to a file ending with .txp
    if filename.endswith('.txt'):
        outfile = filename[:-4] + '.txp'
        f = open(outfile, "wb")
        f.write(sw.read())
        f.close()
        print "File %s written!" % outfile
    # if filename was encrypted text, stream sw now contains normal text
    # and is written to a file ending with .txt
    elif filename.endswith('.txp'):
        outfile = filename[:-4] + '.txt'
        f = open(outfile, "w")
        f.write(sw.read())
        f.close()
        print "File %s written!" % outfile
        #print str3  # test
    else:
        print "File %s does not have proper extension!" % filename
    
    # clean up
    sr.close()
    sw.close()
    

        
# allows cryptXOR() to be used as a module
if __name__ == '__main__':

    str1 = \
'''A list of quotes from Grade School Essays on the History of Classical Music:
"J.S. Bach died from 1750 to the present"
"Agnus Dei was a woman composer famous for her church music."
"Refrain means don't do it.  A refrain in music is the part you better not try  to sing."
"Handel was half German, half Italian, and half English.  He was rather large."
"Henry Purcell is a well-known composer few people have ever heard of."
"An opera is a song of bigly size."
"A harp is a nude piano."
"A virtuoso is a musician with real high morals."
"Music sung by two people at the same time is called a duel."
"I know what a sextet is but I'd rather not say."
"Most authorities agree that music of antiquity was written long ago."
"My favorite composer is opus."
"Probably the most marvelous fugue was between the Hatfields and the McCoys."
"My very best liked piece is the bronze lullaby." '''
    
    # save the string as a normal text file so we have it
    fout = open("Music101.txt", "w")
    fout.write(str1)
    fout.close()
    
    # let's use a fixed password for testing
    password = "nixon"
    
    # encrypt the text file to "Music101.txp" (check with an editor, shows a mess)
    cryptXOR("Music101.txt", password)
    
    # decrypt the text file back to "Music101.txt" (check with an editor, normal text again)
    cryptXOR("Music101.txp", password)
 
0
 

I think that most people would find this a bit more pythonic:

def crypt(text, password):
    old = StringIO.StringIO(text)
    new = StringIO.StringIO(text)
    
    for position in xrange(len(text)):
        bias = ord(password[position % len(password)])  # Get next bias character from password
        
        old_char = ord(old.read(1))
        new_char = chr(old_char ^ bias)  # Get new charactor by XORing bias against old character
        
        new.seek(position)
        new.write(new_char)
    
    new.seek(0)
    return new.read()
 
0
 

The new function is not doing exactly same as old one as it failed to decrypt file crypted with old function.

Usage change also, I changed the input being from file given as first parameter and return to be the (sde)crypted content and changed the example use accordingly to show the both versions from the program, no checking with text editor required:

# testing a simple xor encryption/decryption

import StringIO

def crypt(fn, password):
    text= open(fn, "rb").read()  # binary required
    old = StringIO.StringIO(text)
    new = StringIO.StringIO(text)
    
    for position in xrange(len(text)):
        bias = ord(password[position % len(password)])  # Get next bias character from password
        
        old_char = ord(old.read(1))
        new_char = chr(old_char ^ bias)  # Get new charactor by XORing bias against old character
        
        new.seek(position)
        new.write(new_char)
    
    new.seek(0)
    return new.read()
        
# allows cryptXOR() to be used as a module
if __name__ == '__main__':

    str1 = \
'''A list of quotes from Grade School Essays on the History of Classical Music:
"J.S. Bach died from 1750 to the present"
"Agnus Dei was a woman composer famous for her church music."
"Refrain means don't do it.  A refrain in music is the part you better not try  to sing."
"Handel was half German, half Italian, and half English.  He was rather large."
"Henry Purcell is a well-known composer few people have ever heard of."
"An opera is a song of bigly size."
"A harp is a nude piano."
"A virtuoso is a musician with real high morals."
"Music sung by two people at the same time is called a duel."
"I know what a sextet is but I'd rather not say."
"Most authorities agree that music of antiquity was written long ago."
"My favorite composer is opus."
"Probably the most marvelous fugue was between the Hatfields and the McCoys."
"My very best liked piece is the bronze lullaby." '''
    
    # save the string as a normal text file so we have it
    fout = open("Music101.txt", "w")
    fout.write(str1)
    fout.close()
    
    # let's use a fixed password for testing
    password = "nixon"
    
    # encrypt the text file to "Music101.txp"
    open("Music101.txp", 'wb').write(crypt("Music101.txt", password))
    
    #"Music101.txp" shows a mess
    print(open("Music101.txp",'rb').read())
    print('-'*60)
    # decrypt the text file back and print it
    print crypt("Music101.txp", password)
 
0
 

I feel StringIO is not necessary and the program becomes even more pythonic:

# testing a simple xor encryption/decryption

import StringIO

def crypt(fn, password):
    text= open(fn, "rb").read()  # binary required
    new = ''
    lp = len(password)
    pp = 0 ## position in password
    
    for old_char in text:
        pp = (pp+1) %lp
        bias = ord(password[pp])  # Get next bias character from password
        new_char = chr(ord(old_char) ^ bias)  # Get new charactor by XORing bias against old character
        
        new+=new_char
    
    return new
        
# allows cryptXOR() to be used as a module
if __name__ == '__main__':
    # let's use a fixed password for testing
    password = "nixon"
    
    # encrypt the text file to "Music101.txp"
    open("Music101.txp", 'wb').write(crypt("Music101.txt", password))
    
    #"Music101.txp" shows a mess
    print(open("Music101.txp",'rb').read())
    print('-'*60)
    # decrypt the text file back to "Music101.txt"
    print crypt("Music101.txp", password)
 
0
 

Thank you for integrating that; I was too lazy. However, I feel that it is a bit more flexible left as its own function. In other words, rather than passing in a file object, open the file object outside the function and pass in the read string. That leaves the function usable inline, in case you wanted to encrypt/decrypt any string.

Your second example I believe was a step back. StringIO interaction is known to be much faster than your += string concatenation. It wasn't a bad idea to pull the password-length generation outside of the loop, for performance purposes, but the whole purpose in using a modulo was to remove the need to increment a password-position counter. Finally, using short variable names such as "pp" and "lp" is grossly non-pythonic.

With all of that in mind, my final, non-lazy offering:

'''crypt module

Contains a simple function, "crypt", that will both encrypt and decrypt a string
of text by XORing it with a password or phrase.'''

import StringIO

def crypt(text, password):
    '''Encrypts or decrypts a string of text.
    
    text: any string
    password: the word or phrase you want to encrypt/decrypt with'''
    
    old = StringIO.StringIO(text)
    new = StringIO.StringIO(text)
    password_length = len(password)
    
    for position in xrange(len(text)):
        bias = ord(password[position % password_length])  # Get next bias character from password
        
        old_char = ord(old.read(1))
        new_char = chr(old_char ^ bias)  # Get new charactor by XORing bias against old character
        
        new.seek(position)
        new.write(new_char)
    
    new.seek(0)
    return new.read()
        
def _file_test():
    '''A testing function'''
    
    str1 = '''A list of quotes from Grade School Essays on the History of Classical Music:
"J.S. Bach died from 1750 to the present"
"Agnus Dei was a woman composer famous for her church music."
"Refrain means don't do it.  A refrain in music is the part you better not try  to sing."
"Handel was half German, half Italian, and half English.  He was rather large."
"Henry Purcell is a well-known composer few people have ever heard of."
"An opera is a song of bigly size."
"A harp is a nude piano."
"A virtuoso is a musician with real high morals."
"Music sung by two people at the same time is called a duel."
"I know what a sextet is but I'd rather not say."
"Most authorities agree that music of antiquity was written long ago."
"My favorite composer is opus."
"Probably the most marvelous fugue was between the Hatfields and the McCoys."
"My very best liked piece is the bronze lullaby."'''
    
    plain_text_name = 'Music101.txt'
    encrypted_text_name = 'Music101.enc'
    
    # Save the string as a normal text file
    file_out = open(plain_text_name, 'w')
    file_out.write(str1)
    file_out.close()
    
    # Let's use a fixed password for testing
    password = 'Cold Roses'
    
    # Encrypt the text file
    file_in = open(plain_text_name)
    file_out = open(encrypted_text_name, 'wb')
    file_out.write(crypt(file_in.read(), password))
    file_in.close()
    file_out.close()
    
    # Encrypted file shows a hot mess
    file_in = open(encrypted_text_name, 'rb')
    print(repr(file_in.read()))
    print('-' * 80)
    file_in.close()
    
    # Decrypt the recently encrypted text file and print it
    file_in = open(encrypted_text_name)
    print crypt(file_in.read(), password)
    file_in.close()

# Run tests when this file is run as a program instead of being imported
if __name__ == '__main__':
    _file_test()
 
0
 

Does not return the text after decrypting twice: (timing added by me)

Encrypted in 12 ms
'\x02O\x00\rS&O\x1c\x03S2\x1a\x03\x10E!O\x15\x17\x1c.O+\x16A6\nS6\x10+\x00\x03\x08\x00\x17\x1c\x00\x04\n0O\x03\n\x00&\x07\x16E;*\x1c\x18\x0bR+O\x1c\x03S\x00\x03\r\x17S;\x0c\x12\tS\x0e\x1a\x1f\rCheQ/]\x10AL&A1\x07S\x01\x1a&\x0bL\x02R=\x02STDv_L\x10Or\x1b\x1b\x00S3\x1d\t\x17E<\x1bQoQ\x02\x08\x02\x11Sr+\x16\x0cS4\x0e\x1fDAr\x18\x1c\x08\x12-O\x0f\x0bM"\x00\x00\x00\x01c\t\r\tO\'\x1cS\x03\x1c1O\x04\x01Rr\x0c\x1b\x10\x01 \x07L\tU!\x06\x10KQIM>\x01F \x0e\x1a\x0bS.\n\r\nSr\x0b\x1c\x0bT7O\x08\x0b\x00;\x1b]ES\x02O\x1e\x01F \x0e\x1a\x0bS*\x01L\tU!\x06\x10E\x1a0O\x18\x0cEr\x1f\x12\x17\x07c\x16\x03\x11\x000\n\x07\x11\x161O\x02\x0bTr\x1b\x01\x1cSc\x1b\x03DS;\x01\x14KQIM$\x05N6\n\x1fE\x04"\x1cL\x0cA>\tS"\x161\x02\r\n\x0cr\x07\x12\t\x15c&\x18\x05L;\x0e\x1dIS"\x01\x08DH3\x03\x15E6-\x08\x00\rS:ASE;&O\x1b\x05Sr\x1d\x12\x11\x1b&\x1dL\x08A \x08\x16KQIM$\x01N \x16S5\x061\x0c\t\x08Lr\x06\x00E\x12c\x18\t\x08L\x7f\x04\x1d\n\x04-O\x0f\x0bM"\x00\x00\x00\x01c\t\t\x13\x00"\n\x1c\x15\x1f&O\x04\x05V7O\x16\x13\x161O\x04\x01A \x0bS\n\x15mMfFa<O\x1c\x15\x161\x0eL\rSr\x0eS\x16\x1c-\x08L\x0bFr\r\x1a\x02\x1f:O\x1f\rZ7AQoQ\x02O\x04\x05R"O\x1a\x16S"O\x02\x11D7O\x03\x0c\x12-\x00BF*p.S\x13\x1a1\x1b\x19\x0bS=O\x1a\x16S"O\x01\x11S;\x0c\x1a\x04\x1dc\x18\x05\x10Hr\x1d\x16\x04\x1fc\x07\x05\x03Hr\x02\x1c\x17\x12/\x1cBF*p"\x06\x16\x1a O\x1f\x11N5O\x11\x1cS7\x18\x03DP7\x00\x03\t\x16c\x0e\x18DT:\nS\x16\x12.\nL\x10I?\nS\x0c\x00c\x0c\r\x08L7\x0bS\x04S\'\x1a\t\x08\x0epeQ,S(\x01\x03\x13\x00%\x07\x12\x11S"O\x1f\x01X&\n\x07E\x1a0O\x0e\x11Tr&T\x01S1\x0e\x18\x0cE O\x1d\n\x07c\x1c\r\x1d\x0epeQ(\x1c0\x1bL\x05U&\x07\x1c\x17\x1a7\x06\t\x17\x003\x08\x01\x00\x16c\x1b\x04\x05Tr\x02\x06\x16\x1a O\x03\x02\x003\x01\x07\x0c\x026\x06\x18\x1d\x00%\x0e\x00E\x041\x06\x18\x10E<O\x1f\n\x1d$O\r\x03O|MyG>:O\n\x05V=\x1d\x1a\x11\x16c\x0c\x03\tP=\x1c\x16\x17S*\x1cL\x0bP\'\x1c]Gya?\x1e\x0bB3\r\x1f\x1cS7\x07\tDM=\x1c\x07E\x1e"\x1d\x1a\x01L=\x1a\x00E\x156\x08\x19\x01\x00%\x0e\x00E\x11&\x1b\x1b\x01E<O\x07\r\x16c\'\r\x10F;\n\x1f\x01\x00c\x0e\x02\x00\x00&\x07\x16E> ,\x03\x1dS|MyG>:O\x1a\x01R+O\x11\x00\x007O\x00\rK7\x0bS\x15\x1a&\x0c\tDI!O\x07\r\x16c\r\x1e\x0bN(\nS\t\x06/\x03\r\x06Y|M'
--------------------------------------------------------------------------------
A list of q
 
0
 

Sure it does. I've tested it a dozen times. If you paste exactly what I posted into a file named "scrypt.py", and type "python scrypt.py" in your terminal/command prompt, it will run the test and output both sides of the encrypted text.

Keep in mind that the text you pasted is a representation of the text, not the text itself. If you save that text to a file and try to decrypt it, it will not work.

As a final test, try this: in your terminal/command prompt, navigate to the directory containing the new "scrypt.py" file, start the Python interpreter (I'm running 2.6.3), and try the following test:

>>> from scrypt import crypt
>>> crypt('hey', 'you')
'\x11\n\x0c'
>>> crypt(crypt('hey', 'you'), 'you')
'hey'

Everything works like a charm.

Cheers!

 
0
 

This method is great for basic cryptography in Python,
however advanced and secure encryptions such as AES offer the best degree of security.
For those of you interested in that, PyCrypto is for you.

 
0
 

Ha.. I'm sorry, but I'm not even sure what you're talking about anymore.

I think I proved your bias is working strangly, I added print of bias and here is breaked output:

I think you lack understanding of how the bias (and the modulo operator) is working. I have thoroughly tested that module, and it works exactly the same as the original post, just more efficiently... and the code is prettier. In my opinion. This is all subjective, of course. Some people will like the original post better; I just wanted to post an alternative.

As jcao219 said, there are even better approaches than any of this. However, this is a nice light-weight method if obfuscation is the main goal, and air-tight security isn't really an issue.

 
0
 

I understand modulo quite well in basic Algebra level, the bias was only numeric format and showed how much the password had same characters, I expected it to be password index. I did not read the code enough, sorry my mistake.

I run the program with my function included and the result was same from the both of our function, only I do not know why

repr(file_in.read())

is

A list of q

as representation and what it should be meaning.

For me better to print the contents of file by:

print(list(file_in.read()))

The you can see the representation of actual contents of the file without beeps (chr(7)) like my original plain print would do with binary file.

Second mistake in your code, why it did not work, was that mode 'rb' was missing from line 74 in the result check:

file_in = open(encrypted_text_name,'rb')

For me it is enough that my code works well enough for me 10x faster than the StringIO:

*** Encrypted in 10 ms StringIO, 1 ms simple ***
 
0
 

Extrapolate that to a 10MB file, and then see how the two methods compare.

 
0
 

I did test with around 1 MB file and psyco enabled.

My simple version did it in :

*** Encrypted in 131 ms simple ***
File length 1102564

Unfortunately the computer was not free long enough for the StringIO version to do the job. I shut the program after 5 minutes, but I do not know how far the program was as adding progress messages would slow down the code even more.

Attachments Clipboard01.jpg 87.17KB
 
0
 

I don't believe it. A quick search will give you stats like these, which would seem to conflict with your test.

 
0
 

Here is the code though basically it has stayed the same minus few refinements:

'''crypt module

Contains a simple function, "crypt", that will both encrypt and decrypt a string
of text by XORing it with a password or phrase.'''
from __future__ import print_function
import StringIO
from time import clock,asctime
#{
try:
    import psyco                        # python specialising compiler
    psyco.full()
except:
    print('Install psyco for faster execution')
    pass
#}

def crypt(text, password):
    '''Encrypts or decrypts a string of text.
    
    text: any string
    password: the word or phrase you want to encrypt/decrypt with'''
    
    old = StringIO.StringIO(text)
    new = StringIO.StringIO(text)
    password_length = len(password)
    
    for position in xrange(len(text)):
        bias = ord(password[position % password_length])  # Get next bias character from password
        old_char = ord(old.read(1))
        new_char = chr(old_char ^ bias)  # Get new charactor by XORing bias against old character
        
        new.seek(position)
        new.write(new_char)
    
    new.seek(0)
    return new.read()

def crypt2(password, string_in='',fn=''):
    new = ''
    length_of_password= len(password)
    position_in_password= 0 ## position in password
    
    for old_char in open(fn, "rb").read() if fn else string_in: # binary required, read() required 
        bias = ord(password[position_in_password])  # Get next bias character from password
        new_char = chr(ord(old_char) ^ bias)  # Get new charactor by XORing bias against old character
        
        new+=new_char
        position_in_password = (position_in_password+1) % length_of_password
    
    return new
        
def _file_test():
    plain_text_name = r'c:\fi.txt'
    encrypted_text_name = r'D:\fi.txx'
       
    # Let's use a fixed password for testing
    password = 'Cold Roses'
    
    # Encrypt the text file
    t = clock()   
    crypted2=crypt2(password,fn=plain_text_name)
    t2 = clock()-t
    print('*** Encrypted in %i ms simple ***' % (1000*t2))

    print('File length',len(crypted2))

    file_in = open(plain_text_name, 'rb')
    file_out = open(encrypted_text_name, 'wb')
    
    t = clock()
    print(asctime())
    crypted=crypt(file_in.read(), password)
    t1 = clock()-t
    print('*** Encrypted in %i ms StringIO' % (1000*t1))    

    file_out.write(crypted)
    file_in.close()
    file_out.close()

# Run tests when this file is run as a program instead of being imported
if __name__ == '__main__':
    _file_test()
    input('Enter') ## prevent output to disapear

fi.txt is dictionary file of Finnish words. You can replace that with your file or English word list list.txt from my one word anagrams snippet.

 
0
 

Well, I just traced your two ways of doing things,
and to conclude, tonyjv's method is vastly quicker than the StringIO method,
mostly because the read, write, and seek methods in StringIO take some time when called.

Here is the basic overview of the results:

*** Encrypted in 50 ms simple ***
File length 1013
Wed Jun 16 17:57:12 2010
*** Encrypted in 243 ms StringIO
         17256 function calls in 0.167 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 :0(__enter__)
        1    0.000    0.000    0.000    0.000 :0(asctime)
     2026    0.007    0.000    0.007    0.000 :0(chr)
        4    0.000    0.000    0.000    0.000 :0(clock)
        2    0.000    0.000    0.000    0.000 :0(close)
     1015    0.004    0.000    0.004    0.000 :0(isinstance)
     1013    0.003    0.000    0.003    0.000 :0(join)
     1019    0.003    0.000    0.003    0.000 :0(len)
     1014    0.003    0.000    0.003    0.000 :0(max)
     1013    0.003    0.000    0.003    0.000 :0(min)
        3    0.001    0.000    0.001    0.000 :0(open)
     4052    0.014    0.000    0.014    0.000 :0(ord)
        2    0.000    0.000    0.000    0.000 :0(read)
        1    0.043    0.043    0.043    0.043 :0(setprofile)
        1    0.000    0.000    0.000    0.000 :0(write)
        1    0.000    0.000    0.123    0.123 <string>:1(<module>)
     1014    0.011    0.000    0.017    0.000 StringIO.py:119(read)
     1013    0.018    0.000    0.028    0.000 StringIO.py:208(write)
     3041    0.008    0.000    0.008    0.000 StringIO.py:38(_complain_ifclosed)
        2    0.000    0.000    0.000    0.000 StringIO.py:54(__init__)
     1014    0.013    0.000    0.022    0.000 StringIO.py:95(seek)
        1    0.000    0.000    0.167    0.167 profile:0(_file_test())
        0    0.000             0.000          profile:0(profiler)
        1    0.023    0.023    0.100    0.100 tester.py:11(crypt)
        1    0.012    0.012    0.022    0.022 tester.py:32(crypt2)
        1    0.000    0.000    0.123    0.123 tester.py:47(_file_test)

Attached, I have the detailed results.
That ends the debate.

Attachments results.zip (31.35KB)
 
0
 

Very interesting. Thanks for that. I just looked back at the link I posted earlier; that guy used cStringIO.StringIO. I wonder how making that switch would compare.

 
0
 

> That ends the debate.

Well, as programmers we must realize that the debate never ends. Besides, neither approach looks pythonic enough to me. Since you already have the performance test set up, could you add the following?

def loop(text):
    def looper(t):
        while True:
            for c in t:
                yield c
    return looper(text)

def crypt(text, passwd):
    crypto = []
    for (t, p) in zip(text, loop(passwd)):
        crypto.append(chr(ord(t) ^ ord(p)))
    return ''.join(crypto)
 
0
 

> That ends the debate.

Well, as programmers we must realize that the debate never ends. Besides, neither approach looks pythonic enough to me. Since you already have the performance test set up, could you add the following?

def loop(text):
    def looper(t):
        while True:
            for c in t:
                yield c
    return looper(text)

def crypt(text, passwd):
    crypto = []
    for (t, p) in zip(text, loop(passwd)):
        crypto.append(chr(ord(t) ^ ord(p)))
    return ''.join(crypto)

Of course, I'll do it right now.

Preliminary results:

*** Encrypted in 57 ms simple ***
File length (method 2):1013
*** Encrypted in 258 ms StringIO
File length (StringIO method):1013
*** Encrypted in 64 ms method 3
File length (method 3):780 
         21164 function calls in 0.154 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        2    0.000    0.000    0.000    0.000 :0(__enter__)
      780    0.002    0.000    0.002    0.000 :0(append)
     2806    0.012    0.000    0.012    0.000 :0(chr)
        6    0.000    0.000    0.000    0.000 :0(clock)
        1    0.000    0.000    0.000    0.000 :0(close)
     1015    0.004    0.000    0.004    0.000 :0(isinstance)
     1014    0.004    0.000    0.004    0.000 :0(join)
     1021    0.003    0.000    0.003    0.000 :0(len)
     1014    0.003    0.000    0.003    0.000 :0(max)
     1013    0.004    0.000    0.004    0.000 :0(min)
        3    0.000    0.000    0.000    0.000 :0(open)
     5612    0.017    0.000    0.017    0.000 :0(ord)
        3    0.000    0.000    0.000    0.000 :0(read)
        1    0.000    0.000    0.000    0.000 :0(setprofile)
        1    0.002    0.002    0.004    0.004 :0(zip)
        1    0.000    0.000    0.153    0.153 <string>:1(<module>)
     1014    0.011    0.000    0.017    0.000 StringIO.py:119(read)
     1013    0.018    0.000    0.028    0.000 StringIO.py:208(write)
     3041    0.008    0.000    0.008    0.000 StringIO.py:38(_complain_ifclosed)
        2    0.000    0.000    0.000    0.000 StringIO.py:54(__init__)
     1014    0.016    0.000    0.025    0.000 StringIO.py:95(seek)
        1    0.000    0.000    0.154    0.154 profile:0(_file_test())
        0    0.000             0.000          profile:0(profiler)
        1    0.024    0.024    0.106    0.106 tester.py:11(crypt)
        1    0.013    0.013    0.024    0.024 tester.py:32(crypt2)
        1    0.000    0.000    0.000    0.000 tester.py:47(loop)
      781    0.002    0.000    0.002    0.000 tester.py:48(looper)
        1    0.009    0.009    0.022    0.022 tester.py:54(crypt3)
        1    0.000    0.000    0.153    0.153 tester.py:60(_file_test)
 
0
 

That is very interesting. Thanks for running those tests. After doing some reading about cStringIO, I did one more modification, and would be interested to see how it stacks up. If you have a minute, jcao219, would you mind giving it one more run-through with this?

from cStringIO import StringIO

def crypt(text, password):
    old_text = StringIO(text)
    new_text = StringIO()
    password_length = len(password)
    
    for position in xrange(len(text)):
        old_character = ord(old_text.read(1))  # Get the next old character
        bias_character = ord(password[position % password_length])  # Get next bias character from password
        new_character = chr(old_character ^ bias_character)  # Get new charactor by XORing bias against old 
        
        new_text.write(new_character)
    
    return new_text.getvalue()

Also, just curious... why did the 3rd function's test use fewer characters? Or did I read that wrong?

 
1
 

4 Tests

Python 2.6.5

StringIO

crypt took 9243 ms.
File length: 89729 


         1256226 function calls in 9.244 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    9.244    9.244 <string>:1(<module>)
    89730    0.545    0.000    0.845    0.000 StringIO.py:119(read)
    89729    3.032    0.000    3.492    0.000 StringIO.py:208(write)
   269189    0.427    0.000    0.427    0.000 StringIO.py:38(_complain_ifclosed)
        2    0.000    0.000    0.000    0.000 StringIO.py:54(__init__)
    89730    0.807    0.000    3.382    0.000 StringIO.py:95(seek)
        1    0.000    0.000    9.244    9.244 tester.py:13(__call__)
        1    1.096    1.096    9.244    9.244 tester.py:21(crypt)
    89729    0.153    0.000    0.153    0.000 {chr}
    89731    0.178    0.000    0.178    0.000 {isinstance}
    89734    0.140    0.000    0.140    0.000 {len}
    89730    0.160    0.000    0.160    0.000 {max}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    89729    2.274    0.000    2.274    0.000 {method 'join' of 'str' objects}
    89729    0.158    0.000    0.158    0.000 {min}
   179458    0.275    0.000    0.275    0.000 {ord}
        2    0.000    0.000    0.000    0.000 {time.clock}

Tonyjv's

crypt2 took 898 ms.
File length: 89729 


         269195 function calls in 0.899 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.899    0.899 <string>:1(<module>)
        1    0.000    0.000    0.899    0.899 tester.py:13(__call__)
        1    0.493    0.493    0.899    0.899 tester.py:38(crypt2)
    89729    0.141    0.000    0.141    0.000 {chr}
        2    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
   179458    0.265    0.000    0.265    0.000 {ord}
        2    0.000    0.000    0.000    0.000 {time.clock}

Nezachem's

crypt3 took 1492 ms.
File length: 89729 


         448656 function calls in 1.493 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.493    1.493 <string>:1(<module>)
        1    0.001    0.001    1.493    1.493 tester.py:13(__call__)
        1    0.613    0.613    1.492    1.492 tester.py:53(crypt3)
        1    0.000    0.000    0.000    0.000 tester.py:55(loop)
    89730    0.151    0.000    0.151    0.000 tester.py:56(looper)
    89729    0.141    0.000    0.141    0.000 {chr}
        1    0.000    0.000    0.000    0.000 {len}
    89729    0.136    0.000    0.136    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.001    0.001    0.001    0.001 {method 'join' of 'str' objects}
   179458    0.267    0.000    0.267    0.000 {ord}
        2    0.000    0.000    0.000    0.000 {time.clock}
        1    0.184    0.184    0.335    0.335 {zip}

cStringIO

crypt4 took 1497 ms.
File length: 89729 


         448657 function calls in 1.498 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.498    1.498 <string>:1(<module>)
        1    0.000    0.000    1.498    1.498 tester.py:13(__call__)
        1    0.794    0.794    1.498    1.498 tester.py:66(crypt4)
        2    0.000    0.000    0.000    0.000 {cStringIO.StringIO}
    89729    0.144    0.000    0.144    0.000 {chr}
        3    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'getvalue' of 'cStringIO.StringO' objects}
    89729    0.146    0.000    0.146    0.000 {method 'read' of 'cStringIO.StringI' objects}
    89729    0.147    0.000    0.147    0.000 {method 'write' of 'cStringIO.StringO' objects}
   179458    0.267    0.000    0.267    0.000 {ord}
        2    0.000    0.000    0.000    0.000 {time.clock}

If you would like to run the tests yourself, I have attached the testing setup.
Hopefully I set things up correctly.

Attachments XorCrypting_SpeedTests.zip (37.59KB)
Isn't it about time forums rewarded their contributors?

Earn rewards points for helping others. Gain kudos. Cash out. Get better answers yourself.

It's as simple as contributing editorial or replying to discussions labeled or OP Kudos

You
This is an OP Kudos discussion and contributors may be rewarded
Post:
Start New Discussion
Tags Related to this Article