0
$\begingroup$

I asked this on stack exchange with it being code based, but someone suggested to ask here.

Basically, I've done a script in python and wanted an option to encrypt the input, but I couldn't get any libraries working so I decided to try make my own, despite not knowing how actual encryption works. The result I think still needs a few tweaks, but I don't understand how it could be cracked.

I know the random feature of python is apparently very bad for encryption, but I'm using other things too, so you can't just fake an md5, you also need the exact same results from looping through each letter in the key.

I'll break it down into stages as to what it does:

Step 1:
Get md5 of the key and set that as the seed (it could be more complicated than an md5 but I don't think it'd make much difference

Step 2:
Loop through the key one letter at a time, and for each letter, crc32 it, and select one random digit from the result. Repeat this until you have x numbers (currently set to 128), and then shuffle the list.

Step 3:
Based on the size of the key, calculate how many extra characters to add for each character in the input. For all characters, generate another number between 0 and 255 to add to it.

Step 4 (reverse all this to decode):
Loop through the input one letter at a time, convert the character to a number, and add on the results from step 2 and 3, before converting back to a new character (making sure it is in the range of 0 to 255). The result is then all of these characters together, and can be reversed if the right key is given.

So, here's what the output is like:

key = "super secret key"
input = "Test!"

(encode)
Result: hxrBVvYX27YlyKWKMxrA



key = "super secret key"
encodedstring = "hxrBVvYX27YlyKWKMxrA"

(decode)
Result: Test!

key = "Super secret key"
(decode)
Result: ýO¼É

As you can see, the result is dramatically different when a lowercase letter is swapped for a capital, but it still comes out with something. In what way is this really insecure? The only way I'd know how to break it is by just trying millions of combinations until it finds an output word that's in the dictionary.

Also, as an example of it in use, I got it running here: http://goo.gl/VBRYpf
Hit the execute button, click in the green box, and follow the instructions :)

Apologies if it's not a suitable question for the website anyways, I'm not sure if I worded it a bit too specifically for my case

$\endgroup$
6
  • 1
    $\begingroup$ At first glance it looks bad. If you have access to a hash function, you can build a stream cipher based on HMAC or HKDF $\endgroup$ Feb 20, 2015 at 20:05
  • $\begingroup$ It is bad: After the first few bytes (the key length), the keystream essentially becomes the random number generator's raw output. In a known-plaintext scenario, this is devastating for most non-cryptographic generators (including the Mersenne twister used in Python's random) since it leaks the internal state and can thus be used by an attacker to predict all further bytes. $\endgroup$
    – yyyyyyy
    Feb 21, 2015 at 0:49
  • $\begingroup$ In relation to yyyyy's comment, it doesn't add random characters at the end, it adds them in the middle of the string, so you can't really tell what is random and what is the actual characters you need. So in the above example, not encoded it'd be something like Txxexxxsxt!xx where x is the random stuff. $\endgroup$
    – Peter
    Feb 21, 2015 at 1:04
  • $\begingroup$ Since Python includes SHA-512, I can show you a very secure way to develop a stream cipher based on an incrementing counter, with nonce support and an adjustable security parameter $\endgroup$ Feb 21, 2015 at 1:06
  • $\begingroup$ Thanks, though this was more of a personal project type thing, I doubt my knowledge of python would be good enough to fully understand the entire process yet :) $\endgroup$
    – Peter
    Feb 21, 2015 at 1:46

1 Answer 1

1
$\begingroup$

Your option has the flaw of allowing either key recovery, or RNG state recovery, or both. Cryptographically strong constructions do not allow key recovery with less effort than brute force, even with millions or billions of known message pairs. In your case an attack will most likely succeed in a short period of time. It took me only a few hours to completely break something quite similar that at first glance appeared much stronger than this, but suffered from the same key recovery attack with modest computing power.

Since Python does not have native encryption, you kind of have to roll your own from the standard library, or use an addon library such as PyCrypto.

If you do not have the option of using an addon library, a stream cipher can be built from HMAC-SHA512, plus a nonce and a some counters. The high level overview is:

Generate salt and nonce (nonce can be 128-bit salt + 32-bit message counter).
The salt should be randomly generated, if a good RNG is not available in Python, use random.org
Salt needs to be stored, it is used to generate the encryption key for all messages.
Message counter is very important to keep current.

Generate key from password and salt using PBKFD2. If you are using a true random key and not a password you can skip this part.
key = hashlib.pbkdf2_hmac('sha512', 'Password', 'Salt', 32768)[:44]

I use truncated HMAC to generate 44-byte blocks, and a 32-bit message counter. The reason I truncate is to hide 160-bits of the hash state, which may provide additional security, but slows it down to around 2/3 of what is capable.
Find out how many blocks of keystream are required to encrypt the message. A 113-byte message would need 3 44-byte blocks

hm0 = hmac.new(key, 'Nonce', hashlib.sha512)
hm = hm0.copy()
hm.update('BlockCounter')
KSBlock = hm.digest()[:44]

Loop the last 3 lines for each block of keystream you need, XORing the plaintext with the keystream to generate the ciphertext with the same length as the plaintext. Once that is done, prepend (or include in some way) the message counter. Decryption and encryption are the same operation, with the difference being you are using a specific message counter instead of a new one. You can also decode blocks out of order, which can be useful for large messages. Text readable encoding such as Base64 can be applied after encryption.

This example ties the encryption key directly to the password, which I assume is what you are looking for. Other methods use the password to encrypt a random key, then the key encrypts the message. This would allow for the password to be changed without having to reencrypt the entire message. On the other hand it needs a good random number generator.

The 32-bit counters give you a max message size around 176GB, and over 4 billion messages before the nonce counter runs out. This should be more than enough for a single person using a web application. Note that I am not a Python programmer, and the code samples here may not be proper syntax, and are more of a pseudocode. I would still highly recommend a cryptographic library and use AES-CTR instead, but sometimes you have no option.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.