django, python and link encryption - python

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."

Related

How can I store a dictionary in a file which I can encrypt?

I'm pretty new to Python and programming in general and am currently working on a little password manager. Thus far I have a script which can encode a txt file using the cryptography library. I am now wondering if it is possible to store information on a website or an account with a corresponding Password in the txt file or if I need to use something else than a txt file. What would you recommend?
The easiest way to do that is convert that dict to json and encrypt the string result.
It's easy convert to dict too. What I advise you to do if you're using this only for auth, is save like a key value store. Example:
{
"<user>": "<password>",
"<user2>" : "<password2>"
}
On this datastruct the code will be always O(1).
Yes, you can use a text file. That is not a problem. The text file will contain encrypted text.
Depending upon how secure and how many unique passwords, you probably want a salted encryption techique.
I would recommend passlib.
I will give as an example some code I recently made, and explain it (although I am not a cryptography expert):
from passlib.context import CryptContext
CRYPTO_CTX = CryptContext(schemes=["bcrypt"], deprecated="auto") # This means only use `'bcrypt'`.
#app.post("/login")
def login_user(user, db):
db_user = crud.get_user_by_username(db, username=user.username)
if db_user is None:
raise HTTPException(
status_code=400, detail=f"No username {user.username} found."
)
if not CRYPTO_CTX.verify(user.password, db_user.hashed_password): ## This is the essential portion
raise HTTPException(status_code=400, detail=f"Incorrect password.")
db_user = crud.update_user(db, db_user, {"last_visit": datetime.now(timezone.utc)})
return {"user": db_user}
This code is, as I said, for a website, and it is using FastAPI. I have removed some stuff for clarity while hopefully keeping enough context. Here is the breakdown:
You need to make a "Cryptographic context", which will know which hashing scheme(s) to use.
In my case, I am only using and allowing 'bcrypt', a best practice choice for 2020 (but you can choose others for flexibility).
I then create a '/login' web route, which is a URI endpoint that triggers a function.
That function receives user information from an HTTP POST that an end user submitted (and a database session).
The user is then searched on the database backend. Presuming she is found, the submitted password in the POST is compared to the password in the database, but that password is cryptographically protected.
Let's zoom in on that comparison:
if not CRYPTO_CTX.verify(user.password, db_user.hashed_password):
We are providing a plain text string (user.password) and a hashed string (db_user.hashed_password) to our CRYPTO_CTXs verify method. That is all that is needed, since the passlib library is doing all the heavy lifting.
How was the password encrypted? That was just as easy:
hashed_password = CRYPTO_CTX.hash(user.password)
And this does not just encrypt the password, it also salts it. In short, this means that if somehow the encryption were hacked, it would only work for that one entry. With salting, it's a two-step encryption process:
Create a password. Eg, 'Elderberries'
Add a salt to it: 'Elderberries34(*&#arst##!'
Hash that entire thing: 'B4B6603ABC670967E99C7E7F1389E40CD16E78AD38EB1468EC2AA1E62B8BED3A'

jose encryption returns binary string but decrypting with decoded string gives error in python

