Python cryptograpy.fernet doesn't decrypt symbols properly as expected - python-3.x

Folks, I'm writing a simple cli tool to encrypt a text and decrypt based on the passed arguments.
It works fine when I use only text. But It behaves strange when I pass some symbols.
MY SOURCE CODE
import argparse
from cryptography.fernet import Fernet
def generate_key():
"""
Generates a key and save it into a file
"""
key = Fernet.generate_key()
with open("secret.key", "wb") as key_file:
key_file.write(key)
return key
def load_key():
"""
Loads the key named 'secret.key' from current directory
"""
return open("secret.key", "rb").read()
def encrypt_message(message):
"""
Encrypts a message
"""
key = load_key()
encoded_msg = message.encode()
f = Fernet(key)
encrypted_message = f.encrypt(encoded_msg)
with open("encrypted.txt", "wb") as encrypted_file:
encrypted_file.write(encrypted_message)
return encrypted_message
def decrypt_message(encrypted_msg):
"""
Decrypt an encrypted message
"""
key = load_key()
f = Fernet(key)
decrypted_message = f.decrypt(encrypted_msg)
return decrypted_message.decode()
def Main():
parser = argparse.ArgumentParser()
parser.add_argument("-e", "--en_crypt", help="Pass the text to encrypt as an argument")
parser.add_argument("-d", "--de_crypt", help="Pass the text to decrypt as an argument", action="store_true")
parser.add_argument("-k", "--key", help="Generate the 'secret.key' file", action="store_true")
args = parser.parse_args()
if args.en_crypt:
enc = encrypt_message(args.en_crypt)
print(enc)
if args.de_crypt:
with open("encrypted.txt", "rb") as file:
txt = file.read()
print(decrypt_message(txt))
if args.key:
result = generate_key()
print("Key Generated -> " + str(result))
if __name__ == "__main__":
Main()
MY TEST-CASE 1 - (This is successfully decrypting the text passed)
$ python3 01_crypt.py -k
Key Generated -> b'N5Ll6414I8nvcMlBytk8VwdFC4oVZZZMTCVTLpQ9big='
$ python3 01_crypt.py -e "Some Sample Text to Encrypt"
b'gAAAAABfVMU3JxZOrwLIudKLAqzq5IhivhhkyvJ6TMDxM-MmVQywo4AiZ1zGK5F5gO5JFXfHznV5zPjz6sD8qhOpIR_60Hq4_YLVIV0ztPAWBjln6reg1S0='
$ python3 01_crypt.py -d
Some Sample Text to Encrypt
MY TEST-CASE 2 - (This is not working as expected)
$ python3 01_crypt.py -k
Key Generated -> b'UDUpsIP-Ltjz8XGm-BUSwApXYE_L8eFl6rmE1yBbYW4='
$ python3 01_crypt.py -e "P#$$w0rD"
b'gAAAAABfVMX4tSIU4T1CM5Sw9jGR_O2cuIhccEM4htVTkerQD0YxWuCoUZeDWOeMIfpcP4HV7vYKmrxD22sf7yk27hGCdx0jQA=='
$ python3 01_crypt.py -d
P#4103w0rD
As per the test-case 2, My expected output should be same as the encrypted one P#$$w0rD but instead it shows as P#4103w0rD
I'm clueless why this happen. Am I missing something important? Please advice.
Thanks in Advance!
ADDITIONAL NOTE
When I try the same facility without argparse It works as expected. Please review the code below,
from cryptography.fernet import Fernet
def key_generate():
"""
Generates a key and save it into a file
"""
key = Fernet.generate_key()
with open("secret.key", "wb") as key_file:
key_file.write(key)
return key
def load_keys():
"""
Loads the generated key named 'secret.key' from current directory
"""
return open("secret.key", "rb").read()
def encrypt_message(message):
"""
Encrypts a message
"""
key = load_keys()
encoded_msg = message.encode()
f = Fernet(key)
encrypted_message = f.encrypt(encoded_msg)
return encrypted_message
def decrypt_message(encrypted_msg):
"""
Decrypt an encrypted message
"""
key = load_keys()
f = Fernet(key)
decrypted_message = f.decrypt(encrypted_msg)
# print(type(encrypted_msg))
return decrypted_message.decode()
if __name__ == "__main__":
key_generate()
load_keys()
PLAINTEXT = "P#$$w0rD"
print("Plain Text", PLAINTEXT)
ENCRYPTED_TEXT = encrypt_message(PLAINTEXT)
print("Encrypted Text", ENCRYPTED_TEXT)
DECRYPTED_TEXT = decrypt_message(ENCRYPTED_TEXT)
print("Decrypted Text", DECRYPTED_TEXT)
OUTPUT
$python3 02_decrypt.py
Plain Text P#$$w0rD
Encrypted Text b'gAAAAABfVMfzv7H--aTCaUBdHVs05VRbFmuqpnrt-7k1NCTY9FrGMZKH8y2pkKqZsu5oxRqRgp5DzyRHZhfmA9p_cgNniWfsNw=='
Decrypted Text P#$$w0rD
The above behaviour makes me suspect, argparse could be culprit. Please advice.

