16

I am using Python 2.7. I have an alphanumeric string, on which I want to perform a encryption/decryption. Whatever I do should remain 2-way and the result should be alphanumeric too.

For example:

str = 'ma6546fbd'
encrypted_data = encrypt_function(str)
decrypted_data = decrypt_function(encrypted_data)
print decrypted_data # I get 'ma6546fbd'

What have I done:

I have written a function

def xor_crypt_string(data, key):
    return ''.join(chr(ord(x) ^ ord(y)) for (x,y) in izip(data, cycle(key)))

This takes the data and a key and returns the result, the problem is that it includes special characters too, which I want to avoid.

Zain Khan
  • 3,753
  • 3
  • 31
  • 54
  • 2
    Do you have a good reason to invent your own encryption? – Simeon Visser Jun 21 '12 at 06:32
  • 2
    He's hardly _inventing_ it, it's just an XOR – mhawke Jun 21 '12 at 06:35
  • I tried some of the mechanisms like ASE, etc but I get special characters in those too. As I said I want an alphanumeric string back which would be sent to the user as a json obj. And the code above is a simple Xor operation – Zain Khan Jun 21 '12 at 06:37

3 Answers3

41

If you want serious encryption (read unbreakable) then I'd use AES from pycrypto something like this.

>>> from Crypto.Cipher import AES
>>> from Crypto import Random
>>> key = b'Sixteen byte key'
>>> iv = Random.new().read(AES.block_size)
>>> cipher = AES.new(key, AES.MODE_CFB, iv)
>>> msg = iv + cipher.encrypt(b'Attack at dawn')
>>> msg.encode("hex")
'e10e096aabff9db382abe8d704404995a7b64d72a4e1b9e5208912d206c4'

That is your ascii message. Now decode the message like this

>>> recv='e10e096aabff9db382abe8d704404995a7b64d72a4e1b9e5208912d206c4'
>>> cipher.decrypt(recv.decode("hex"))[len(iv):]
'Attack at dawn'
>>> 

Any encryption method you make up yourself will be easily breakable by an expert and the one you've shown above falls into that category.

Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
  • I thought XOR encryption using a random key is unbreakable. The problem is key management. Wouldn't you still have the same problem with AES? – mhawke Jun 21 '12 at 06:52
  • +1 After some quick re-education, what I said is probably true, but key management with XOR encryption is rather more unwieldy and difficult than it is for AES. XOR is also vulnerable to known plaintext attacks. – mhawke Jun 21 '12 at 07:01
  • 4
    If you use the key once and once only, so a different key for every message, you are correct. This is a one time pad. However one time pads are really inconvenient as you will need to store as much key information as message, and that needs to be agreed with sender and receiver in advance! With AES you can use the same key for every message and be certain (for practical purposes) that no-one can decrypt your messages without the key. – Nick Craig-Wood Jun 21 '12 at 07:01
  • 1
    See [this SO post](http://stackoverflow.com/questions/6624453/whats-the-correct-way-to-convert-bytes-to-a-hex-string-in-python-3) for how to perform hex conversions in Python 3. – franklin Feb 20 '16 at 23:34
  • You need to add a MAC to protect against chosen ciphertext attacks. – CodesInChaos Mar 10 '16 at 08:16
2

How strict is the alphanumeric requirement?

How about base64 encoding the result of your existing encryption function? You might end up with a few stray '=' padding characters, but you could trim these and calculate and handle the extra padding.

def xor_crypt_string(plaintext, key):
    ciphertext = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in izip(plaintext, cycle(key)))
    return ciphertext.encode('base64')

def xor_decrypt_string(ciphertext, key):
    ciphertext = ciphertext.decode('base64')
    return ''.join(chr(ord(x) ^ ord(y)) for (x,y) in izip(ciphertext, cycle(key)))
mhawke
  • 84,695
  • 9
  • 117
  • 138
  • Or, as suggested by another answer, you could use hex encoding rather than base64. – mhawke Jun 21 '12 at 06:50
  • I tired it, the result contains many special characters. I have to send this alphanumeric string as json encoded to the user. Cannot play with special chars – Zain Khan Jun 21 '12 at 07:10
  • Then use .encode('hex') which will result in strings containing 0-9 and a-f only. You should seriously consider Nick Craig-Wood's answer. – mhawke Jun 21 '12 at 07:20
  • +1 for the phrase "You should seriously consider Nick Craig-Wood's answer" – Zain Khan Jun 21 '12 at 07:27
1

Use PyCryptodome which is a fork of PyCrypto.

pip install pycryptodome

>>> from Crypto.Cipher import AES
>>> from Crypto import Random
>>> key = b'Sixteen byte key'
>>> iv = Random.new().read(AES.block_size)
>>> cipher = AES.new(key, AES.MODE_CFB, iv)
>>> msg = iv + cipher.encrypt(b'Attack at dawn')
>>> msg.hex()
'747a6a3c7357711773f4df5c3e8989dd055da5870d0bf7a967a5cd59ca98'

For decrytion:

>>> encrypted_data = '747a6a3c7357711773f4df5c3e8989dd055da5870d0bf7a967a5cd59ca98'
>>> cipher = AES.new(key, AES.MODE_CFB, iv)
>>> cipher.decrypt(binascii.unhexlify(encrypted_data))[len(iv):]

Note that:

A cipher object is stateful: once you have decrypted a message you cannot decrypt (or encrypt) another message with the same object.

Une30
  • 11
  • 2
  • Thanks for this `pycrypto` no longer works on modern versions of Python, but `pycryptodome` does. – Matt Feb 17 '23 at 15:08