I am using jose for python jwe encryption.
Here is my code from the example
import jose
from time import time
from Crypto.PublicKey import RSA
key = RSA.generate(2048)
claims = {'name': 'Jack'}
pub_jwk = {'k': key.publickey().exportKey('PEM')}
jwe = jose.encrypt(claims, pub_jwk)
jwt = jose.serialize_compact(jwe)
Here the value of jwt is binary string
b'eyJlbmMiOiAiQTEyOENCQy1IUzI1NiIsICJhbGciOiAiUlNBLU9BRVAiLCAiX192IjogMn0.N1RFIEaRIGxCgSkT8HhQI4bO66XOL2RVfn4tMu8BBfGBO79AFKzHUYIRuVqpBX9YcUrsn66n3ccH5O2HO-CuCEPZ6EBM47IBUW1NAdFnm4uc3_X3EAngGTe2hnkLp0RzByYUcaLp2bMn7TWptRmvDGrADaI3uliZCV_ahLeWWFySFjIm_LaLBUzH1okZ-uPqvKQRXDEsdmBSTH5KlsQZHOdRa6uZz_iILmZY6Pp-9XtOSldTLiGasIA_9DNfljP5UtImOhAax_piA7hHeacGAtBNZJVZCWZZajLI6HKz5hVs4aZy7I2EIK6ogL0ubBNMeCQ0dZ70SWjvBTcTbtV2jw.65XaQ1rCSIn25Gc73CJe0g.oo81kAasMwPTISH5XEnnY5Mym3PPXMVs-FtYwgboHUE.5TR5Au7A7JYU7x0iYoPhGQ'
When I decrypt with the same jwt value it gives me exact result.
enc = jose.decrypt(jose.deserialize_compact(jwt), priv_jwk)
Here when I try to make json of encrypted value
data = {'jwt': jwt}
json.dumps(data)
It gives me error Object of type 'bytes' is not JSON serializable
I can decode the encryption like:
jwt = jose.serialize_compact(jwe)
but decrypting with this encryption value will raise error. I don't want to encode jwt in decrypting process.
Is there anyway I can get string instead of byte while encryption so that I can dump it to JSON.
First of all, the Demonware/JOSE project was written for Python 2 originally, you are presumably using the Python 3 branch. There are unresolved issues with the implementation, which to me speaks of the package author not actually understanding the issue very well. JWT tokens, in their compact serialization are just a series of URL-safe Base64 strings concatenated with . characters.
Whenever you encrypt or sign a new token, you have to decode bytes to strings (just decode the bytes value as ASCII). When verifying or decrypting, you'll need to encode the strings to bytes again.
E.g. to encode the jwt value in a JSON object, you'll need to decode:
data = {'jwt': jwt.decode('ascii')}
The whole point of a Javascript Web Token (JWT) is to be exchanged with another party as text. The library compounds matters by claiming the method returns a string.
You could shorten this use the default utf-8 codec, as ASCII is a subset of UTF-8:
data = {'jwt': jwt.decode()}
In the opposite direction, you'll have to encode the compact string to bytes again:
data = json.loads(json_document)
jose.decrypt(jose.deserialize_compact(data['jwt'].encode()), priv_jwk)
But you are basically using outdated software; the Demonware/jose project has not been updated in 3 years time. It also relies on the outdated, unmaintained pycrypto package. You do not want to use.
Instead, take a look at Authlib or JWCrypto, two modules that are actively maintained, and use the cryptography project to handle the tricky cryptography primitives (there is also pyjwt and python-jose, but those projects do not (yet) support JWE encryption, only JWS signed tokens).
Of these, Authlib offers by far the cleanest and clearest API. E.g. to both generate a public / private key pair and create your encrypted token with Authlib, using the default encryption and signing algorithm chosen by Demonware/JOSE, you'd do:
from authlib.jose import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
# generate a private key with corresponding public key, then
# export the keys as a PEM serializations.
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
private_key_pem = private_key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.PKCS8,
serialization.NoEncryption()
)
public_key = private_key.public_key()
public_key_pem = public_key.public_bytes(
serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo
)
# create an encrypted token (JWT using JWE)
claims = {'name': 'Jack'}
header = {'enc': 'A128CBC-HS256', 'alg': 'RSA-OAEP'}
token = jwt.encode(header, claims, public_key_pem).decode()
# decrypt the token again
claims_from_token = jwt.decode(token.encode(), private_key_pem)
Note that Authlib too returns a bytes value here, so you'd have to decode and encode this too if you wanted to further embed this in JSON and pass the token data from a JSON payload back to the jwt.decode() method.
Using the same public and private key serializations with JWCrypto:
import json
from jwcrypto import jwt, jwk
# Create a JWK key object from the private key
jwk_key = jwk.JWK.from_pem(private_key_pem)
# create a JWT() instance to handle ecryption and serialization
header = {'enc': 'A128CBC-HS256', 'alg': 'RSA-OAEP'}
jwt_token = jwt.JWT(claims, header)
jwt_token.make_encrypted_token(jwk_key)
token = jwt_token.serialize()
# deserialize again, using a new JWT instance but with different arguments;
# Yes, this is a confusing API.
# it's always a good idea to limit what algorithms you'll accept
# note that this is a list of both *signing* and *encryption* algorithms, not
# the possible values of the "alg" key in a JOSE header.
jwt_token = jwt.JWT(key=jwk_key, jwt=token, algs=['A128CBC-HS256', 'RSA-OAEP'])
claims_from_token = json.loads(jwt_token.claims)
Note that this library does return a string when serializing, but you have to do the JSON decoding of the claims manually. The API is also mixing serializing and de-serializing into one class, creating a very confusing mix of arguments, methods, and properties.