Argparse is not at fault, your usage of your shell is.
python3 01_crypt.py -e "P#$$w0rD"
has your Unix shell substitute the current PID for $$ (which happened to be 4103).
Use single quotes to avoid the substitution.
python3 01_crypt.py -e 'P#$$w0rD'

Related

Implementing Laravel crypt and decrypt functions in python

I was able to find the decrypt function with a few researches, and now
I am trying to write laravel encrypt function using python.
I can decrypt using it:
class decrypter:
def __init__(cls, key):
cls.key = key
def decrypt(cls, text):
decoded_text = json.loads(base64.b64decode(text))
iv = base64.b64decode(decoded_text['iv'])
crypt_object = AES.new(key=cls.key, mode=AES.MODE_CBC, IV=iv)
decoded = base64.b64decode(decoded_text['value'])
decrypted = crypt_object.decrypt(decoded)
return unpad(decrypted, 16).decode('utf-8')
def decrypt_string(str):
try:
key = b"xxxx+xxxxxx+x+xxxx+xxxxx"
key = base64.b64decode(key)
msg = str
obj = decrypter(key)
decrypted = obj.decrypt(msg)
return decrypted
except Exception as e:
logla.logla(e, "decrypt_string")
print(e)
But I couldn't find a source for the encrypt method. There is a source I could find, but I couldn't run it.
enter link description here
For encryption, proceed in the opposite direction:
Create an IV
Pad plaintext
Save IV and ciphertext to JSON
Encode JSON with Base64
For encryption as in the linked code, additionally the MAC has to be generated and the PHP serialization has to be used:
import json
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
from Crypto.Hash import HMAC, SHA256
from phpserialize import loads, dumps
class encrypter:
def __init__(cls, key):
cls.key = key
def encrypt(cls, text):
text = dumps(text)
msg = pad(text, 16)
iv = get_random_bytes(16) # b'0123456789012345'
crypt_object = AES.new(key=cls.key, mode=AES.MODE_CBC, IV=iv)
encrypted = crypt_object.encrypt(msg)
ivB64 = base64.b64encode(iv)
encryptedB64 = base64.b64encode(encrypted)
mac = HMAC.new(cls.key, digestmod=SHA256).update(ivB64+encryptedB64).hexdigest()
json_string = json.dumps({'iv': ivB64.decode(), 'value': encryptedB64.decode(), 'mac': mac})
return base64.b64encode(json_string.encode())
def encrypt_string(str, key):
try:
msg = str.encode()
obj = encrypter(key)
encrypted = obj.encrypt(msg)
return encrypted
except Exception as e:
print(e)
# Test
keyB64 = b'MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE='
key = base64.b64decode(keyB64)
plaintext= 'This is a test plaintext'
encrypted = encrypt_string(plaintext, key)
decrypted = decrypt_string(encrypted, key)
print(encrypted)
print(base64.b64decode(encrypted))
For the test-IV b'0123456789012345' the output is:
b'eyJpdiI6ICJNREV5TXpRMU5qYzRPVEF4TWpNME5RPT0iLCAidmFsdWUiOiAiTlE1djFpaWU1QnFoTWNwRlhNdUFSZ2N3YVlrNG5CZlJyYmRKUGRna3FDcUN6NEZ6ZDhSOHhIUy95N1N3TWlQTyIsICJtYWMiOiAiYmYwNGJjMWEyN2NhNWUzMGFlYTdjZTI4Y2FkYTBlZGVjOGEwMzc3NWZhODVhMDc2MGRhODUzNDc1OTBmYmNmZCJ9'
b'{"iv": "MDEyMzQ1Njc4OTAxMjM0NQ==", "value": "NQ5v1iie5BqhMcpFXMuARgcwaYk4nBfRrbdJPdgkqCqCz4Fzd8R8xHS/y7SwMiPO", "mac": "bf04bc1a27ca5e30aea7ce28cada0edec8a03775fa85a0760da85347590fbcfd"}'
The linked code produces the same output using the same plaintext, key, and test-IV.

