Generate and Verify Voucher Code Using Python - python

I'm working on generating voucher code.
Current case is:
Voucher is ranging from 0-999, and generated once a week with an unique code for each voucher.
Can be verified later to check validity.
There's a secret key for each week, I used the random generated secret key to encrypt the voucher number to code.
I've found out that the code is guessable, for example:
'002' => '\xec\xbe\x98'
'003' => '\xec\xbe\x99'
then I tried to use MD5 to hash the code but it's too long(32 digit), and can be time consuming as I wrap the code in a qrcode for the voucher that I generate.
currently I'm using pycrypto module with CFB mode AES encryption,
can anyone tell me which is a preferred method to generate a verifiable voucher code ?

The Hashids module is probably what you're after:
from hashids import Hashids
hash_id = Hashids(salt='your_secret_here', min_length=8)
hash_id.encrypt(0) # aDnwJKQZ
hash_id.encrypt(999) # p32jAqVW
# it is reversible
hash_id.decrypt('p32jAqVW') # (999,)
It has some good features for voucher code generation such as curse word filtering and configurable alphabet.
It is not a hardened encryption solution but for voucher codes with limited value I'm sure it would provide enough protection from brute-force attack, especially if you change the secret each week.

Related

Python library for RSA public decryption

I'm searching for a python library that is capable if decrypting and encrypting RSA (RSA_PKCS1_PADDING to be precise) with a public key. I've aleready tried pycryptodome, cryptography and rsa and all of them cannot decrypt using public key. I have searched through hundreds of posts, and all answers are useless, so to filter them:
I am not confusing a public key with a private key
Public decryption is possible (I was able to do it here)
There is no other way around. I literally need to send public encrypted messages to the server, and recieve private encrypted messages and decrypt them with the public key
Ideally it should be something like nodejs's crypto.publicDecrypt() and crypto.publicEncrypt(). Please help me to find even if not a library, but just a function that is capable of doing it. I looked through the hundreds of posts and I'm feel like I'm going insane. Thank you.
It is as you say indeed possible to encrypt with private and decrypt with public, the mathematical symmetry in RSA allows just swapping e/d in the keys and then calling the encrypt/decrypt functions.
This being said, I want to emphasize that I'm not a crypto expert and cannot say for certain that this doesn't compromise security.
So, you could extend the RSA-Key class with that swapped logic, use blackmagic to swap the implementation of the loaded key, and pass it to the normal functions:
from Crypto.PublicKey.RSA import RsaKey
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Math.Numbers import Integer
class SwappedRsaKey(RsaKey):
def _encrypt(self, plaintext):
# normally encrypt is p^e%n
return int(pow(Integer(plaintext), self._d, self._n))
def _decrypt(self, ciphertext):
# normally decrypt is c^d%n
return int(pow(Integer(ciphertext), self._e, self._n))
data = "I met aliens in UFO. Here is the map.".encode("utf-8")
# It's important to also use our swapped logic in encryption step, otherwise the lib would still use e&n (the private contains all 3 values).
private_key = RSA.import_key(open("mykey.pem").read())
private_key.__class__ = SwappedRsaKey
public_key = RSA.import_key(open("mykey.pub").read())
public_key.__class__ = SwappedRsaKey
cipher_priv = PKCS1_OAEP.new(private_key)
cipher_pub = PKCS1_OAEP.new(public_key)
enc_data = cipher_priv.encrypt(data)
# Decrypt again, just a showcase to prove we can get the value back
dec_data = cipher_pub.decrypt(enc_data)
print(dec_data.decode("utf-8"))
First of all, big thanks to Tobias K. for the short and clear answer. It worked flawlessly.
But when I tried to decrypt the server messages, pycryptodome kept returning sentinel instead of decrypted data. I decided to look into the library source code, and check what would .decrypt() return if I comment out this lines in .../Crypto/Cipher/PKCS1_v_1_5.py:
if not em.startswith(b'\x00\x02') or sep < 10:
return sentinel
And to my surprise it returned the decrypted message! Not sure that I didn't break something, but it works. I guess it was a bug? This is aleready fixed in their github repo, but the version is not released yet

Zip Password functionality works wrong