How to use decrypt with RSA private key and SHA256 on python

I am learning for school to encrypt and decrypt a file using public and private keys en encoding.
I used this code to encode the message. (which generates public key ≠ not private key error)
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
def signing():
#open file = message als binary
message = open('C:/Users/Gebruiker/Desktop/message.txt', "rb").read()
#open public key -> key
key = RSA.import_key(open('C:/Users/Gebruiker/Desktop/public.pem').read())
#message becomes a hash
h = SHA256.new(message)
#f = open file as write binary
f = open('C:/Users/Gebruiker/Desktop/message.signature', 'wb')
# sign hash message with private key
signature = pkcs1_15.new(key).sign(h)
#write signed hash to file
f.write(signature)
f.close()
But now i am trying to decode this message and I found all these people that do it in different ways and work with different type of encoding and encrypting. And I cant find a clear answer.
what i have right now is this
First i have to read the message so
def decrypt():
f = open('C:/Users/Gebruiker/Desktop/message.signature', 'rb').read()
then i open up my private key
key = RSA.import_key(open('C:/Users/Gebruiker/Desktop/private.pem').read())
Because I write and read in binary i have to turn it back into a hash
h = SHA256.new(f)
And then i have to decrypt the hash using my private key.???
Instead i see a lot of people using something like this.
h = pkcs1_15.new(key).sign(h) # sign hash message with private key
which i don't get. You are supposed to decode it right? not sign it again. this part makes no sense to me.
Now I have 2 problems.
I get an encoding error saying my public key is not a private key. Which is kinda the point of public keying right. so only private key can decrypt? Why am i getting an error?
I don't know how to proceed with the decrypting of my message
Can anybody help me with this?
Thanks a lot!
There is confusion in your question. Signature generation for RSA requires modular exponentiation using the values of the private key, not the public key. Modular exponentiation is also used for encryption with the public key. But although the same mathematical formula is used - at least on the surface - doesn't mean that signature generation is encryption with the private key because such a thing does not exist. Current PKCS#1 standards go out of their way to explain this fact, even though earlier PKCS#1 standards used to identify signature generation with RSA encryption.
What you are trying to do is to verify the message. That's the function you would expect rather than sign. Verification is performed by a trusted public key, not by a private key. You are not trying to decode the message, you are trying to verify that the bytes of the message are indeed signed by the private key that belongs to the same key pair as the trusted public key. Generally, the message is not recovered, not even partially. PKCS#1 is called signature generation with appendix, which contrasts with other schemes called signature generation giving message recovery. The appendix is the signature value, it needs to be appended (included with) the message to be of any use.
Actually, the fact that you can at least recover the hash over the message is specific to some schemes like PKCS#1 signature generation (officially called RSASSA-PKCS1-v1_5 in the standard). Other schemes such as PSS in the same standard may not even recover the hash. This is OK as long the verification (which can take place given the data and therefore hash) can succeed or fail. In other words, the verification should at least result in a boolean true / false, but it doesn't need to generate any other information.
Or, in simplified pseudo-code:
ciphertext = encrypt(publicKey, plaintext)
(recovered) plaintext = decrypt(privateKey, ciphertext)
and
signature = sign(privateKey, data)
verificationResult = verify(publicKey, data, signature)
where the data hashing algorithm is a configuration parameter for the signature generation & verification algorithm. If you want to include it you could e.g. include it as initial parameter:
signature = sign(SHA256alg, privateKey, data)
verificationResult = verify(SHA256alg, publicKey, data, signature)
Finally, you are talking about "decoding". You decode messages that have been encoded using an encoding scheme. Encoding/decoding does not presume the presence of a key. We're talking about encryption / decryption and signature generation / verification instead. Examples of encoding are hexadecimals / base 64 that convert binary to text. Character encoding such as UTF-8 is about converting text to binary.
The convention is to encrypt with a receives public RSA key so that only the holder of the corresponding private key can decrypt the message.
Also by convention, you would use your private RSA key to create a signatur that everybody else with the corresponding public key can verify.
In principle you could use a public key for creating a signature, but this would be an erroneous use case of the key, and is often prevented in the libraries implementing RSA. In your case you get a "public key ≠ not private key error" as you try to use a public key in a sign(..) call.
When signing you don't use the full message as input to RSA, but instead calculates a hash (you use SHA256). This hash would not need to be "decryped" in the signature verification, but instead recalculated on the original message, that you want to verify.