Cryptography Decryption Problems

My script is a password manager which uses SQLITE3. I am having problems decrypting the data which I have recieved from my database.
The error:
cryptography.exceptions.InvalidSignature: Signature did not match digest,
raise InvalidToken,
cryptography.fernet.InvalidToken
comes up when I try to decode the data. My code is down below and I need help understanding and fixing the problem with decoding. (just focus on the find_password() function, load_key() function, and decode_data() functions)
#The Dependicies and Packages required for this script
import sqlite3
from cryptography.fernet import Fernet
def generate_key():
"""
Generates a key and save it into a file
"""
key = Fernet.generate_key()
with open("secret.key", "wb") as key_file:
key_file.write(key)
def load_key():
"""
Loads the key named `secret.key` from the current directory.
"""
return open("secret.key", "rb").read()
#These are the keys for encryption
key = load_key()
f = Fernet(key)
def decode_data(datas):
new_name = f.decrypt(datas)
final_name = new_name.decode()
return final_name
def create_password():
"""
This function is used for gathering the user's data about the website, their username to that site, and their password.
This data in then encrypted using the key generated at lines 5 and 6.
Finally, the data is pushed to the sql database using bind parameters so that there is no risk of sql injection attacks
"""
#Each of these variables asks for a input which is then encoded into bytes so that it can be encrypted.
#The user input is then encrypted using the keys generated on lines 5,6
encrypted_website = f.encrypt(input("What is the website that you have made a password for?>").encode())
encrypted_username = f.encrypt(input("What is the username for the website you are making a password for?>").encode())
encrypted_password = f.encrypt(input("What is the password for the website you are making?>").encode())
#This is the command which uses bind parameters to insert the encrypted data into the database. The type of data being inserted is a blob
c.execute("INSERT INTO passwords (website, username, password) VALUES (?, ?, ?)",
(encrypted_website, encrypted_username, encrypted_password))
def find_password():
"""
This function is to get the password of the website that the user expected
"""
website_name = input("What is the website's name for which you need a password>")
c.execute("SELECT * FROM passwords")
data = c.fetchall()
for row in data:
print(row[0])
name = decode_data(row[0])
if name == website_name:
password = decode_data(row[2])
print(f'The password to {website_name} is {password}')
def main():
go_on = True
while go_on:
direction_question = input("This is your password manager. Press 1 to create a new pasword, Press 2 to search for a password, or Press 3 to exit the program>")
if direction_question.lower() == "1":
create_password()
if direction_question.lower() == "2":
find_password()
if direction_question.lower() == "3":
go_on = False
else:
print("Invalid response")
db.commit()
db.close()
if __name__ == "__main__":
db = sqlite3.connect('password.db')
c = db.cursor()
# generate_key()
#This is the code to create the table
# c.execute("""CREATE TABLE passwords (
# website blob,
# username blob,
# password blob
# )""")
main()

Python object serialization

