I'm trying to make a program that fetches someone's MAC Address from their machine, encrypts it, and then copies it to their clipboard. However, all of the encryption methods I see generate a fresh key and thus can't be deciphered without knowing the specific key that was used to encrypt the address. Is there a way to use one key to encrypt everything so all addresses can be decrypted with a single key, and a fresh key is not generated every single time?
you can try it, using Fernet Lib:
from cryptography.fernet import Fernet
# IMPORTANT: The encryption key must be binary, so the prefix 'b' before the string
# To create a random binary key, use 'generate_key' method as below:
# new_key = Fernet.generate_key()
crypto_key = b'dTlQeWw2u5oMoFPHXQ7vQHPaQUEiD71SYzWeJJAQQUk='
mac = '00:33:A4:D9:F1:E1'
fernet = Fernet(crypto_key)
enc_mac = fernet.encrypt(mac.encode())
dec_mac = fernet.decrypt(enc_mac).decode()
print(f'Fixed encryption key: {crypto_key}')
print('Original MAC string: ', mac)
print('Encrypted MAC string: ', enc_mac)
print('Decrypted MAC string: ', dec_mac)
You are describing asymmetric encryption here.
That exists and is a thing, yes. It works by by having a public key for encryption, and a private key for decryption.
There are multiple algorithms that implement that, like RSA.
RSA is supported by the python library cryptography.
A tutorial on how to use it can be found for example here:
https://nitratine.net/blog/post/asymmetric-encryption-and-decryption-in-python/
Working example
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import serialization, hashes
# Generate keys. This only has to be done once.
# Store the keys somewhere and distribute them with the program.
def generate_keys():
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public_key = private_key.public_key()
private_key_string = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
public_key_string = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
return (public_key_string, private_key_string)
# This is just for demonstration.
# In practice, don't generate them every time.
# Only generate them once and store them in a string or a file.
(public_key_string, private_key_string) = generate_keys()
# REMOTE COMPUTER
# Only use the public key here, the private key has to stay private.
public_key = serialization.load_pem_public_key(public_key_string, backend=default_backend())
mac_address = "01:23:45:67:89:AB"
mac_address_encrypted = public_key.encrypt(
mac_address.encode(),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# LOCAL SERVER
# Use private keys here to decrypt the MAC address
private_key = serialization.load_pem_private_key(private_key_string, password=None, backend=default_backend())
mac_address_decrypted = private_key.decrypt(
mac_address_encrypted,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
).decode()
print(mac_address_decrypted)
https://ideone.com/0eEyU6
You can use Import RSA library rsa
installing :
pip install rsa
Then encrypt the byte string with the public key.
Then the encrypted string can be decrypted with the private key.
The public key can only be used for encryption and the private can only be used for decryption
for examle:
import rsa
publicKey, privateKey = rsa.newkeys(512)
message = "Salio" #this is MAC Address
encMessage = rsa.encrypt(message.encode(), publicKey)
print("encrypted: ", encMessage)
decMessage = rsa.decrypt(encMessage, privateKey).decode()
print("decrypted : ", decMessage)
Related
I'm new to cryptography, sorry if I'm just trying to do something stupid.
So, don't hesitate to say if I tried to do something wrong or not in the right way or whatever it is.
I want to use RSA and I have two people: Alice and Bob.
At first, I wanted to encrypt the message with Alice's private key and later encrypt the encrypted message with Bob's public key, to safeguard the integrity/authenticity and confidentiality of the message.
I have learned that it is not possible to encrypt with the private key, the message needs to be signed and then verified.
I have seen that I need the signed message and the message non-signed to verify the signature.
According to my research at this point, I have two options:
Encrypt two messages one signed and one not and check the signature after the decryption,
Encrypt the concatenation of the message and the signed message with a separator, decrypt the text, get both with the separator, and after that check the signature.
I have decided the second option.
But with this method I have an error with the length that can be encrypted with the RSA key, maybe the right choice is to do as #Topaco said :
Encrypt the message
Sign the encrypted message
Give both to Bob
Verify the signature with the messages
Finally, decrypt the encrypted message?
But with this method, we have to send 2 different messages to Bob (?)
I feel like it's weird
Here is my code :
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import rsa, utils
# Generate private key for Alice
alice_private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
# Get the public key for Alice
alice_public_key = alice_private_key.public_key()
# Generate private key for Bob
bob_private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
# Get the public key for Bob
bob_public_key = bob_private_key.public_key()
# Sign the message using Alice's private key
message = b"Hello, world!"
signature = alice_private_key.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
# Concatenate the message and the signature using a separator
separator = b'|'
signed_message = message + separator + signature
# Encrypt the signed message using Bob's public key
ciphertext = bob_public_key.encrypt(
signed_message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Print the ciphertext
print(ciphertext)
# Decrypt the package using Bob's private key
plaintext = bob_private_key.decrypt(
ciphertext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Get the signature and message
# signature_length = 256 # assuming the signature is 256 bytes long
# signature = plaintext[-signature_length:]
# message = plaintext[:-signature_length]
# Split the plaintext to get the signature and message using the separator
message, signature = plaintext.split(separator)
# Verify the signature using Alice's public key
try:
alice_public_key.verify(
signature,
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print("Message send by Alice !")
except InvalidSignature as e:
print("Message not send by Alice !")
Thank you in advance for your help !
With help from #Topaco the result is the working code below.
But it still feels weird to me to have to send 2 messages in order to ensure the authenticity / integrity and the confidentiality of the message.
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import rsa
# Generate private key for Alice
alice_private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
# Get the public key for Alice
alice_public_key = alice_private_key.public_key()
# Generate private key for Bob
bob_private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
# Get the public key for Bob
bob_public_key = bob_private_key.public_key()
message = b"Hello, world!"
print(message)
# Encrypt the message using Bob's public key
encoded_message = bob_public_key.encrypt(
message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Sign the encoded message using the alice's private key
signed_encoded_message = alice_private_key.sign(
encoded_message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
# Print the encoded_message (To virtually send these to Bob)
print(encoded_message)
print(signed_encoded_message)
# Verify the signature using Alice's public key
alice_public_key.verify(
signed_encoded_message,
encoded_message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
# If the previous block doesn't raise an InvalidSignature exception
# we can decrypt the encoded_message
# Decrypt the package using Bob's private key
decoded_message = bob_private_key.decrypt(
encoded_message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Bob have the Alice's message
print(decoded_message)
I'm working with pycrpytodomex lib in python3.
Here I'm using a passphrase while generating an RSA key:
from Cryptodome.PublicKey import RSA
def encrypt(pass1):
key = RSA.generate(2048)
encrypted_key = key.exportKey(passphrase=pass1, pkcs=8, protection="scryptAndAES128-CBC").decode('utf')
return encrypted_key
I've put in a 24 char phassphrase, and this is the output:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFJTBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQIpUW82j76E18CAkAA
AgEIAgEBMB0GCWCGSAFlAwQBAgQQKDB2dU0KvLw9KvDq/pkRRgSCBNAru/ZpRI7o
pHIVVXb8iEuQUc+suZXwH9IBTJzzjMAq1iEETWSCzMB1VWz+PBJ8RmUB0qEie0Cf
Kg6/j0tw3dcZ7P48GXjzFvV4e5gaFtGy4khjGSa5/T97n/pi5oZZ7qsnJRktlxdS
1E2nceChpQwLUPdkVkeG5F3iWc2U9Pgh165m5GSg+ai3Dl7YpJEvhA7LQv55a4kZ
HuTjTnoMxakaA4ncYJoQhiZ0cGKZrnBoAWB7tAwVwnNwobTUpIuReeIJYd+B09gl
ZaiTTJQQAyJ60hRz+bQQioT2HBg1nXi+9KzwPw1AMUY0nU9Y8kUMNtjUzbv7Y2Av
2KiVtlFZ4ottihCWX9W85UQ4gQ4o4/ZyT5B4b1VMiUTO7pCaheHfqPokGU9/MzPO
aZCqDL43efebKqdJ6ZJr0aTkJlAGWxHLJrvd87Fh5yvPdkTvZh++0xHcmHigtpDB
Ufe+jJJE9Xr6VpOtecW8KkVhmC3I6MwnwyhNn3vtSRfGWZi/V23DpdwFUXb8gZSe
Qou7GdjMZ5VXalhrqbVmz1RaS4yTPkXhd71hHZHkWF6jQmNfIZaBAGum7FRF8zgz
PmHvb3nbfYAwHz/I/fHOqsFM8N9UvTnHK5JLSfhKosdUtrq3Hotu6iU7EzLgl7qt
k1yfVbKiyMUFOFbU4n8hmOKy7eVpv1UW7Dh4guq/9xd7vBRZC03gSUBhJY1lj45p
vMqeASkwVSENHoHWS+jkuPgq6IrgYsF3VPfUw3hy0xhj9PSZoArikMaifIRjVwwq
Ayltn5yAU0wbAygwrnHG64t9MdFnoy3RvWe43sG7D+6wzrz53tUKqujV1Ve7rSmG
G6HoXNvwKpVCdGaURbgpac+UaDFwSKkcPEReMMEw46HvH6/s1Ufxv/ecQb1ac0dh
DgkiSNUPSAPDOhRxiI/NzGxny50IkIcI8HwigYFZkTZn081kaps9NEV8L/KfzmNn
KnJO9H3018BMR3TAPGsntUunjfTS9bVoUlYCcbSX/Xpz+sGjWJnnF9KdbpUPLTS2
B/YwvPaU6EtoehfavWX5qIA670hiPSwSGlbdT7KVx5cTzDuk5gOQCFGNWoosoPCV
SyeNpS/IUbPpk6s2mvQRoX09XsfzTqYkApByBdErEHWinkSmyrPNRdi/NXBerWW2
EKPQQw6dwO6RkD9sAv9ncoFkW6b3kn7FiUoFEhUUeVi4tfP3YcSiKzcSvNxgdc9x
KuRzfq+HD8DgHxgY4wciJxcErK52/snrV0vvkf6l2IczQV2tw5tDWF476E0iI2cV
7+ycp9crAjfS9nuMUr2RTZV0x9J1jc8pIZyocTfLMPHaqR5P9LmKmDfoy1U29PKu
TiiFagL2Ukon1r1KAIDlfHZP6MfqTWfxyfhfAMzRCDgR4uJAZi+JEeiZg21Y7caI
tBN5A92Ymiw7jIalAdUT7t1JOwF635c/fc8m33JNYQF+v8GCDBRFoPN2YpsT5ZvJ
P2EbcCNJxcqrSrf4SJl+tPkSuJPCQfeyX3MmyI9TEN5BPlCSpsDsO6VzRuDuGkMq
/JwN4VTltFD02Zhl+KotUkEjKsWLzIcIvaMMIh9jEcT+8TvRnWgBbwTpgIw/LDtv
Gv6LSf9906/YcHVI1xJRF2yTTWVFdLF04Q==
-----END ENCRYPTED PRIVATE KEY-----
I am able to validate the passphrase with the encrycpted key string by using this function:
def decrypt(encoded_key,pass1):
try:
key = RSA.import_key(encoded_key, passphrase=pass1)
return True
except ValueError:
return False
Supposing one only has the private key and not the passphrase (ie, you). Would it still be possible to derive the passphrase using this private key?
Alternative: Would it be possible to construct any passphrase that will return True on the decrypt() function above?
Can you find out what the (or a valid) passphrase is for the above key? What computing power and time did it take?
scryptAndAES128-CBC
It is not possible to derive the password from the ciphertext (to our current knowledge)
Can you find out what the (or a valid) passphrase is for the above key
The problem with passwords are people. Reusing passwords, using simple passwords,..
Usually passwords are looked up using dictionaries and combination tools. So the question is how "guessable" or random password is used.
I encrypt string:
def encrypt(self, message):
obj = AES.new('This is a key123'.encode("utf8"), AES.MODE_CFB, 'This is an IV456'.encode("utf8"))
encrypted = obj.encrypt(message.encode("utf8"))
return encrypted
How can I store encrypted in a file and read to decrypt using:
def decrypt(self, encrypted):
obj = AES.new('This is a key123'.encode("utf8"), AES.MODE_CFB, 'This is an IV456'.encode("utf8"))
decrypted=obj.decrypt(encrypted)
return decrypted
THe library "pycryptodome" has a full running example for AES encryption to a file and vice versa.
I know that the example runs another mode and stores additional data but that might be helpfull to you as the usage of a static IV is UNSECURE: https://pycryptodome.readthedocs.io/en/latest/src/examples.html
The following code generates a new AES128 key and encrypts a piece of data into a file. We use the EAX mode because it allows the receiver to detect any unauthorized modification (similarly, we could have used other authenticated encryption modes like GCM, CCM or SIV).
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
key = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_EAX)
ciphertext, tag = cipher.encrypt_and_digest(data)
file_out = open("encrypted.bin", "wb")
[ file_out.write(x) for x in (cipher.nonce, tag, ciphertext) ]
file_out.close()
At the other end, the receiver can securely load the piece of data back (if they know the key!). Note that the code generates a ValueError exception when tampering is detected.
from Crypto.Cipher import AES
file_in = open("encrypted.bin", "rb")
nonce, tag, ciphertext = [ file_in.read(x) for x in (16, 16, -1) ]
# let's assume that the key is somehow available again
cipher = AES.new(key, AES.MODE_EAX, nonce)
data = cipher.decrypt_and_verify(ciphertext, tag)
With this command it is possible to generate an RSA public-private key pair:
ssh-keygen -f key
Now I would like to load these keys in Python using module cryptography. Example:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
with open("key.pub", "rb") as f:
datapub = f.read()
public_key = serialization.load_ssh_public_key(datapub, backend=default_backend())
But now: How do you generate a fingerprint from this public key? With OpenSSH this can be done using ssh-keygen -lf key.pub. But how do you the same in Python?
First of all: Thank you very much Topaco, you provided the essential hint to me how to generate such a fingerprint. I crosschecked this with other sources on the WWW and can no provide code for anybody to use.
Let's assume we've loaded the key and stored it in public_key. Then a fingerprint can be generated as follows:
rawKeyData = public_key.public_bytes(
encoding=serialization.Encoding.OpenSSH,
format=serialization.PublicFormat.OpenSSH,
)
# prepare for hashing
m = re.match("ssh-rsa ([A-Za-z0-9+/=]+)", rawKeyData.decode("utf-8"))
assert m is not None
base64BinaryData = m.group(1).encode("utf-8")
rawBinaryData = base64.b64decode(base64BinaryData)
# now create the hash
hexDigest = hashlib.md5(rawBinaryData).hexdigest()
# formatting for output similar to "ssh-keygen -l -E md5 -f <pubkeyfile>"
chunks = [ hexDigest[i:i+2] for i in range(0, len(hexDigest), 2) ]
fingerprint = str(self.__public_key.key_size) + "MD5:" + ":".join(chunks) + " (RSA)"
This provides output like this:
2048 MD5:bd:5a:67:a3:4c:46:9d:2c:63:78:7e:68:bc:82:eb:23 (RSA)
The only difference to OpenSSH fingerprints: Here no email address is included in the output.
Some remarks:
regex
I use a regular expression here to parse the output. This is done for safety as this way I ensure that the output matches the expectations of data processing here.
base64
base64 might add padding to the data.
base64 is safe to use as padding is deterministic.
md5
Here the MD5 output is used.
You can safely replace MD5 by any other hash algorithm if you want - e.g. SHA256.
Following the tutorial listed on the cryptography library documentation I have successfully created a function that demonstrates a Diffie-Hellman exchange. I am now trying to create a proof of concept socket server and socket client.
An undocumented requirement of this application is the sending of the public key to the client. This requires the DHPublicKey object to be serialised and serialised in order for it to be sent over the socket.
By doing this however the shared keys are not the same! I have tried to use different encoding formats (PEM for example) to see if this made a difference. Unfortunatly it has not. I get a different shared key on both sides. Here is an example of my code.
Client
parameters = dh.generate_parameters(generator=2, key_size=1024, backend=default_backend())
client_private_key = parameters.generate_private_key()
client_public_key = client_private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
# Recv Server Pub key
length = s.recv(2) # Prepend the length of the message
server_public_key = s.recv(int.from_bytes(length, "big"))
print("Got server public key: " + str(server_public_key))
server_public_key = load_der_public_key(server_public_key, default_backend())
# Send Pub key
s.send(len(client_public_key).to_bytes(2, "big") + client_public_key)
print("Generating shared key...")
shared_key = client_private_key.exchange(server_public_key)
print("Our shared key!: " + str(shared_key))
Server
parameters = dh.generate_parameters(generator=2, key_size=1024, backend=default_backend())
server_private_key = parameters.generate_private_key()
server_public_key = server_private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
newsocket.send(len(server_public_key).to_bytes(2, "big") + server_public_key)
print("Sent server public key: " + str(server_public_key))
length = newsocket.recv(2) # Prepend the length of the message
client_public_key = newsocket.recv(int.from_bytes(length, "big"))
client_public_key = load_der_public_key(client_public_key, default_backend())
# Send the public key to the client
shared_key = server_private_key.exchange(client_public_key)
print("Our shared key is: " + str(shared_key))
As stated, I'm using the Python 3 Library Cryptography and use the following imports
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import dh
from cryptography.hazmat.primitives.serialization import PublicFormat, Encoding, load_der_public_key
Also, putting the code into one file (Without network serialization) it works! the key is the same! Here is an example of the working code
print("SERVER: Performing DH exchange. DH 2048-bit key size")
parameters = dh.generate_parameters(generator=2, key_size=2048, backend=default_backend()) # Generate a 256-byte key
print("SERVER: Generating server private and public keys")
server_private_key = parameters.generate_private_key()
server_peer_public_key = server_private_key.public_key()
print("CLIENT: Generating private and public keys")
client_private_key = parameters.generate_private_key()
client_peer_public_key = client_private_key.public_key()
print("SERVER: Sending public key to client")
print("CLIENT: Sending public key to server")
server_shared_key = server_private_key.exchange(client_peer_public_key)
client_shared_key = client_private_key.exchange(server_peer_public_key)
print("server key is: " + str(server_shared_key))
print("client key is: " + str(client_shared_key))
What am I doing wrong when serialising or deserialising the key?
Your problem isn’t to do with serializing and deserializing the key, it is because you are generating different DH parameters on the server and the client. They need to be the same for Diffie Hellman to work.
You could generate the parameters on the server and send them to the client, but a better option is to use a set of predefined parameters, for example group 14 defined in RFC 3526.
To do that, change the line
parameters = dh.generate_parameters(generator=2, key_size=1024, backend=default_backend())
in both client and server to:
p = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF
g = 2
params_numbers = dh.DHParameterNumbers(p,g)
parameters = params_numbers.parameters(default_backend())
Now both client and server will be using the same set of parameters and the key agreement will work. It will also be much faster, parameter generation is a costly process.
Your code in a single file works because you only generate one set of parameters that is used by both sides of the exchange.