Hashing Password in .py file

how i said in the title, i want the password to be hashed when is saved. Its possible with this?
def __OnClickSaveLoginButton(self):
id = self.idEditLine.GetText()
pwd = self.pwdEditLine.GetText()
if (len(id) == 0 or len(pwd) == 0):
self.PopupNotifyMessage("ID and Password required",self.SetIDEditLineFocus)
return
file_object = open("account.cfg", "w")
file_object.write(id+"\n"+pwd)
self.PopupNotifyMessage("Saved.",self.SetIDEditLineFocus)
file_object.close()
You'll want to use the python hashlib. An example could look something like this:
import hashlib
def valid_password(userid, password):
user = get_user(userid)
pw_hash = hashlib.sha256(password).hexdigest()
if user.hash == pw_hash:
return True
return False
Also I recommend reviewing some password storage best practices noted in this SO
Edit: I used sh256 in this example, but that is more useful as a message digest. A better use would be hashlib.pbkdf2_hmac or another key derivation function. There is a good write up here.
If you're going to hash passwords in Python, as nudies mentioned, hashlib.pbkdf2_hmac is correct.
If you want to save the result in Base64, that's a reasonable option, as it turns it into a character string.
Just remember you also have to store the salt and the number of iterations; choose as high a number of iterations as your CPU can stand.
DO NOT request more bytes of output than the native hash function can support; for instance, PBKDF2-HMAC-SHA-512 caps out at 64 bytes for password hashing; the others less.
I have a fully working example at my github repository, including test vectors for all the normal SHA variants, of which the core piece is
import argparse,hashlib,base64,timeit
BinaryOutput = hashlib.pbkdf2_hmac('sha512',args.password, args.salt, args.iterations, args.outputBytes)
BinaryOutput.encode('base64')

python hex hmac md5 of passwords not matching javascript

i have a javascript password coder
md5 = hex_hmac_md5(secret, password)
How can i emulate this in python - ive tried md5 but that is not the same value
i got my md5 javascript code from this website:
Pajs Home
(md5.js)
He states the use is as follows:
In many uses of hashes you end up
wanting to combine a key with some
data. It isn't so bad to do this by
simple concatenation, but HMAC is
specifically designed for this use.
The usage is:
hash = hex_hmac_md5("key", "data");
The HMAC result is also available
base-64 encoded or as a binary string,
using b64_hmac_* or str_hmac_*.
Some other hash libraries have the
arguments the other way round. If the
JavaScript HMAC doesn't match the
value your server library generates,
try swapping the order.
I have tried some python like this:
> def md5_test(secret, password):
>
> return md5(secret+password).hexdigest()
Can anyone tell me what the code should be in python to get the same value?
Thanks
That's what Python's hmac module is for, don't use the MD5 function directly.
# Right
import hmac
# Note that hmac.new defaults to using MD5
hmac.new("password", "message").hexdigest() # 'f37438341e3d22aa11b4b2e838120dcf'
# Wrong
from hashlib import md5
md5("message"+"password").hexdigest() # 'd0647ee3be62a57c9475541c378b1fac'
md5("password"+"message").hexdigest() # 'c494404d2dd827b05e27bd1f30a763d2'
Also take a look at how HMAC is implemented (e.g. on Wikipedia).

Categories

Resources