I am trying to mimic a dict by using that as the base class. The objective is to meet these conditions:
If 2 arguments on the command line, set a key and value in the object's dictionary;
if 1 argument on the command line, treat it as a key and show the value; if no arguments on the command line, show all keys and values.
Here is my code:
import pickle,os,sys
class ConfigDict(dict):
def __init__(self, filename):
self._filename = filename
if not os.path.exists(self._filename):
with open(self._filename,"wb") as fh:
pickle.dump({}, fh)
with open(self._filename,"rb") as fh:
self.update(pickle.load(fh))
def __setitem__(self, key, value):
dict.__setitem__(self,key,value)
with open(self._filename,"wb") as fh:
pickle.dump(self, fh)
def __getitem__(self,key):
return dict.__getitem__(self,key)
cd = ConfigDict('first.pickle')
# if 2 arguments on the command line,
# set a key and value in the object's dictionary
if len(sys.argv) == 3:
key, value = sys.argv[1], sys.argv[2]
print('writing data: {0}, {1}'.format(key, value))
cd[key] = value
# if 1 argument on the command line, treat it as a key and show the value
elif len(sys.argv) == 2:
print('reading a value')
key = sys.argv[1]
print('the value for {0} is {1}'.format(sys.argv[1], cd[key]))
# if no arguments on the command line, show all keys and values
else:
print('keys/values:')
for key in cd.keys():
print(' {0} = {1}'.format(key, cd[key]))
I am able to write to the file, however, when i try to retrive the value for a given key, i hit the error (only the end of stack trace shown):
with open(self._filename,"wb") as fh:
AttributeError: 'ConfigDict' object has no attribute '_filename'
But, i already set the _filename in __init__. What am i missing ?
Well, this is a tricky one - the problem seems to be with
pickle.load(fh) and NOT with self.update(
try this in two lines
...
with open(self._filename,"rb") as fh:
tmp = pickle.load(fh)
self.update(tmp)
...
this would fail at tmp =, so it's the object you're un-pickling that's failing. An easy fix would be to do pickle.dump(dict(self), fh), when serialising your things. Though this whole approach seems "forced" to me. Fully working version:
import pickle,os,sys
class ConfigDict(dict):
def __init__(self, filename):
self._filename = filename
if not os.path.exists(self._filename):
with open(self._filename,"wb") as fh:
pickle.dump({}, fh)
with open(self._filename,"rb") as fh:
self.update(pickle.load(fh))
def __setitem__(self, key, value):
dict.__setitem__(self,key,value)
with open(self._filename,"wb") as fh:
pickle.dump(dict(self), fh)
def __getitem__(self,key):
return dict.__getitem__(self,key)
cd = ConfigDict('first.pickle')
# if 2 arguments on the command line,
# set a key and value in the object's dictionary
if len(sys.argv) == 3:
key, value = sys.argv[1], sys.argv[2]
print('writing data: {0}, {1}'.format(key, value))
cd[key] = value
# if 1 argument on the command line, treat it as a key and show the value
elif len(sys.argv) == 2:
print('reading a value')
key = sys.argv[1]
print('the value for {0} is {1}'.format(sys.argv[1], cd[key]))
# if no arguments on the command line, show all keys and values
else:
print('keys/values:')
for key in cd.keys():
print(' {0} = {1}'.format(key, cd[key]))

How to write to text file in python?

I am a beginner in using python. I have created a plain text file and have to encrypt it to output file. But I am getting an error as below and unable to write it to output file. The code is running but the output file which should be encrypted is created.
#!/usr/bin/env python3
import os
import binascii
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
import argparse
def readfile_binary(file):
with open(file, 'rb') as f:
content = f.read()
return content
def writefile_binary(file, content):
with open(file, 'wb') as f:
f.write(content)
def main():
parser = argparse.ArgumentParser(description = 'Encryption and Decryption of the file')
parser.add_argument('-in', dest = 'input', required = True)
parser.add_argument('-out', dest = 'output', required = True)
parser.add_argument('-K', dest = 'key', help = 'The key to be used for encryption must be in hex')
parser.add_argument('-iv', dest = 'iv', help = 'The inintialisation vector, must be in hex')
args = parser.parse_args()
input_content = readfile_binary(args. input)
output_content = writefile_binary(args. output)
if __name__ == "__main__":
main()
The output file should be encrypted and it should be available in the directory.
These two lines:
input_content = readfile_binary(args. input)
output_content = writefile_binary(args. output)
There should not be a space in args.input. Here is an example,
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('filename')
args = parser.parse_args()
# using type hints can help reasoning about code
def write(filename: str, content: str) -> None:
with open(filename, 'wb') as f:
f.write(str.encode(content))
# if the filename was successfully parsed from stdin
if args.filename == 'filename.txt':
print(f"args: {args.filename}")
# write to the appropriate output file
write(filename=args.filename, content="content")
You might need to correct your code's indentation. Python requires indenting code within each function definition, loop, etc.
And as eric points out, there should be no spaces after the periods in args. input and args. output. Change those to args.input and args.output instead.
So:
#!/usr/bin/env python3
import os
import binascii
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
import argparse
def readfile_binary(file):
with open(file, 'rb') as f:
content = f.read()
return content
def writefile_binary(file, content):
with open(file, 'wb') as f:
f.write(content)
def main():
parser = argparse.ArgumentParser(description = 'Encryption and Decryption of the file')
parser.add_argument('-in', dest = 'input', required = True)
parser.add_argument('-out', dest = 'output', required = True)
parser.add_argument('-K', dest = 'key', help = 'The key to be used for encryption must be in hex')
parser.add_argument('-iv', dest = 'iv', help = 'The inintialisation vector, must be in hex')
args = parser.parse_args()
input_content = readfile_binary(args.input)
output_content = writefile_binary(args.output)
if __name__ == "__main__":
main()

How convert an mbox to a JSON structure?

I am trying to convert an mbox to a JSON structure suitable for import into MongoDB i.e.
I am using mining social web second edition mailbox chapter but its not working properly.
I am trying to convert an mbox to a JSON structure suitable for import into MongoDB i.e.
I am using mining social web second edition mailbox chapter but its not working properly.
import sys
import mailbox
import email
import quopri
import json
import time
from BeautifulSoup import BeautifulSoup
from dateutil.parser import parse
MBOX = 'resources/ch06-mailboxes/data/enron.mbox'
OUT_FILE = MBOX + '.json'
def cleanContent(msg):
# Decode message from "quoted printable" format, but first
# re-encode, since decodestring will try to do a decode of its own
msg = quopri.decodestring(msg.encode('utf-8'))
# Strip out HTML tags, if any are present.
# Bail on unknown encodings if errors happen in BeautifulSoup.
try:
soup = BeautifulSoup(msg)
except:
return ''
return ''.join(soup.findAll(text=True))
# There's a lot of data to process, and the Pythonic way to do it is with a
# generator. See http://wiki.python.org/moin/Generators.
# Using a generator requires a trivial encoder to be passed to json for object
# serialization.
class Encoder(json.JSONEncoder):
def default(self, o): return list(o)
# The generator itself...
def gen_json_msgs(mb):
while 1:
msg = mb.next()
if msg is None:
break
yield jsonifyMessage(msg)
def jsonifyMessage(msg):
json_msg = {'parts': []}
for (k, v) in msg.items():
json_msg[k] = v.decode('utf-8', 'ignore')
# The To, Cc, and Bcc fields, if present, could have multiple items.
# Note that not all of these fields are necessarily defined.
for k in ['To', 'Cc', 'Bcc']:
if not json_msg.get(k):
continue
json_msg[k] = json_msg[k].replace('\n', '').replace('\t', '').replace('\r', '')\
.replace(' ', '').decode('utf-8', 'ignore').split(',')
for part in msg.walk():
json_part = {}
if part.get_content_maintype() != 'text':
print >> sys.stderr, "Skipping MIME content in JSONification
({0})".format(part.get_content_maintype())
continue
json_part['contentType'] = part.get_content_type()
content = part.get_payload(decode=False).decode('utf-8', 'ignore')
json_part['content'] = cleanContent(content)
json_msg['parts'].append(json_part)
# Finally, convert date from asctime to milliseconds since epoch using the
# $date descriptor so it imports "natively" as an ISODate object in MongoDB
then = parse(json_msg['Date'])
millis = int(time.mktime(then.timetuple())*1000 + then.microsecond/1000)
json_msg['Date'] = {'$date' : millis}
return json_msg
mbox = mailbox.UnixMailbox(open(MBOX, 'rb'), email.message_from_file)
# Write each message out as a JSON object on a separate line
# for easy import into MongoDB via mongoimport
f = open(OUT_FILE, 'w')
for msg in gen_json_msgs(mbox):
if msg != None:
f.write(json.dumps(msg, cls=Encoder) + '\n')
f.close()
print "All done"
getting error:
80 # for easy import into MongoDB via mongoimport
81
---> 82 f = open(OUT_FILE, 'w')
83 for msg in gen_json_msgs(mbox):
84 if msg != None:
IOError: [Errno 13] Permission denied: 'resources/ch06-mailboxes/data/enron.mbox.json'
The code you mentioned became obsolete in Third Edition of Mining Social Web
I tried making a workable script that not just converts MBOX to JSON, but even extracts the Attachments to usable formats.
Link to the repo -
https://github.com/PS1607/mbox-to-json
Read the README file for usage instructions.
It seems that your problem is related to user permissions instead of Python. Line 82 tries to open a file in the "data" folder, but permission was denied. You should try executing your script using the sudo command from a terminal:
sudo python3 <your script name>
This should take care of the error you pointed out.
PS: Python 3 uses print as a function; line 88 should read
print('All done')

Resources