I have been working on a purely PROOF OF CONCEPT code stemming from the Diffie Hellman keypair exchange through SHA-256 encryption of those keys to then using that as a key for AES-256 encryption of text. I am only writing it for personal use. I know the prime numbers and secrets would need to be much longer..
The only code I have an issue with is the AES-256 encryption. It appears to work as it shows the encrypted text and is able to decrypt it but upon closer inspection, the encrypted text changes every time you run the program? I find this strange as the program is able to encrypt the given plain text, generate encrypted text and then decrypt this using the key given. However, this encrypted text changes every time the program is run using the same plaintext and same key.
Please see below the code...
from __future__ import print_function
import hashlib
from Crypto.Cipher import AES
from Crypto import Random
from base64 import b64encode, b64decode
# Variables
sharedPrime = 3721728827 # p
sharedBase = 2097383831 # g
p1Secret = 927391 # a
p2Secret = 193749 # b
# Begin
print( "\n--------------------------------------------\n" )
print( "Publicly Shared Variables:")
print( " Publicly Shared Prime (p): " , sharedPrime )
print( " Publicly Shared Base (g): " , sharedBase )
print( "\nPrivate Variables:")
print( " Private key a (a): " , p1Secret )
print( " Private key b (b): " , p2Secret )
print( "\n--------------------------------------------\n" )
# Person1 Sends Person2 A = g^a mod p
A = (sharedBase**p1Secret) % sharedPrime
print("Public Keys Sent Over Public Chanel: ")
print(" A = g^b mod p")
print(" Person1 Sends Over Public Chanel: " , A )
# Person2 Sends Person1 B = g^b mod p
B = (sharedBase ** p2Secret) % sharedPrime
print(" B = g^a mod p")
print(" Person2 Sends Over Public Chanel: ", B )
print( "\n--------------------------------------------\n" )
print( "Privately Calculated Shared Secret: " )
# P1 Computes Shared Secret: s = B^a mod p
p1SharedSecret = (B ** p1Secret) % sharedPrime
print( " s = B^a mod p")
print( " Person1 Shared Secret: ", p1SharedSecret )
# P2 Computes Shared Secret: s = A^b mod p
p2SharedSecret = (A**p2Secret) % sharedPrime
print( " s = A^b mod p")
print( " Person2 Shared Secret: ", p2SharedSecret )
print( "\n--------------------------------------------\n" )
# Converts DH secret to SHA256
print( "Converting shared key to SHA-256 key")
secretbyte = str(p1SharedSecret).encode()
print( " Shared secret to bytes: ", secretbyte)
shaV = hashlib.sha256(secretbyte).hexdigest()
print( " SHA-256 key: ", shaV)
key32 = shaV[0:32]
print( " SHA-256 key to AES-256 32bit secret key: ", key32)
print( "\n--------------------------------------------\n" )
#Begin AES-256 encryption
print( "Using SHA-256 32bit key for AES-256 encryption")
class AESCipher(object):
def __init__(self, key):
self.block_size = AES.block_size
self.key = hashlib.sha256(secretbyte).digest()
def encrypt(self, plain_text):
plain_text = self.__pad(plain_text)
iv = Random.new().read(self.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
encrypted_text = cipher.encrypt(plain_text.encode())
return b64encode(iv + encrypted_text).decode("utf-8")
def decrypt(self, encrypted_text):
encrypted_text = b64decode(encrypted_text)
iv = encrypted_text[:self.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
plain_text = cipher.decrypt(encrypted_text[self.block_size:]).decode("utf-8")
return self.__unpad(plain_text)
def __pad(self, plain_text):
number_of_bytes_to_pad = self.block_size - len(plain_text) % self.block_size
ascii_string = chr(number_of_bytes_to_pad)
padding_str = number_of_bytes_to_pad * ascii_string
padded_plain_text = plain_text + padding_str
return padded_plain_text
@staticmethod
def __unpad(plain_text):
last_character = plain_text[len(plain_text) - 1:]
return plain_text[:-ord(last_character)]
#Accept user input for encryption
message = input("Please enter message you want to be encrypted: ")
encMessage = AESCipher(key32)
encrypted = encMessage.encrypt(message)
print("Your encrypted message is: \n", encrypted)
print("Person 2 will now decrypt this message using the shared public key")
decrypted = encMessage.decrypt(encrypted)
print("Your decrypted message is: \n", decrypted)
The code produces the SHA-256 key of 72538667a2065257993b531746b9d92527cfe3caecc1457c4842e6a6caffe472
which I then truncate down to 32 characters to produce the AES-256 key of 72538667a2065257993b531746b9d925
. Any plaintext I then enter will be encoded and decoded successfully, however the encrypted text changes each time even though the same key and plaintext are entered. Am I doing something wrong?
As long as your ciphertext can be decrypted successfully every time, you don't need to worry about the ciphertext being different.
Your code uses CBC chaining mode, which is common that a different Initialization Vector is generated each time you encrypt something, to avoid identical plaintext looking identical after encryption (which would partially defeat the point of encryption since an attacker now knows whether it's the same plaintext).
Image source: Block cipher mode of operation - Wikipedia (public domain)