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.
Related
Purpose of this question
This question was created to share information, because the documentation for various modules is limited on this topic. This question might become a community wiki.
Use Case
How to securely move sensitive data from memory to disk?
The broader use case is how to do this without exposing the sensitive data by writing it to a temporary file on the local disk.
What is sensitive data?
Sensitive data is information that must be protected and is inaccessible to outside parties unless specifically granted permission to view the data. This type of data can include, but not limited to PII (Personally identifiable information), PHI (Protected health information) or GDPR sensitive data.
It some cases the sensitive data might be generated during an investigation of a computer system for legal reasons (e.g., surveillance warrant) or covert reasons (e.g. spying on a potential adversary).
There are several ways to accomplish this and I will be providing several examples that either use cleartext data or encrypted data and encrypted compressed files with password protection.
pyzipper
Here is one method to accomplish this use case using pyzipper
import pyzipper
# generate data
secret_data = b'xyz' * 10
# password for the zip file
secret_password = b'super secret password'
# Create encrypted password protected ZIP file on disk
with pyzipper.AESZipFile('password_protected.zip',
'w',
compression=pyzipper.ZIP_LZMA,
encryption=pyzipper.WZ_AES) as zf:
zf.setpassword(secret_password)
zf.writestr('secret_data.txt', secret_data)
# Read encrypted password protected ZIP file from disk
with pyzipper.AESZipFile('password_protected.zip', 'r') as zf:
zf.setpassword(secret_password)
my_secrets = zf.read('secret_data.txt')
print(my_secrets)
# output
xyzxyzxyzxyzxyzxyzxyzxyzxyzxyz
When I was reviewing the code for pyzipper it wasn't clear if the secret data was being protected during the read and write processes. For extra security I decided to encrypt the secret data using the cryptography package. This package was first released in 2014 and has been continually maintained by hundreds of contributors.
import os
import base64
import pyzipper
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
# password for the cryptographic process
password = b"password"
# random salt for the cryptographic process
salt = os.urandom(16)
# key derivation function (KDF) for the cryptographic process
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=390000,
)
# create the encryption key for the cryptographic process
encryption_key = base64.urlsafe_b64encode(kdf.derive(password))
# https://cryptography.io/en/latest/fernet/
f = Fernet(encryption_key)
# generate data
secret_data = b'xyz' * 10
encrypted_data = f.encrypt(secret_data)
# password for the zip file
secret_password = b'super secret password'
# Create encrypted password protected ZIP file on disk
with pyzipper.AESZipFile('password_protected.zip',
'w',
compression=pyzipper.ZIP_LZMA,
encryption=pyzipper.WZ_AES) as zf:
zf.setpassword(secret_password)
zf.writestr('encryption_key.txt', encryption_key)
zf.writestr('encrypted.txt', encrypted_data)
# Read encrypted password protected ZIP file from disk
# and decrypt data
with pyzipper.PyZipFile('password_protected.zip', 'r') as zf:
zf.setpassword(secret_password)
my_secrets = zf.read('encrypted.txt')
my_key = zf.read('encryption_key.txt')
f2 = Fernet(my_key)
decrypt_message = f2.decrypt(my_secrets)
print(decrypt_message)
# output
b'xyzxyzxyzxyzxyzxyzxyzxyzxyzxyz'
For an added layer of security an in-memory file system can be used. In this scenario the secret data is encrypted and then written to an in-memory file, which is then written to an encrypted password protected ZIP file.
import fs
import os
import base64
import pyzipper
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
# create in-memory file system
mem_fs = fs.open_fs('mem://')
mem_fs.makedir('hidden_dir')
# password for the cryptographic process
password = b"password"
# random salt for the cryptographic process
salt = os.urandom(16)
# key derivation function (KDF) for the cryptographic process
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=390000,
)
# create the encryption key for the cryptographic process
encryption_key = base64.urlsafe_b64encode(kdf.derive(password))
# https://cryptography.io/en/latest/fernet/
f = Fernet(encryption_key)
# generate data
secret_data = b'xyz' * 10
encrypted_data = f.encrypt(secret_data)
# password for the zip file
secret_password = b'super secret password'
# write encryption key to an in-memory file
with mem_fs.open('hidden_dir/encryption_key.txt', 'wb') as in_file_in_memory:
in_file_in_memory.write(encryption_key)
in_file_in_memory.close()
# write encrypted data to an in-memory file
with mem_fs.open('hidden_dir/encrypted.txt', 'wb') as in_file_in_memory:
in_file_in_memory.write(encrypted_data)
in_file_in_memory.close()
# Create encrypted password protected ZIP file on disk
with pyzipper.AESZipFile('password_protected.zip',
'w',
compression=pyzipper.ZIP_LZMA,
encryption=pyzipper.WZ_AES) as zf:
zf.setpassword(secret_password)
zf.writestr('encryption_key.txt', mem_fs.readbytes('hidden_dir/encryption_key.txt'))
zf.writestr('encrypted.txt', mem_fs.readbytes('hidden_dir/encrypted.txt'))
# Read encrypted password protected ZIP file from disk
# and decrypt data
with pyzipper.PyZipFile('password_protected.zip', 'r') as zf:
zf.setpassword(secret_password)
my_secrets = zf.read('encrypted.txt')
my_key = zf.read('encryption_key.txt')
f2 = Fernet(my_key)
decrypt_message = f2.decrypt(my_secrets)
print(decrypt_message)
b'xyzxyzxyzxyzxyzxyzxyzxyzxyzxyz'
7zip
Another way to accomplish the use case is by using 7zip and the Python subprocess module. For this example I show how to create the in-memory file system, encrypt the data, write the data and the encryption key to a directory in memory, write these files to an encrypted password protected 7zip file on the disk. I also showed how to extract the files from this 7zip archive and decrypt the secret data.
import fs
import os
import base64
from subprocess import Popen, PIPE
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
# create in-memory file system
mem_fs = fs.open_fs('mem://')
mem_fs.makedir('hidden_dir')
# password for the cryptographic process
password = b"password"
# random salt for the cryptographic process
salt = os.urandom(16)
# key derivation function (KDF) for the cryptographic process
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=390000,
)
# create the encryption key for the cryptographic process
encryption_key = base64.urlsafe_b64encode(kdf.derive(password))
# https://cryptography.io/en/latest/fernet/
f = Fernet(encryption_key)
# generate data
secret_data = b'xyz' * 10
encrypted_data = f.encrypt(secret_data)
# password for the zip file
secret_password = b'super secret password'
# write encryption key to an in-memory file
with mem_fs.open('hidden_dir/encryption_key.txt', 'wb') as in_file_in_memory:
in_file_in_memory.write(encryption_key)
in_file_in_memory.close()
# write encrypted data to an in-memory file
with mem_fs.open('hidden_dir/encrypted.txt', 'wb') as in_file_in_memory:
in_file_in_memory.write(encrypted_data)
in_file_in_memory.close()
# Create encrypted password protected 7ZIP file on disk
file_names = ['encryption_key.txt', 'encrypted.txt']
data_elements = [mem_fs.readbytes('hidden_dir/encryption_key.txt'), mem_fs.readbytes('hidden_dir/encrypted.txt')]
for file_name, data_element in zip(file_names, data_elements):
args = [f'{full_path}/7zz', 'a', '-mem=AES256', '-y', f'-p{secret_password}', f'-si{file_name}', f'{zip_archive_name}']
proc_zip = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
proc_zip.stdin.write(data_element)
proc_zip.communicate()
# Unzip encrypted password protected 7ZIP file from disk
outfile_directory_name = 'test_files'
args = [f'{full_path}/7zz', "x", f'-p{secret_password}', f"{zip_archive_name}", f'-o{outfile_directory_name}']
proc_unzip = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
proc_unzip.communicate()
# decrypt data from disk
with open(f'{full_path}/{outfile_directory_name}/encryption_key.txt', 'rb') as key_file:
with open(f'{full_path}/{outfile_directory_name}/encrypted.txt', 'rb') as text_file:
f = Fernet(key_file.read())
print(f.decrypt(text_file.read()))
# output
b'xyzxyzxyzxyzxyzxyzxyzxyzxyzxyz'
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'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)
I have a hex encoded string which I need to decrypt using RSA_OAEP algorithm.Below is my code snippet:
final_token = decoded_req['token']
print(final_token)
print("Converting actual token into bytes")
## Hex encoded string to bytes
token_to_bytes = bytes.fromhex(final_token)
print(token_to_bytes)
## Read the private key
with open('private.rsa', 'r') as pvt_key:
miPvt = pvt_key.read()
## Decrypt the token using RSA_OAEP
print("Decrypting the token")
rsakey_obj = RSA.importKey(miPvt)
cipher = PKCS1_OAEP.new(rsakey_obj)
dec_token = cipher.decrypt(token_to_bytes)
print(dec_token)
Below is the command line output:
6c79855ca15a3ac0308d17db97a1c576b6f35e4bb630da22867d0d081d55f05e1b1c640d7e7bd8c8de06b6a03e2ef7449e31fa9c0f0c675ebbf838bb880a4c6e309391441e1c41a0c42fbe11eaf6306bb39e7bbbffbe2dbced8c9bb07679ff18cef348b2b7ce08aa05028fda818ac4ce08ad84246c94afbcac405db5258c5333f66148007be8fa5384fa78a0d54ccf26028571180723b2a61fe18ebd0124f7e008902e79b48a53d29953ffde949878b4eb99d376ccf68341b0ec2ceb03b050e7c260f8905dccddb5e50cb331a3bb6bc034fc06dfaf722fd8fad8cb7a311b020be8b1a9115279173b18f46ccc0e28c71f0a6f67e8362a103fa4729057d8924ef0a6fb5a33eeb5b42cdc764fde53b9d6338472fc73e80cf785eb54ffb4ac15a6bf63db19baca586508b8160bf73dbad322221ddb632f5ae8040ef86805a408fc7df6f4a8ee5c7a444a5a13dbdcb8b958c52a8bdc4394e5deaabce203a3ff8675079e6618ac3558cdaf5ce6da77ca667bd5349ba82cc1ad3f71a662a2b7c8b47cc548209264a45a65b3f6f25fedcad8449699a19e2910d24c13b44e3e9445a62f88213346e00048480218996ade7e01151b7e038fbc8a5ff03bfa511126763d4571dec6ce0f5183302a99eee62facabe09211f3a61b8d154a38f0ee9a2647998e2ec1b8ee96b52c443f408cec24140838616c79d82cba4b77171b621f261539e239
Converting actual token into bytes
b'ly\x85\\\xa1Z:\xc00\x8d\x17\xdb\x97\xa1\xc5v\xb6\xf3^K\xb60\xda"\x86}\r\x08\x1dU\xf0^\x1b\x1cd\r~{\xd8\xc8\xde\x06\xb6\xa0>.\xf7D\x9e1\xfa\x9c\x0f\x0cg^\xbb\xf88\xbb\x88\nLn0\x93\x91D\x1e\x1cA\xa0\xc4/\xbe\x11\xea\xf60k\xb3\x9e{\xbb\xff\xbe-\xbc\xed\x8c\x9b\xb0vy\xff\x18\xce\xf3H\xb2\xb7\xce\x08\xaa\x05\x02\x8f\xda\x81\x8a\xc4\xce\x08\xad\x84$l\x94\xaf\xbc\xac#]\xb5%\x8cS3\xf6aH\x00{\xe8\xfaS\x84\xfax\xa0\xd5L\xcf&\x02\x85q\x18\x07#\xb2\xa6\x1f\xe1\x8e\xbd\x01$\xf7\xe0\x08\x90.y\xb4\x8aS\xd2\x99S\xff\xde\x94\x98x\xb4\xeb\x99\xd3v\xcc\xf6\x83A\xb0\xec,\xeb\x03\xb0P\xe7\xc2`\xf8\x90]\xcc\xdd\xb5\xe5\x0c\xb31\xa3\xbbk\xc04\xfc\x06\xdf\xafr/\xd8\xfa\xd8\xcbz1\x1b\x02\x0b\xe8\xb1\xa9\x11Ry\x17;\x18\xf4l\xcc\x0e(\xc7\x1f\nog\xe86*\x10?\xa4r\x90W\xd8\x92N\xf0\xa6\xfbZ3\xee\xb5\xb4,\xdcvO\xdeS\xb9\xd63\x84r\xfcs\xe8\x0c\xf7\x85\xebT\xff\xb4\xac\x15\xa6\xbfc\xdb\x19\xba\xcaXe\x08\xb8\x16\x0b\xf7=\xba\xd3""\x1d\xdbc/Z\xe8\x04\x0e\xf8h\x05\xa4\x08\xfc}\xf6\xf4\xa8\xee\\zDJZ\x13\xdb\xdc\xb8\xb9X\xc5*\x8b\xdcC\x94\xe5\xde\xaa\xbc\xe2\x03\xa3\xff\x86u\x07\x9ef\x18\xac5X\xcd\xaf\\\xe6\xdaw\xcaf{\xd54\x9b\xa8,\xc1\xad?q\xa6b\xa2\xb7\xc8\xb4|\xc5H \x92d\xa4Ze\xb3\xf6\xf2_\xed\xca\xd8D\x96\x99\xa1\x9e)\x10\xd2L\x13\xb4N>\x94E\xa6/\x88!3F\xe0\x00HH\x02\x18\x99j\xde~\x01\x15\x1b~\x03\x8f\xbc\x8a_\xf0;\xfaQ\x11&v=Eq\xde\xc6\xce\x0fQ\x830*\x99\xee\xe6/\xac\xab\xe0\x92\x11\xf3\xa6\x1b\x8d\x15J8\xf0\xee\x9a&G\x99\x8e.\xc1\xb8\xee\x96\xb5,D?#\x8c\xec$\x14\x088aly\xd8,\xbaKw\x17\x1bb\x1f&\x159\xe29'
Decrypting the token
Traceback (most recent call last):
File "script.py", line 80, in <module>
dec_token = cipher.decrypt(token_to_bytes)
File "/home/venv/lib/python3.6/site-packages/Crypto/Cipher/PKCS1_OAEP.py", line 201, in decrypt
raise ValueError("Incorrect decryption.")
ValueError: Incorrect decryption.
Can someone help me in resolving this issue?
I suspect you have not correctly transferred your encrypted token. But there isn't a lot to go on.
I've used you example code though to help me write the below which does seem to work, (thank you for that) or at least it decrypted a value from another implementation.
In my case it was a node JS implementation
var nodeRsa = require("node-rsa");
const key = new nodeRsa( "-----BEGIN PUBLIC KEY-----\n\
.
.
.
-----END PUBLIC KEY-----");
key.encrypt("passwd","base64");
If I took the output of the above and put in a file called 'ciphertext', this following python decrypts the message correctly.
import Crypto.Cipher.PKCS1_OAEP as rsaenc
import Crypto.PublicKey.RSA as RSA
import codecs
## Load the private key and the base 64 ciphertext.
with open("key.pem") as f:
keystring = f.read()
with open("ciphertext","rb") as f:
base64msg = f.read()
# Create the binary ciphertext
binmsg=codecs.decode(base64msg,'base64')
# Setup the RSA objetcs
k = RSA.importKey(keystring)
cipher = rsaenc.new(k)
plaintext = cipher.decrypt(binmsg)
print (plaintext)
I'm encrypting a string in ColdFusion
enc_string = '7001010000006aaaaaabbbbbb';
uid = encrypt(enc_string,'WTq8zYcZfaWVvMncigHqwQ==','AES','Hex');
// secret key for tests only
Result:
DAEB003D7C9DBDB042C63ED214E85854EAB92A5C1EC555765B565CD8723F9655
Later I want to decrypt that string in Node (just an example)
uid='DAEB003D7C9DBDB042C63ED214E85854EAB92A5C1EC555765B565CD8723F9655'
decipher = crypto.createDecipher('aes-192-ecb', 'WTq8zYcZfaWVvMncigHqwQ==')
decipher.setAutoPadding(false);
dec = decipher.update(uid, 'hex', 'utf8')
dec += decipher.final('utf8')
I have tried few ciphers but with no luck. I would like not to modify the ColdFusion code to make it work, but if there is no other chance I will do that. I want to send some ciphered data with GET from one site to another. Any advice?
EDIT: I tried all AES, DES, with IV, without IV, with & without padding. Tried also base64. Also with no luck.
ColdFusion Encryption with IV
enc_string = '7001010000006aaaaaabbbbbb';
myKey = Tobase64("abcdefghijkl1234");
myIV = charsetDecode("abcdefghijkl9876", "utf-8");
uid=encrypt(enc_string,myKey,'AES/CBC/PKCS5Padding','hex',myIV);
Encrypted uid value is:
614981D0BC6F19A3022FD92CD6EDD3B289214E80D74823C3279E90EBCEF75D90
Now we take it to node:
var Crypto = require('crypto');
var key = new Buffer('abcdefghijkl1234');
var iv = new Buffer('abcdefghijkl9876');
var encrypted = new Buffer('614981D0BC6F19A3022FD92CD6EDD3B289214E80D74823C3279E90EBCEF75D90', 'hex');
var decipher = Crypto.createDecipheriv('aes-128-cbc', key, iv);
var decrypted = decipher.update(encrypted);
var clearText = Buffer.concat([decrypted, decipher.final()]).toString();
console.log(clearText);
Result is:
7001010000006aaaaaabbbbbb
what was expected.
Origin of the problem
Originally in Coldfusion i was using key generated by:
GenerateSecretKey(algorithm [,keysize]);
which generated base64 key which was required by encrypt method. And there was no 'secret' from which was generated.
In Node Crypto method createDecipheriv gets Buffer as params. Buffers requires secret, not keys. I'm not sure why it doesn't work without IV.
What need to be changed in Coldfusion
Don't use GenerateSecretKey if you want to decrypt in other language than CF
Use Tobase64(secret) to generate key
Use IV and generate it using charsetDecode(ivSecret, "utf-8")
Algorithm: AES/CBC/PKCS5Padding
For AES/ECB look #Leigh answer
In Node every input is Buffer.
I think that this short tutorial can help also people who have same issue in other languages like cf->php or cf->python.
A few clarifications and corrections to the accepted answer
Short answer:
Use "crytographically random" keys produced by GenerateSecretKey() rather than creating one with Tobase64(secret).
Although technically ECB mode works (see below), CBC mode is preferred as the more secure method. For CBC, see my full example: Encrypt in ColdFusion, Decrypt in Node.js
Longer Answer:
Don't use GenerateSecretKey if you want to decrypt in other language
No, it is perfectly fine to use the generated value with the encryption functions in other languages - as long as they follow the specifications. That does not mean the values can be used in any language exactly "as is". It may need tweaking to conform with language X or Y's implementation. (For example, a function in language X may expect the key to be a hexadecimal string, instead of base64. So you may need to convert the key value first). That did not quite happen in the original code, which is why the decryption did not work.
GenerateSecretKey() produces a cryptographically random key for the specified algorithm. (While CF generates base64 encoded key strings, it could just as easily be hex encoded. The binary value of the key is what matters.) The generated key is suitable for use with any language that implements the same encryption algorithms and key sizes. However, as I mentioned in the earlier comments, symmetric encryption only works if everything matches. You must use the same key, same algorithm, same iv, etcetera for both encrypting AND decrypting. In the original code, both the "key" and "algorithm" values were different. That is why the decryption failed.
The original code used crypto.createCipher(algorithm, password). Per the API, "password" is used to derive the cipher key. In other words, the Node.js code was using a totally different key than in the CF code. Also, Node.js was configured to use a 192 bit key, whereas the CF code was using a 128 bit key.
To answer your original question, yes - you can use ECB mode (though it is strongly discouraged). However, it requires modifying the CF code to derive the same password Node.js will be using. (The other direction is not possible as it involves one-way hashing.)
To derive the "password" in CF, decode the secret key string into binary and generate an md5 hash. Then decode the hash into binary and re-encode it as base64 to make the encrypt() function happy.
CF:
plainText = "7001010000006aaaaaabbbbbb";
secretKey = "WTq8zYcZfaWVvMncigHqwQ==";
keyHash = hash(binaryDecode(secretKey, "base64"), "md5");
nodeJSPassword = binaryEncode(binaryDecode(keyHash, "hex"), "base64");
encryptedText = encrypt(plainText, nodeJSPassword, "AES/ECB/PKCS5Padding", "Hex");
writeOutput(encryptedText);
Result:
C43E1179C15CD962373A6E28486D6F4ADB12FBB6731EF99C9212474E18D51C70
On the Node.js side, modify the code to use a 128 bit key, not 192. Also, password strings are first decoded into binary. When creating the cipher object, you need to indicate the input string is base64 encoded, to ensure it is interpreted properly.
Node.js
var password = 'WTq8zYcZfaWVvMncigHqwQ==';
var passwordBinary = new Buffer(password, "base64");
var encrypted = 'C43E1179C15CD962373A6E28486D6F4ADB12FBB6731EF99C9212474E18D51C70'
var crypto = require('crypto');
var decipher = crypto.createDecipher('aes-128-ecb', passwordBinary );
var decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
console.log(decrypted);
Result:
7001010000006aaaaaabbbbbb
Having said that, Using ECB mode is NOT recommended. The preferred method is CBC mode (with a random iv), which generates less predictable output and hence is more secure.
than CF Use Tobase64(secret) to generate key
Along those same lines, while you can technically use arbitrary strings, ie "abcdefghijkl1234", to generate a key - DON'T. A very important part of strong encryption is using secret keys that are "truly random and contain sufficient entropy". So do not just do it yourself. Use a proven function or library, like GenerateSecretKey(), which was specifically designed for the task.
The ciphers you are using to encrypt and decrypt are not equal.
For Node to decrypt your result to the expected string, you should first make sure that encrypting the initial string in Node gives you the same encrypted result.
Consider the following, which runs through all known (to me) AES ciphers in Node, and tries to get the same encrypted result that you get from Coldfusion:
var crypto = require('crypto');
var key = 'WTq8zYcZfaWVvMncigHqwQ==';
var algorithm;
var ciphers = [
'aes-128-cbc',
'aes-128-cbc-hmac-sha1',
'aes-128-cfb',
'aes-128-cfb1',
'aes-128-cfb8',
'aes-128-ctr',
'aes-128-ecb',
'aes-128-gcm',
'aes-128-ofb',
'aes-128-xts',
'aes-192-cbc',
'aes-192-cfb',
'aes-192-cfb1',
'aes-192-cfb8',
'aes-192-ctr',
'aes-192-ecb',
'aes-192-gcm',
'aes-192-ofb',
'aes-256-cbc',
'aes-256-cbc-hmac-sha1',
'aes-256-cfb',
'aes-256-cfb1',
'aes-256-cfb8',
'aes-256-ctr',
'aes-256-ecb',
'aes-256-gcm',
'aes-256-ofb',
'aes-256-xts',
'aes128',
'aes192',
'aes256'
]
function encrypt(text){
var cipher = crypto.createCipher(algorithm, key);
var crypted = cipher.update(text,'utf8','hex');
crypted += cipher.final('hex');
return crypted;
}
for (var i = 0; i < ciphers.length; i++) {
algorithm = ciphers[i];
console.log(encrypt("7001010000006aaaaaabbbbbb"));
}
If you run this you will get the following output:
ab1e8ddd6be53040fcfdf07578704ed9831c4e962eddd36899fc3819b51d6ade
ab1e8ddd6be53040fcfdf07578704ed9831c4e962eddd36899fc3819b51d6ade
ff19a0b91dad25671632581655f53139ac1f5554383951e255
e4756965c26df5b2e7e2e5291f5a2b1bc835b523ae7e39da0d
ff93cfff713798bcf94ff60fb61a6d9d4ae0a7ad6672e77a22
ff19a0b91dad25671632581655f5313940ed1d69d874cf04d7
70ef98bda47bd95e64221c144c4fdec1e5ad1422ca9f4589653214577adf9d9a
918559eaab9a983f91160dbdb2f093f55b0a2bc011fbe1b309
ff19a0b91dad25671632581655f53139cb62004d669030b400
2c4e36eb6b08107bbdf9c79c2f93160211128977181fee45ab
37fed7d50a56f42fa26805a69c38b12b519e59116702a9f0d15a437791600b3a
01f4d909c587684862ea9e27598f5d5c489028a223cc79be1a
0c482981e6aefa068b0c0429ba1e46894c39d7e7f27d114651
01c9d7545c3bfe8594ebf5aef182f5d4930db0555708057785
01f4d909c587684862ea9e27598f5d5c7aa4939a9008ea18c4
6fb304a32b676bc3ec39575e73752ad71255f7615a94ed93f78e6d367281ee41
7494a477258946d781cb53c9b37622248e0ba84a48c577c9df
01f4d909c587684862ea9e27598f5d5c889a935648f5f7061f
ea16ecf9ad13756f9bd8ad3fcff2a9e06778647d763f88e679dde519e7155cd6
ea16ecf9ad13756f9bd8ad3fcff2a9e06778647d763f88e679dde519e7155cd6
d0688b6632962acf7905ede7e4f9bd7b2d557e3b828a855208
c0119ab62e5c7a3d932042648291f7cd97c30c9b42c9fa1779
d0f72742cc0415a74e201fcc649f90cf9506eac14e24fd96a9
d0688b6632962acf7905ede7e4f9bd7b5e4921830c30ae8223
d6cd01243405e8741e4010698ab2943526f741cfdb2696b5a6d4e7c14479eccf
2592fb4b19fd100c691598c4bdb82188b6e9d6a6b308d0d627
d0688b6632962acf7905ede7e4f9bd7bf375251be38e1d1e08
d9ae0f940e7c40dcb3a620a5e2a1341819632124af5014bf2f
ab1e8ddd6be53040fcfdf07578704ed9831c4e962eddd36899fc3819b51d6ade
37fed7d50a56f42fa26805a69c38b12b519e59116702a9f0d15a437791600b3a
ea16ecf9ad13756f9bd8ad3fcff2a9e06778647d763f88e679dde519e7155cd6
The encrypted result you have from Coldfusion is not present in the above output.
So, using the AES ciphers available in Node, the encrypted result is always different from your encrypted result from Coldfusion. If the encrypted result is always different, you cannot decrypt it to the same value.
The Coldfusion Encryption Docs are not very helpful at describing exactly which algorithm is used when simply specifying "AES". I would strongly recommend specifying a precise algorithm to use, including which key size to use, and choose one that has a corresponding algorithm in Node.