I'm writing a password manager program, and I need to store an encrypted string in a file. (The "service" variable) When the user needs to retrieve the information, they enter the same string as before. When I encrypt the same string, I get a different output. I've checked multiple times, and the string is the same. Anyone know of a fix for this?
Code I'm using to encrypt and write to the file:
def new_account(service, username, password, filepath, key):
# Encrypting the account details #
encrypted_service = key.encrypt(bytes(service, "utf-8"))
encrypted_username = key.encrypt(bytes(username, "utf-8"))
encrypted_password = key.encrypt(bytes(password, "utf-8"))
encrypted_service = encrypted_service.decode("utf-8")
encrypted_username = encrypted_username.decode("utf-8")
encrypted_password = encrypted_password.decode("utf-8")
password_file = open(f"{filepath}\passwords.txt", "a")
password_file.close()
password_file = open(f"{filepath}\passwords.txt", "r")
password_file_content = password_file.read()
password_file.close()
password_file = open(f"{filepath}\passwords.txt", "a")
if f"{encrypted_service},{encrypted_username},{encrypted_password}" in password_file_content:
password_file.close()
return("The account already exists.")
else:
password_file.write(f"{encrypted_service},{encrypted_username},{encrypted_password}\n")
password_file.close()
return f"Account saved for {service.title()} with username {username} and password {password}."
Code I'm using to retrieve the information:
# The account retrieval function #
def get_account(service, filepath, key):
encrypted_service = key.encrypt(bytes(service, "utf-8"))
encrypted_service = encrypted_service.decode("utf-8")
password_file = open(f"{filepath}\passwords.txt")
lines = password_file.read().split("\n")
word = f"{encrypted_service}"
# Getting the line with the account details #
for i in lines:
index = lines.index(i)
myline = lines[index]
encrypted_account_content = myline.split(",")
print(encrypted_account_content)
print(f"service is: {encrypted_service}")
if encrypted_account_content.count(encrypted_service) != 0:
# Decrypting the account details #
username = encrypted_account_content[1]
decrypted_username = key.decrypt(bytes(username, "utf-8"))
decrypted_username = decrypted_username.decode("utf-8")
password = encrypted_account_content[2]
decrypted_password = key.decrypt(bytes(password, "utf-8"))
decrypted_password = decrypted_password.decode("utf-8")
return f"Service: {service.title()}\nUsername: {decrypted_username}\nPassword: {decrypted_password}"
else:
return "Account not found. Please try again."
Any proper encryption will use randomization, so that the result will always be different, and you won't be able to tell that the plaintext was the same. That's needed to achieve semantic security. In practice, initialization vectors and modes of operation like CBC or CTR are used. The cryptography library you're using does it out of the box (with CBC mode).
You will either have to store service as a plaintext, which shouldn't significantly reduce the overall security, or decrypt each service field in order to find the needed record.
Related
Currently, I am designing a credential database meant to hold a servername, username, and password in a text file. However, for the servername and passwords I am trying to make them unique (no two credentials / logins can have the same servername AND password, usernames can be duplicated).
For example,
# This is NOT acceptable because the servername is duplicated.
servername : username : password
google : john : 123
google : john : 321
# This is acceptable because the servername and password are unique.
servername : username : password
google : john : 123
yahoo : john : 321
I have searched stackoverflow for some answers but I was not able to find exactly what I am looking for. In my program's current state, when credentials are entered, they are simply listed right underneath the existing one and continue to add each additional credential without checking for duplicates.
# Creates the file "passdatabase.txt" for use with this code.
with open('passdatabase.txt', 'a+') as searchfile:
searchfile.close()
# Here I initialize the variable / count for the while loop
x = 1
print("\n======================="+
"\nYou chose to add a credential."
"\n-----------------------")
# User enters in a server name to be saved.
addservername = input("Server Name: ")
# User enters in a username to be saved.
addusername = input("Username: ")
# User enters in a password to be saved.
addpassword = input("Password: ")
# All inputs are combined to create a single variable in the format (servername : username : password)
addCredential = addservername + " : " + addusername + " : " + addpassword
# Loops until the user enters a valid submission (either [y] or [n]) to confirm information.
while x == 1:
# Input information is displayed for user to confirm.
confirmCredential = input("~~~~~~~~~~~~~~~~~~~~~~~" +
"\nPlease verify the entered credentials.\n" +
"\nServer Name: [" +
addservername + "]" +
"\nUsername: [" +
addusername + "]" +
"\nPassword: [" +
addpassword + "]" +
"\n\nIs the information correct? (y/n): ")
print("~~~~~~~~~~~~~~~~~~~~~~~")
if confirmCredential == "y":
x = 2
with open('passdatabase.txt', 'r') as searchfile:
lines_seen = set(open('passdatabase.txt'))
for addCredential in (searchfile, 1):
if addCredential not in lines_seen:
with open('passdatabase.txt', 'a+') as searchfile:
# I set the format of the credential to save in the text file as
# servername : username : password.
searchfile.write("\n" + addservername + " : " + addusername + " : " + addpassword)
print("[!] SUCCESS! Your credentials have successfully been stored [!]" +
"\n=======================")
# This breaks the for loop so it does not continue the code.
break
else:
print('Duplicate credential detected!')
elif confirmCredential == "n":
x = 2
print("[!] Your credentials have not been stored [!]" +
"\n=======================")
else:
print("###########################################################" +
"\n[!!!ERROR!!!] PLEASE ENTER EITHER [y] OR [n]. [!!!ERROR!!!]\n" +
"###########################################################")
My questions / requests in one simple list are as follows:
- How do I ensure that the user can only enter in unique entries for their servername and password credentials while allowing duplicate username entries?
- If a duplicate is detected, I would like the code to stop from going further and ask the user for input again until a proper unique credential is provided.
- Should I be using a set or an array? (Data-order does not matter)
- I've seen quite a few duplicate checking scripts that involve the use of 2 .txt files, is this possible with only 1?
- In the future I plan on adding encryption to the password entries, password strength checker, and possibly a credential log (of who has logged in).
- Any input is welcome as I am always willing to learn!
I am fairly new to Python coding and I would really just like some guidance on what I should be looking into next, I'm honestly not sure if I am even headed in the right direction.
Thanks
Maybe you should use sqlite instead of pure text file.
If you really want to implement database feature in a txt file:
Use set for tuple(servername, password), check if exist before appending to file. Remember to load all exist ones from your txt file before ask user to input.
You'd better use a file lock to make sure that only one instance of your script run at the same time.
In company we move some parts of the system from .NET to python. One of these parts is logging/authorization module. I need to implement in python CryptographyManager.CompareHash Method. It takes salted and hashed password from database and compares it with user input (plaintext). To do it I have to salt and hash user input and then check it with hashed password from database.
The password in the database was hashed by Microsoft Enterprise Library.Security.Cryptography. Here more details:
Symmetric Crypto Providers:
algorithmType="System.Security.Cryptography.AesManaged name="AES"
Hash Providers:
algorithmType="System.Security.Cryptography.SHA256Managed
saltEnabled="true" name="SHA256"
I found that password is slated in that way: Base64(salt + SHA256(salt + password)). The salt is the first 16 bytes from the hashed password
What I am trying to do is get salt from hashed password in database and next hash and salt user input (plain text) to compare both.
Unfortunately, in the end there is no succes. Hashes are diffrent. Probably I am doning something wrong with de/encoding
My code looks like that:
import base64
import hashlib
hash = 'EwxBhfN0fM5Puv8/z+3/L50QvdU6BHFb4XQU9xtye/mOXJ8tBPc3tIyW7dEiZrvA'
password = 'some_plain_password'
#password = password.encode()
password = base64.b64decode(password)
#hash to byte
b_hash = base64.b64decode (hash)
print(b_hash)
#get salt, first 16 bytes
salt = b_hash[:15]
salt=base64.b64encode(salt)
print(salt)
m = hashlib.sha256()
m.update(salt)
m.update(password)
print(m.hexdigest())
It's a bit tricky to do this without an example plaintext and hash to verify the results, but your code should implement the algorithm you list in your question:
Base64(salt + SHA256(salt + password))
I think you want something like this:
import base64
import hashlib
hash = 'EwxBhfN0fM5Puv8/z+3/L50QvdU6BHFb4XQU9xtye/mOXJ8tBPc3tIyW7dEiZrvA'
salt = base64.b64decode(hash)[:16] # Use 16 here, not 15
password = 'some_plain_password'
# First let's do the SHA256(salt + password) part
m = hashlib.sha256()
m.update(salt)
m.update(password.encode('utf-8'))
# Now let's feed that into the Base64 part
new_hash = base64.encode(salt + m.digest())
My post is not 100% answer but more the end of the story.
The problem is that there is no official source how works CryptographyManager.Compare/Create Hash
I tried a lot of combinations with Base64(salt + SHA256(salt + password)) but without succes.
Finally, I decide to rewrite C# code.
If someone is looking for python code that is salting and hashing password and compare two hashes, below is my working example:
import base64
import hashlib
import os
class Hashing(object):
# base64( SHA256(password + salt) + salt)
# generate new salt (default 16 bytes_
def generate_new_salt(self, salt_ln=16):
self.new_salt = os.urandom(salt_ln)
print(f'new salt: {self.new_salt}')
return self.new_salt
# get salt from hash
def get_old_salt(self, input_hash, salt_ln=16):
self.old_salt = base64.b64decode(input_hash)[-salt_ln:]
print(f'old salt: {self.old_salt}')
return self.old_salt
# compute hash using parameters
def compute_hash(self, password, salt):
self.salt = salt
self.enc_password = password.encode()
# hashing SHA256(password + salt)
hash_object = hashlib.sha256(self.enc_password + salt)
# add salt to hash and encode to base64
hash_b64 = base64.b64encode(hash_object.digest() + salt)
print(f'new_hash: {hash_b64}')
return hash_b64
# create hash from new or old salt
def create_hash(self, password, salt_ln=16,old_salt=None):
if old_salt: #if old salt then use it
self.salt = old_salt
else: #else generate new salt
self.salt = Hashing().generate_new_salt(salt_ln)
hash = Hashing().compute_hash(password, self.salt)
return hash
# compare input hash with created using salt get from input
def compare_hashes(self, password, old_hash, salt_ln=16):
self.enc_password = password.encode()
#get salt from input hash
self.old_salt = Hashing().get_old_salt(old_hash, salt_ln)
#recreat input hash
re_hash = Hashing().create_new_hash(password,salt_ln, self.old_salt)
print(f'Compare: old_hash: {old_hash}')
print(f'Compare: new_hash: {re_hash}')
#compare
if re_hash.decode() == old_hash.decode():
return True
else:
return False
#code below is just for testing
NewSalt = Hashing().generate_new_salt()
Hash = Hashing().create_new_hash('pass')
OldSalt = Hashing().get_old_salt(Hash)
CompareHash = Hashing().compare_hashes('pass', Hash)
if CompareHash:
print('HASHES THE SAME')
else:
print('NOT THE SAME')
print(CompareHash)
I have been writing a program that saves passwords in hash form, but I am trying to get a value from within my file which stores a value for the salt. For some reason, it doesn't seem to work.
Here is my code:
hashpass = hashlib.sha256()
salt = ['hbjGVY0Kj07,kbjgvhjb,ZsGhnBi0lp]
for line in login:
usr = input()
pas = input()
log = line.split(',')
if usr in line:
x = line
salt_num = int(x[2])
setpass = str(pas + salt[salt_num])
hashpass.update(setpass.encode('utf-8'))
I have tried everything, but still no results when I concatenate the string, I just get the value of pas
Here is what I tried and it works. The code you have shared has some issue that I would request you to cross check with original code.
import hashlib
hashpass = hashlib.sha256()
salt = ['hbjGVY0Kj07','kbjgvhjb','ZsGhnBi0lp']
login = ["user,68a782faf939dfa370345934d255101926b7f59b3a65ab7db5b0bc6f78ec25e5,0"]
for line in login:
#print(line)
usr = input() # I input "user"
pas = input() # I input "qwerty"
log = line.split(',')
#print(log)
if usr in line:
x = log
salt_num = int(x[2])
setpass = str(pas + salt[salt_num])
print(setpass)
hashpass.update(setpass.encode('utf-8'))
OUTPUT --> qwertyhbjGVY0Kj07
Things I would suggest you to check:
All the items in list salt are in quotes, i.e., as string.
Login is a list of strings having elements with comma separated value, like I have created.
change x=line to x=log inside if condition.
I have fixed the issue, but am getting different errors when comparing the variable hashpass with log[1], when comparing my program claims that the password is wrong, here is the whole program for reference.
login = open('login.csv','r')
def logging():
atmptcount = 0
while atmptcount < 3:
usr = input('Please enter your username: ')
pas = input('Please enter your password: ')
hashpass = hashlib.sha256()
for line in login:
log = line.split(',')
if usr.upper() in line:
print(log[2])
salt_num = int(log[2])
setpass = str(pas + salt[salt_num])
hashpass.update(setpass.encode('utf-8'))
if usr == log[0] and hashpass.hexdigest() == log[1]:
print('correct')
return True
print(hashpass.hexdigest())
print(log[1])
atmptcount = atmptcount + 1
print('Sorry you have entered your details incorrectly, please try again')
login.seek(0)
print('Sorry, you have reached your maximum login attempts!')
return False
I have changed the variable names a bit but its the saem concept
Writing a Python script, I would like to know if it is possible to bind to an LDAP server without writing the password in plaintext, like in this example:
import ldap
l = ldap.open("myserver")
username = "cn=Manager, o=mydomain.com"
## I don't want to write the password here in plaintext
password = "secret"
l.simple_bind(username, password)
Example Function for decrypting a file called '.credentials'. This would of course have a seporate script to encrypt the credentials to a file in the first place prior to trying to use it.
So you would call this function:
username, password = decrypt()
l.simple_bind(username, password)
from Crypto.Cipher import AES
import base64
from local_logging import info
def decrypt(dir_path):
#Read '.credentials' file and return unencrypted credentials (user_decoded, pass_decoded)
lines = [line.rstrip('\n') for line in open(dir_path + '/.credentials')]
user_encoded = lines[0]
user_secret = lines[1]
pass_encoded = lines[2]
pass_secret = lines[3]
# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length. This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE
PADDING = '{'
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
# create a cipher object using the random secret
user_cipher = AES.new(user_secret)
pass_cipher = AES.new(pass_secret)
# decode the encoded string
user_decoded = DecodeAES(user_cipher, user_encoded)
pass_decoded = DecodeAES(pass_cipher, pass_encoded)
return (user_decoded, pass_decoded)
I've generated a public and private key with pycrypto, and I save them to a file using export key:
from Crypto.PublicKey import RSA
bits=2048
new_key = RSA.generate(bits, e=65537)
prv = open('keymac.pem','w')
prv.write(new_key.exportKey('PEM'))
prv.close()
pub = open('pubmac.pem', 'w')
pub.write(new_key.publickey().exportKey('PEM'))
pub.close()
I use the public key to encrypt a file (following http://insiderattack.blogspot.com/2014/07/encrypted-file-transfer-utility-in.html#comment-form)
When I read the file to decrypt it, I get "Ciphertext with incorrect length."
I added a try-except block around the decryption code on Deepal Jayasekara example:
try:
encryptedonetimekey = filetodecrypt.read(512)
privatekey = open("keymac.pem", 'r').read()
rsaofprivatekey = RSA.importKey(privatekey)
pkcs1ofprivatekey = PKCS1_OAEP.new(rsaofprivatekey)
aesonetimekey = pkcs1ofprivatekey.decrypt(encryptedonetimekey)
except Exception as decrypprivkeyerr:
print "Decryption of the one time key using the private key failed!!"
print "Key error == %s" %decrypprivkeyerr
raise Exception("Decryption using Private key failed error = %s" %decrypprivkeyerr)
Am I missing something? Should I save the private key differently? Am I not reading the private key correctly?
This doesnt answer your question directly but it may give you some clues to the problem. Im using two functions for encrypting content to a file rather than encrypting a file directly. One for encrypting (in my case username and password) to a file then another to decrypt that data to use as needed.
Note the need for the padding
Creat Encrypted Content In File:
from Crypto.Cipher import AES
import base64
import os
import argparse
parser = argparse.ArgumentParser(description='Arguments used to generate new credentials file, Use: -u for username, -p for password')
parser.add_argument('-u', help='Specify username', required=True)
parser.add_argument('-p', help='Specify password', required=True)
parser.add_argument('-b', help='Specify debug', required=False, action='store_true')
args = vars(parser.parse_args())
def encrypt(username, password):
#Encrypt Credentials To '.creds' file, including 'secret' for username and password
dir_path = os.path.dirname(os.path.realpath(__file__))
# the block size for the cipher object; must be 16 per FIPS-197
BLOCK_SIZE = 16
# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length. This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE
PADDING = '{'
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
# generate a random secret key
user_secret = os.urandom(BLOCK_SIZE)
pass_secret = os.urandom(BLOCK_SIZE)
# one-liners to encrypt/encode and decrypt/decode a string
# encrypt with AES, encode with base64
EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
# create a cipher object using the random secret
user_cipher = AES.new(user_secret)
pass_cipher = AES.new(pass_secret)
# encode a string
user_encoded = EncodeAES(user_cipher, username)
pass_encoded = EncodeAES(pass_cipher, password)
try:
with open('.creds', 'w') as filename:
filename.write(user_encoded + '\n')
filename.write(user_secret + '\n')
filename.write(pass_encoded + '\n')
filename.write(pass_secret + '\n')
filename.close()
print '\nFile Written To: ', dir_path + '/.creds'
except Exception, e:
print e
if args['b']:
print((user_encoded, user_secret), (pass_encoded, pass_secret))
username = args['u']
password = args['p']
encrypt(username, password)
Decrypt The Data
def decrypt(dir_path, filename):
#Read '.creds' file and return unencrypted credentials (user_decoded, pass_decoded)
lines = [line.rstrip('\n') for line in open(dir_path + filename)]
user_encoded = lines[0]
user_secret = lines[1]
pass_encoded = lines[2]
pass_secret = lines[3]
# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length. This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE
PADDING = '{'
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
# create a cipher object using the random secret
user_cipher = AES.new(user_secret)
pass_cipher = AES.new(pass_secret)
# decode the encoded string
user_decoded = DecodeAES(user_cipher, user_encoded)
pass_decoded = DecodeAES(pass_cipher, pass_encoded)
return (user_decoded, pass_decoded)
The error message, "Ciphertext with incorrect length", has told us all. That means,
cipher text exceeded the limit length which can be calculated by (length of key,
1024.2048..)/8. to solve this problem, you can separate the cipher text and decrypt
them within a loop, then assemble all the decrypted byte string. My code in Python 3.6 for reference:
# 1024/8
default_length = 128
encrypt_str = str(data["content"])
sign_str = str(data["sign"])
try:
rsa_private_key = RSA.importKey(private_key)
encrypt_byte = base64.b64decode(encrypt_str.encode())
length = len(encrypt_byte)
cipher = PKCS115_Cipher(rsa_private_key)
if length < default_length:
decrypt_byte = cipher.decrypt(encrypt_byte, 'failure')
else:
offset = 0
res = []
while length - offset > 0:
if length - offset > default_length:
res.append(cipher.decrypt(encrypt_byte[offset: offset +
default_length], 'failure'))
else:
res.append(cipher.decrypt(encrypt_byte[offset:], 'failure'))
offset += default_length
decrypt_byte = b''.join(res)
decrypted = decrypt_byte.decode()