I have a paradoxon, that I just cannot explain.
In short: I built a python script that is supposed to crack a zipped file, which is password protected.
This is what I've done:
(1) zip the text file:
zip --password bla zip3.zip myZip So the passphrase is "bla".
(2) Then I use the following Python Script:
import zipfile
import itertools
from itertools import *
import string
import time
That's the basic function, that is supposed to check, if a given password works or not:
def crack(File, pwd):
try:
File.extractall(pwd=str.encode(pwd))
print("\n---- SUCCESS! {0} ----".format(pwd))
except:
print("{0} did not work.".format(pwd))
pass
Here I specify, which characters I want to use for trying:
myLetters = string.ascii_letters
Here I specify, which zip-file I want to crack:
File = zipfile.ZipFile("PATH/TO/MY/zip3.zip", 'r')
Here I specify, how long the password-phrase is:
pwd_len = 3
here I specify, how many possible combinations of the charactes exist:
all_poss = (len(myLetters)**pwd_len)
Here is the procedure for concrete password cracking:
count = 0
start_time = time.time()
for i in range(0,pwd_len+1):
for j in map(''.join, itertools.product(myLetters, repeat=i)):
crack(File, j)
count += 1
print(round((count/all_poss)*100, 1), end='\r')
res_time = time.time() - start_time
print("\n--- {} ---".format(round(res_time,2)))
I use a nested loop, to try every password. If it works, I should get the Success-message. Else I should only see the "doesn't work message".
However...
If I type in my terminal: python3 pwdCracker.py >> out I get a long text file, which contains many many "does not work messages", BUT I also get a whole bunch of "Success-messages", although only ONE ("bla") should be correct.
Here is a little extract:
wN did not work.
---- SUCCESS! wO ----
wO did not work.
wP did not work.`
So apparently "wO" is working.. But why?? I set the password to "bla"! I really can open the file with "wO"... why can that happen??
Hope you can help!
The default zip encryption is known to be weak, and I think you are seeing hash collisions 1,2.
Most encryption methods (including those used in zip files) need a fixed length key, and so the password is hashed to give that key. The hash function used in zip is crc32 (specified here, although it details a different attack) which was designed for error checking rather than cryptographic hashing. Therefore it will be vulnerable to this type of attack.
The old zip format contains a check byte to quickly verify if your password is right or wrong. This check byte is verified against the last byte of the decrypted 'decryption header'.
Since the check byte is, well, only one byte, false positives happen quite frequently (1/256). What bruteforce crackers usually do in these cases is to check against multiple files from the same archive (hence using multiple check bytes).
From PkWare's APPNOTE.TXT:
After the header is decrypted, the last 1 or 2 bytes in Buffer SHOULD
be the high-order word/byte of the CRC for the file being decrypted,
stored in Intel low-byte/high-byte order. Versions of PKZIP prior to
2.0 used a 2 byte CRC check; a 1 byte CRC check is used on versions after 2.0.
This can be used to test if the password supplied is correct or not.
So what you are seeing is just that, false positives.

hashlib vs crypt.crypt() in Python. Why different results?

I'm learning Python. I can't figure out why hashlib.sha512(salt + password).hexdigest() doesn't give the expected results.
I'm looking for a pure Python implementation of the equivalent of Ulrich Drepper's sha512crypt.c algorithm. (It took me a while to figure out what I was looking for.)
According to the man page for crypt on my Ubuntu 12.04 system, crypt is using SHA-512 (because the strings start with $6$).
The code below verifies that the behavior is as expected when I call Python's wrapper of the system crypt (i.e., crypt.crypt()). I want to use hashlib.sha512 or some other Python lib to produce the same result as crypt.crypt(). How?
This code shows the problem I'm encountering:
import hashlib, crypt
ctype = "6" #for sha512 (see man crypt)
salt = "qwerty"
insalt = '${}${}$'.format(ctype, salt)
password = "AMOROSO8282"
value1 = hashlib.sha512(salt + password).hexdigest() #what's wrong with this one?
value2 = crypt.crypt(password, insalt) #this one is correct on Ubuntu 12.04
if not value1 == value2:
print("{}\n{}\n\n".format(value1, value2))
According to the crypt man page, SHA-512 is 86 chars. The crypt() call in the code above conforms to that. However, the output of hashlib.sha512 is longer than 86 chars, so something is way off between these two implmentations...
Here's the output for those who don't want to run the code:
051f606027bd42c1aae0d71d049fdaedbcfd28bad056597b3f908d22f91cbe7b29fd0cdda4b26956397b044ed75d50c11d0c3331d3cb157eecd9481c4480e455
$6$qwerty$wZZxE91RvJb4ETR0svmCb69rVCevicDV1Fw.Y9Qyg9idcZUioEoYmOzAv23wyEiNoyMLuBLGXPSQbd5ETanmq/
Another attempt based on initial feedback here. No success yet:
import hashlib, crypt, base64
ctype = "6" #for sha512 (see man crypt)
salt = "qwerty"
insalt = '${}${}$'.format(ctype, salt)
password = "AMOROSO8282"
value1 = base64.b64encode(hashlib.sha512(salt + password).digest())
value2 = crypt.crypt(password, insalt) #this one is correct
if not value1 == value2:
print("{}\n{}\n\n".format(value1, value2))
Here's the solution. There is also more detail at this other question: Python implementation of sha512_crypt.c where it shows that the backend of passlib contains a pure Python implementation of sha512_crypt (and the Python implementation is called if crypt.crypt() isn't available on the OS).
$ sudo pip install passlib
import passlib.hash, crypt
ctype = "6" #for sha512 (see man crypt)
salt = "qwerty"
insalt = '${}${}$'.format(ctype, salt)
password = "AMOROSO8282"
value1 = sha512_crypt.encrypt(password, salt=salt, rounds=5000)
value2 = crypt.crypt(password, insalt)
if not value1 == value2:
print("algorithms do not match")
print("{}\n{}\n\n".format(value1, value2))
Here is the output:
$6$qwerty$wZZxE91RvJb4ETR0svmCb69rVCevicDV1Fw.Y9Qyg9idcZUioEoYmOzAv23wyEiNoyMLuBLGXPSQbd5ETanmq/
$6$qwerty$wZZxE91RvJb4ETR0svmCb69rVCevicDV1Fw.Y9Qyg9idcZUioEoYmOzAv23wyEiNoyMLuBLGXPSQbd5ETanmq/
One key point is that Passlib has a pure Python implementation of sha512_crypt that it will use when the system doesn't have the crypt implementation that current Linux systems have (e.g., http://www.akkadia.org/drepper/SHA-crypt.txt).
See the documentation for PassLib here:
passlib - password hashing library for python - Google Project Hosting
https://code.google.com/p/passlib/
Passlib 1.6.2 documentation — Passlib v1.6.2 Documentation
http://pythonhosted.org/passlib/
passlib-users - Google Groups
https://groups.google.com/forum/#!forum/passlib-users
New Application Quickstart Guide — Passlib v1.6.2 Documentation
http://pythonhosted.org/passlib/new_app_quickstart.html#sha512-crypt
passlib.hash.sha512_crypt - SHA-512 Crypt — Passlib v1.6.2 Documentation
http://pythonhosted.org/passlib/lib/passlib.hash.sha512_crypt.html#passlib.hash.sha512_crypt
The manual of crypt is imprecise (even misleading). The algorithms used by crypt with the “MD5”, “SHA-256” or “SHA-512” monikers are in fact algorithms built on these primitives. They are password-based key derivation functions, using the hash to perform key strengthening.
A good password hashing algorithm has two properties: it must combine the password with a unique salt (to defeat attempts to crack many passwords at once), and it must be slow (because that hurts the attacker more than the defender, since the attacker needs to try a huge number of combinations). Everyday hash algorithms like MD5 and the SHA families are designed to be fast, as fast as possible while still having the desired security properties. One way to build a password hash algorithm is to take a cryptographic hash algorithm and iterate it many times. While this isn't ideal (because there are better techniques that make it more difficult to build dedicated hardware for password cracking), it is adequate.
The Wikipedia article for crypt(3) provides a brief explanation and has pointers to primary sources. Linux and FreeBSD's man pages are poor, but Solaris's has enough information not to be misleading (follow the links to crypt.conf(4) and then crypt_sha512 and the others). You can also read Is user password in ubuntu 13.04 in plain text? and Is there repetition in the Solaris 11 hash routine? Can I add some?
The right way to compute the output of crypt in Python is to call crypt.crypt.
Your passwords are not the same length, that is because the crypt()
output is base64 encoded and you use hexdigest for value1.
Instead of hexdigest, you should try to do something like
value1 = crypt_base64(hashlib.sha512(salt + password))
with crypt_base64 like the bash implementation, final part of
doHash() function.

Salt and hash a password in Python

This code is supposed to hash a password with a salt. The salt and hashed password are being saved in the database. The password itself is not.
Given the sensitive nature of the operation, I wanted to make sure everything was kosher.
import hashlib
import base64
import uuid
password = 'test_password'
salt = base64.urlsafe_b64encode(uuid.uuid4().bytes)
t_sha = hashlib.sha512()
t_sha.update(password+salt)
hashed_password = base64.urlsafe_b64encode(t_sha.digest())
Based on the other answers to this question, I've implemented a new approach using bcrypt.
Why use bcrypt
If I understand correctly, the argument to use bcrypt over SHA512 is that bcrypt is designed to be slow. bcrypt also has an option to adjust how slow you want it to be when generating the hashed password for the first time:
# The '12' is the number that dictates the 'slowness'
bcrypt.hashpw(password, bcrypt.gensalt( 12 ))
Slow is desirable because if a malicious party gets their hands on the table containing hashed passwords, then it is much more difficult to brute force them.
Implementation
def get_hashed_password(plain_text_password):
# Hash a password for the first time
# (Using bcrypt, the salt is saved into the hash itself)
return bcrypt.hashpw(plain_text_password, bcrypt.gensalt())
def check_password(plain_text_password, hashed_password):
# Check hashed password. Using bcrypt, the salt is saved into the hash itself
return bcrypt.checkpw(plain_text_password, hashed_password)
Notes
I was able to install the library pretty easily in a linux system using:
pip install py-bcrypt
However, I had more trouble installing it on my windows systems. It appears to need a patch. See this Stack Overflow question: py-bcrypt installing on win 7 64bit python
EDIT: This answer is wrong. A single iteration of SHA512 is fast, which makes it inappropriate for use as a password hashing function. Use one of the other answers here instead.
Looks fine by me. However, I'm pretty sure you don't actually need base64. You could just do this:
import hashlib, uuid
salt = uuid.uuid4().hex
hashed_password = hashlib.sha512(password + salt).hexdigest()
If it doesn't create difficulties, you can get slightly more efficient storage in your database by storing the salt and hashed password as raw bytes rather than hex strings. To do so, replace hex with bytes and hexdigest with digest.
Edit:
The library suggested in this answer is now outdated, and the hashlib key derivation functionality mentioned in this answer: https://stackoverflow.com/a/56915300/893857 is a good suggestion to use nowadays.
Original Answer
The smart thing is not to write the crypto yourself but to use something like passlib: https://passlib.readthedocs.io/en/stable/#
It is easy to mess up writing your crypto code in a secure way. The nasty thing is that with non crypto code you often immediately notice it when it is not working since your program crashes. While with crypto code you often only find out after it is to late and your data has been compromised. Therefore I think it is better to use a package written by someone else who is knowledgeable about the subject and which is based on battle tested protocols.
Also passlib has some nice features which make it easy to use and also easy to upgrade to a newer password hashing protocol if an old protocol turns out to be broken.
Also just a single round of sha512 is more vulnerable to dictionary attacks. sha512 is designed to be fast and this is actually a bad thing when trying to store passwords securely. Other people have thought long and hard about all this sort issues so you better take advantage of this.
As of Python 3.4, the hashlib module in the standard library contains key derivation functions which are "designed for secure password hashing".
So use one of those, like hashlib.pbkdf2_hmac, with a salt generated using os.urandom:
from typing import Tuple
import os
import hashlib
import hmac
def hash_new_password(password: str) -> Tuple[bytes, bytes]:
"""
Hash the provided password with a randomly-generated salt and return the
salt and hash to store in the database.
"""
salt = os.urandom(16)
pw_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
return salt, pw_hash
def is_correct_password(salt: bytes, pw_hash: bytes, password: str) -> bool:
"""
Given a previously-stored salt and hash, and a password provided by a user
trying to log in, check whether the password is correct.
"""
return hmac.compare_digest(
pw_hash,
hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
)
# Example usage:
salt, pw_hash = hash_new_password('correct horse battery staple')
assert is_correct_password(salt, pw_hash, 'correct horse battery staple')
assert not is_correct_password(salt, pw_hash, 'Tr0ub4dor&3')
assert not is_correct_password(salt, pw_hash, 'rosebud')
Note that:
The use of a 16-byte salt and 100000 iterations of PBKDF2 match the minimum numbers recommended in the Python docs. Further increasing the number of iterations will make your hashes slower to compute, and therefore more secure.
os.urandom always uses a cryptographically secure source of randomness
hmac.compare_digest, used in is_correct_password, is basically just the == operator for strings but without the ability to short-circuit, which makes it immune to timing attacks. That probably doesn't really provide any extra security value, but it doesn't hurt, either, so I've gone ahead and used it.
For theory on what makes a good password hash and a list of other functions appropriate for hashing passwords with, see https://security.stackexchange.com/q/211/29805.
For this to work in Python 3 you'll need to UTF-8 encode for example:
hashed_password = hashlib.sha512(password.encode('utf-8') + salt.encode('utf-8')).hexdigest()
Otherwise you'll get:
Traceback (most recent call last):
File "", line 1, in
hashed_password = hashlib.sha512(password + salt).hexdigest()
TypeError: Unicode-objects must be encoded before hashing
passlib seems to be useful if you need to use hashes stored by an existing system. If you have control of the format, use a modern hash like bcrypt or scrypt. At this time, bcrypt seems to be much easier to use from python.
passlib supports bcrypt, and it recommends installing py-bcrypt as a backend: http://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html
You could also use py-bcrypt directly if you don't want to install passlib. The readme has examples of basic use.
see also: How to use scrypt to generate hash for password and salt in Python
I don' want to resurrect an old thread, but... anyone who wants to use a modern up to date secure solution, use argon2.
https://pypi.python.org/pypi/argon2_cffi
It won the the password hashing competition. ( https://password-hashing.net/ ) It is easier to use than bcrypt, and it is more secure than bcrypt.
I did the same thing in NodeJs before:
echo "console.log(require('crypto').createHmac('sha256', 'salt').update('password').digest('hex'))" | node
it's equivalent in python is:
python3 -c 'import hashlib;import base64;import hmac;print(hmac.new(b"salt", "password".encode(), hashlib.sha256).hexdigest())'
And the equivalent shell command is:
echo -n "password" | openssl sha256 -hmac "salt"

django, python and link encryption

I need to arrange some kind of encrpytion for generating user specific links. Users will be clicking this link and at some other view, related link with the crypted string will be decrypted and result will be returned.
For this, I need some kind of encryption function that consumes a number(or a string) that is the primary key of my selected item that is bound to the user account, also consuming some kind of seed and generating encryption code that will be decrypted at some other page.
so something like this
my_items_pk = 36 #primary key of an item
seed = "rsdjk324j23423j4j2" #some string for crypting
encrypted_string = encrypt(my_items_pk,seed)
#generates some crypted string such as "dsaj2j213jasas452k41k"
and at another page:
decrypt_input = encrypt(decypt,seed)
print decrypt_input
#gives 36
I want my "seed" to be some kind of primary variable (not some class) for this purpose (ie some number or string).
How can I achieve this under python and django ?
There are no encryption algorithms, per se, built in to Python. However, you might want to look at the Python Cryptography Toolkit (PyCrypt). I've only tinkered with it, but it's referenced in Python's documentation on cryptographic services. Here's an example of how you could encrypt a string with AES using PyCrypt:
from Crypto.Cipher import AES
from urllib import quote
# Note that for AES the key length must be either 16, 24, or 32 bytes
encryption_obj = AES.new('abcdefghijklmnop')
plain = "Testing"
# The plaintext must be a multiple of 16 bytes (for AES), so here we pad it
# with spaces if necessary.
mismatch = len(plain) % 16
if mismatch != 0:
padding = (16 - mismatch) * ' '
plain += padding
ciph = encryption_obj.encrypt(plain)
# Finally, to make the encrypted string safe to use in a URL we quote it
quoted_ciph = quote(ciph)
You would then make this part of your URL, perhaps as part of a GET request.
To decrypt, just reverse the process; assuming that encryption_obj is created as above, and that you've retrieved the relevant part of the URL, this would do it:
from urllib import unquote
# We've already created encryption_object as shown above
ciph = unquote(quoted_ciph)
plain = encryption_obj.decrypt(ciph)
You also might consider a different approach: one simple method would be to hash the primary key (with a salt, if you wish) and store the hash and pk in your database. Give the user the hash as part of their link, and when they return and present the hash, look up the corresponding pk and return the appropriate object. (If you want to go this route, check out the built-in library hashlib.)
As an example, you'd have something like this defined in models.py:
class Pk_lookup(models.Model):
# since we're using sha256, set the max_length of this field to 32
hashed_pk = models.CharField(primary_key=True, max_length=32)
key = models.IntegerField()
And you'd generate the hash in a view using something like the following:
import hashlib
import Pk_lookup
hash = hashlib.sha256()
hash.update(str(pk)) # pk has been defined previously
pk_digest = hash.digest()
lookup = Pk_lookup(hashed_pk=pk_digest,key=pk)
lookup.save()
Note that you'd have to quote this version as well; if you prefer, you can use hexdigest() instead of digest (you wouldn't have to quote the resulting string), but you'll have to adjust the length of the field to 64.
Django has features for this now. See https://docs.djangoproject.com/en/dev/topics/signing/
Quoting that page:
"Django provides both a low-level API for signing values and a high-level API for setting and reading signed cookies, one of the most common uses of signing in Web applications.
You may also find signing useful for the following:
Generating “recover my account” URLs for sending to users who have lost their password.
Ensuring data stored in hidden form fields has not been tampered with.
Generating one-time secret URLs for allowing temporary access to a protected resource, for - example a downloadable file that a user has paid for."

Categories

Resources