I'm trying to decrypt some message encrypted by FiSH plugin from mirc. The plugin use a blowfish encryption as mode ECB based on a key but first it encrypted the messages as code64 and then with blowfish ECB. The problem is when I try decrypt the message with blowfish after decode it from base64 get the same error always. "Data must be aligned to block boundary in ECB mode"
The des encrypted message is: "Probando un mensaje cifrado"
from Crypto.Cipher import Blowfish
from Crypto.Util.Padding import pad, unpad
from os import urandom
import base64
key = b"passw0rd"
text =b"+OK Tnkrh0sIoWb1oS1FT.RQop/.JPXNc.lclFO/gueZ4/ZwN1H0"
decode64 = base64.b64decode(text)
decrypt = Blowfish.new(key,Blowfish.MODE_ECB)
msg = decrypt.decrypt(decode64)
print(msg)
site-packages\Crypto\Cipher\_mode_ecb.py", line 163, in decrypt
raise ValueError("Data must be aligned to block boundary in ECB mode")
ValueError: Data must be aligned to block boundary in ECB mode
WITH NON-STANDARD TABLE:
custom_a = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
standard_a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
DECODE_TRANS = str.maketrans(custom_a,standard_a)
key = b"passw0rd"
text ="Tnkrh0sIoWb1oS1FT.RQop/.JPXNc.lclFO/gueZ4/ZwN1H0"
decode64 = base64.b64decode(text.translate(DECODE_TRANS))
decrypt = Blowfish.new(key,Blowfish.MODE_ECB)
msg = decrypt.decrypt(decode64)
print(msg)
There are two obvious problems I see here. There may be more, but these will need to be dealt with first.
You need to remove the prefix "+OK " from the message before decrypting it. It's not part of the encrypted data.
The data transmitted by FiSH is not standard Base64. It uses a nonstandard encoding table which you'll have to account for.
Related
So I am using pycryptodome to encrypt a message using a secret key with AES. I want to then, as a test, decrypt the encrypted message using AES with the same secret key. I have done that here, but the result of the decrypted message is not the same as the encrypted message. Maybe I am misunderstanding how AES works, but I would assume that I should be able to decrypt a message encrypted with AES if I have the same secret key, but it would appear that I'm wrong. How do I make this work properly?
finalCipher = AES.new(sKey, AES.MODE_CFB)
message = input()
#Encrypt the message using the cipher
enMessage = message.encode('utf-8')
encMessage = finalCipher.encrypt(enMessage)
print(encMessage)
#Create a new cipher to decrypt the encrypted message, using the same key
otherCipher = AES.new(sKey, AES.MODE_CFB)
print(otherCipher.decrypt(encMessage))
I realized that I need more than just the original secret key to create a cipher that can decrypt messages encrypted using the original cipher. The original cipher I created has an attribute "iv" that I need to use in the constructor of the new cipher in order to be able to use it to decrypt properly, by doing this instead:
finalCipher = AES.new(sKey, AES.MODE_CFB)
message = input()
#Encrypt the message using the cipher
enMessage = message.encode('utf-8')
encMessage = finalCipher.encrypt(enMessage)
print(encMessage)
#Create a new cipher to decrypt the encrypted message, using the same key
otherCipher = AES.new(sKey, AES.MODE_CFB, finalCipher.iv)
print(otherCipher.decrypt(encMessage))
i am relatively new to pyhton and the cryptography module, so i'm trying to learn the basics of encrypting and decrypting.
It all works fine when i encrypt a file and decrypt it on the same program, but if i try to just run a decrypt code on a pre-encrypted file (i used the same key, of course) i get an InvalidSignature error, followed by an InvalidToken.
Now, i assumed that for some reason the key didn't match, but they are indeed the same.
Then i thought that for some reason i was passing a string instead of a byte to the functions, or that there are some sort of conversion errors that might alter the encrypted message. But the encrypt-decrypt code works, so i can't figure why the decrypt-only should face errors.
At last, i had a look at the source code for the decrypt function, and tried to figure out if the time stamp had something to do with the error i get, but i couldn't get anything relevant since i'm not too experienced.
This is the encrypt-decrypt code: given a password by the user it encrypts and prints a file, that can decrypt right away.
import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
print("Insert password: ")
password_str = input()
password = password_str.encode()
salt = os.urandom(16)
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt,
iterations=100000, backend=default_backend())
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
message = "A really secret message. Not for prying eyes.".encode()
token = f.encrypt(message)
file = open("text_encrypted.txt", "wb")
file.write(token)
file.close()
file = open("text_encrypted.txt", "rb")
data = file.read()
file.close()
token = f.decrypt(data)
file = open("text_decrypted.txt", "wb")
file.write(token)
file.close()
Now, that works just fine and i get the two files that contain the encrypted and decrypted message.
If i delete the:
message = "A really secret message. Not for prying eyes.".encode()
token = f.encrypt(message)
file = open("text_encrypted.txt", "wb")
file.write(token)
file.close()
part, i should be left with just a decryption code, that should work on a previously generated encrypted file, that should be decrypted by the same password.
I'm obviously missing something maybe trivial, since it raises both invalid signature and invalid token.
Thanks for your help
The encryption key you’re using is a result of a PBKDF2. In order for PBKDF2 to return the same encryption key it must get the exact same parameters. That includes salt, which in your example is generated randomly every time.
You need to store the generated salt together with the encrypted file in order to be able to decrypt it later.
I'm using python to encrypt and decrypt files. When file encrypted, then try to decrypt like this:
from Crypto.Cipher import AES
from Crypto import Random
def decrypt(in_file, out_file, pwd, key_len=32):
bs = AES.block_size
salt = in_file.read(bs)[len('Salted__'):]
key, iv = derive_keyiv(pwd, salt, key_len, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
next_chunk = ''
finished = False
try:
while not finished:
chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024*bs))
if len(next_chunk) == 0:
padding_len = ord(chunk[-1])
chunk = chunk[:-padding_len]
finished = True
out_file.write(chunk)
return True, None
except Exception as e:
return False, e
But if the password input error, the decrypt still decrypt in_file and write to out_file and no exception throw.
How to check the password error during decrypt?
AES by itself cannot check if the key is "correct". It is simply a pure function that transforms some bytes to some other bytes.
To achieve what you want, you need to implement it yourself. One way to do is to add a fixed header (like 16 bytes of zero) to the plaintext before encryption. Upon decryption, check and discard the said header if it matches or raise an error if the header mismatches.
A side note: you are doing encryption without any authentication, which is probably insecure.
Edit
First of all, you should add authentication. Encryption without authentication easily leads to many security flaws, many not obvious to the untrained. Especially since you are using AES in CBC mode, you may open yourself to padding oracle attacks without authentication.
When you do authenticated encryption the right way (encrypt-then-mac), you will get an authentication error if the user input the wrong password. If you want to further distinguish a wrong password from tampered data, you have to devise your own method, like prepending a ciphertext of magic number.
To connect a server, I've found that, using PHP, I need to use openssl_seal(). That's OK, but I want to use Python. I'm not able to convert openssl_seal() in an equivalent function.
Can you help me?
This is what openssl_seal() does:
Description
int openssl_seal ( string $data , string &$sealed_data , array &$env_keys ,
array $pub_key_ids )
openssl_seal() seals (encrypts) data by using RC4 with a randomly generated
secret key. The key is encrypted with each of the public keys associated
with the identifiers in pub_key_ids and each encrypted key is returned in
env_keys. This means that one can send sealed data to multiple recipients
(provided one has obtained their public keys). Each recipient must receive
both the sealed data and the envelope key that was encrypted with the
recipient's public key.
this blogpost has a very detailed description of what's going on inside openssl_seal(). It also has an implementation in java.
From this, I would think it should be relatively straightforward ("the proof left as an exercise to the reader" kind of straightforward) to do an equivalent implementation in python using pyopenssl, which includes RC4, or the newer, but for these purposes more focused tlslite.
What openssl_seal does is:
Extract the public_key from the certificate
Generate a 128 bits (16 bytes) long random_key (this will be used to encrypt the message using a symmetrical algorithm, since it's faster)
Encrypt the random_key using PKCS #1
Encrypt the message using ARC4 a secure cipher method and the random_key (Note that ARC4 is no longer considered secure and that PHP strongly recommends to explicitly specify a secure cipher method using the cipher_algo param)
Output the encrypted_random_key and the encrypted_message
The receiving party can then decrypt the encrypted_random_key using their private_key and then decrypt the encrypted_message using the random_key.
Since there's no way of doing this in Python via the standard library, I'm just gonna' throw out the 3 approaches that I've tried out:
# pyca/cryptography (cryptography.io) version
# pip install cryptography
import os
import cryptography
from cryptography import x509
message = 'Super secret secret message'
message = message.encode('utf-8')
certificate_data = open('/path/to/certificate.cer', 'r').read()
certificate_data = certificate_data.encode('utf-8')
certificate = cryptography.x509.load_pem_x509_certificate(data=certificate_data, backend=cryptography.hazmat.backends.default_backend())
public_key = certificate.public_key()
random_key = os.urandom(16)
encrypted_random_key = public_key.encrypt(plaintext=random_key, padding=cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15())
print(encrypted_random_key)
algorithm = cryptography.hazmat.primitives.ciphers.algorithms.AES(random_key)
cipher = cryptography.hazmat.primitives.ciphers.Cipher(algorithm=algorithm, mode=None, backend=cryptography.hazmat.backends.default_backend())
encryptor = cipher.encryptor()
encrypted_message = encryptor.update(message)
print(encrypted_message)
.
# M2Crypto version
# pip install pip install git+https://gitlab.com/m2crypto/m2crypto#python3
import M2Crypto
message = 'Super secret secret message'
message = message.encode('utf-8')
certificate = M2Crypto.X509.load_cert('/path/to/certificate.cer')
public_key = certificate.get_pubkey()
rsa_pub = public_key.get_rsa()
random_key = M2Crypto.Rand.rand_bytes(16)
encrypted_random_key = rsa_pub.public_encrypt(random_key, M2Crypto.RSA.pkcs1_padding)
print(encrypted_random_key)
cipher = M2Crypto.EVP.Cipher(alg='aes_128_cbc', key=random_key, iv=b'', op=M2Crypto.encrypt)
encrypted_message = cipher.update(message)
encrypted_message += cipher.final()
print(encrypted_message)
.
# PyCrypto version
# Update: PyCrypto 2.x is unmaintained, obsolete, and contains security vulnerabilities!!!
# pip install pycrypto
# Please bear in mind that PyCrypto cannot handle x509 certificates.
# You will have to extract the public_key to a pem file:
# openssl x509 -inform pem -in certificate.cer -pubkey -noout > public_key.pem
from Crypto import Random
from Crypto.Cipher import ARC4
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
message = 'Super secret secret message'
message = message.encode('utf-8')
public_key_data = open('/path/to/public_key.pem', 'r').read()
public_key = RSA.importKey(public_key_data)
random_key = Random.new().read(16)
cipher = PKCS1_v1_5.new(public_key)
encrypted_random_key = cipher.encrypt(random_key)
print(encrypted_random_key)
cipher = ARC4.new(random_key)
encrypted_message = cipher.encrypt(message)
print(encrypted_message)
You can check out my post at => http://helpfulsheep.com/2017-09-01-openssl-seal-in-python/
Since I can't post comments yet, I need to add to Gabi Nagy's answer, that while their answer describes a correct algorithm, it is not the same as using openssl_seal() function.
OpenSSL doesn't let unencrypted key to get outside of OpenSSL structures. It generates key somewhere inside and keeps it there, giving you only encrypted key. The crucial difference is that when OpenSSL cleans it's structures, it should dispose of unencrypted key in a safe way.
A text s has been encrypted with:
s2 = iv + Crypto.Cipher.AES.new(Crypto.Hash.SHA256.new(pwd).digest(),
Crypto.Cipher.AES.MODE_CFB,
iv).encrypt(s.encode())
Then, later, a user inputs the password pwd2 and we decrypt it with:
iv, cipher = s2[:Crypto.Cipher.AES.block_size], s2[Crypto.Cipher.AES.block_size:]
s3 = Crypto.Cipher.AES.new(Crypto.Hash.SHA256.new(pwd2).digest(),
Crypto.Cipher.AES.MODE_CFB,
iv).decrypt(cipher)
Problem: the last line works even if the entered password pw2 is wrong. Of course the decrypted text will be random chars, but no error is triggered.
Question: how to make Crypto.Cipher.AES.new(...).decrypt(cipher) fail if the password pw2 is incorrect? Or at least how to detect a wrong password?
Here is a linked question: Making AES decryption fail if invalid password
and here a discussion about the cryptographic part (less programming) of the question: AES, is this method to say “The password you entered is wrong” secure?
.
AES provides confidentiality but not integrity out of the box - to get integrity too, you have a few options. The easiest and arguably least prone to "shooting yourself in the foot" is to just use AES-GCM - see this Python example or this one.
You could also use an HMAC, but this generally requires managing two distinct keys and has a few more moving parts. I would recommend the first option if it is available to you.
A side note, SHA-256 isn't a very good KDF to use when converting a user created password to an encryption key. Popular password hashing algorithms are better at this - have a look at Argon2, bcrypt or PBKDF2.
Edit: The reason SHA-256 is a bad KDF is the same reason it makes a bad password hash function - it's just too fast. A user created password of, say, 128 bits will usually contain far less entropy than a random sequence of 128 bits - people like to pick words, meaningful sequences etc. Hashing this once with SHA-256 doesn't really alleviate this issue. But hashing it with a construct like Argon2 that is designed to be slow makes a brute-force attack far less viable.
The best way is to use authenticated encryption, and a modern memory-hard entropy-stretching key derivation function such a scrypt to turn the password into a key. The cipher's nounce can be used as salt for the key derivation. With PyCryptodome that could be:
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import scrypt
# initialize an AES-128-GCM cipher from password (derived using scrypt) and nonce
def cipherAES(pwd, nonce):
# note: the p parameter should allow use of several processors, but did not for me
# note: changing 16 to 24 or 32 should select AES-192 or AES-256 (not tested)
return AES.new(scrypt(pwd, nonce, 16, N=2**21, r=8, p=1), AES.MODE_GCM, nonce=nonce)
# encryption
nonce = get_random_bytes(16)
print("deriving key from password and nonce, then encrypting..")
ciphertext, tag = cipherAES(b'pwdHklot2',nonce).encrypt_and_digest(b'bonjour')
print("done")
# decryption of nonce, ciphertext, tag
print("deriving key from password and nonce, then decrypting..")
try:
plaintext = cipherAES(b'pwdHklot2', nonce).decrypt_and_verify(ciphertext, tag)
print("The message was: " + plaintext.decode())
except ValueError:
print("Wrong password or altered nonce, ciphertext, tag")
print("done")
Note: Code is here to illustrate the principle. In particular, the scrypt parameters should not be fixed, but rather be included in a header before nonce, ciphertext, and tag; and that must be somewhat grouped for sending, and parsed for decryption.
Caveat: nothing in this post should be construed as an endorsement of PyCryptodome's security.
Addition (per request):
We need scrypt or some other form of entropy stretching only because we use a password. We could use a random 128-bit key directly.
PBKDF2-HMAC-SHAn with 100000 iterations (as in the OP's second code fragment there) is only barely passable to resist Hashcat with a few GPUs. It would would be almost negligible compared to other hurdles for an ASIC-assisted attack: a state of the art Bitcoin mining ASIC does more than 2*1010 SHA-256 per Joule, 1 kWh of electricity costing less than $0.15 is 36*105 J. Crunching these numbers, testing the (62(8+1)-1)/(62-1) = 221919451578091 passwords of up to 8 characters restricted to letters and digits cost less than $47 for energy dedicated to the hashing part.
scrypt is much more secure for equal time spent by legitimate users because it requires a lot of memory and accesses thereof, slowing down the attacker, and most importantly making the investment cost for massively parallel attack skyrocket.
Doesn't use the Crypto package, but this should suit your needs:
import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
def derive_password(password: bytes, salt: bytes):
"""
Adjust the N parameter depending on how long you want the derivation to take.
The scrypt paper suggests a minimum value of n=2**14 for interactive logins (t < 100ms),
or n=2**20 for more sensitive files (t < 5s).
"""
kdf = Scrypt(salt=salt, length=32, n=2**16, r=8, p=1, backend=default_backend())
key = kdf.derive(password)
return base64.urlsafe_b64encode(key)
salt = os.urandom(16)
password = b'legorooj'
bad_password = b'legorooj2'
# Derive the password
key = derive_password(password, salt)
key2 = derive_password(bad_password, salt) # Shouldn't re-use salt but this is only for example purposes
# Create the Fernet Object
f = Fernet(key)
msg = b'This is a test message'
ciphertext = f.encrypt(msg)
print(msg, flush=True) # Flushing pushes it strait to stdout, so the error that will come
print(ciphertext, flush=True)
# Fernet can only be used once, so we need to reinitialize
f = Fernet(key)
plaintext = f.decrypt(ciphertext)
print(plaintext, flush=True)
# Bad Key
f = Fernet(key2)
f.decrypt(ciphertext)
"""
This will raise InvalidToken and InvalidSignature, which means it wasn't decrypted properly.
"""
See my comment for links to the documentation.
For future reference, here is a working solution following the AES GCM mode (recommended by #LukeJoshuaPark in his answer):
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
# Encryption
data = b"secret"
key = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(data)
nonce = cipher.nonce
# Decryption
key2 = get_random_bytes(16) # wrong key
#key2 = key # correct key
try:
cipher = AES.new(key2, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
print("The message was: " + plaintext.decode())
except ValueError:
print("Wrong key")
It does fail with an exception when the password is wrong indeed, as desired.
The following code uses a real password derivation function:
import Crypto.Random, Crypto.Protocol.KDF, Crypto.Cipher.AES
def cipherAES(pwd, nonce):
return Crypto.Cipher.AES.new(Crypto.Protocol.KDF.PBKDF2(pwd, nonce, count=100000), Crypto.Cipher.AES.MODE_GCM, nonce=nonce)
# encryption
nonce = Crypto.Random.new().read(16)
cipher = cipherAES(b'pwd1', nonce)
ciphertext, tag = cipher.encrypt_and_digest(b'bonjour')
# decryption
try:
cipher = cipherAES(b'pwd1', nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
print("The message was: " + plaintext.decode())
except ValueError:
print("Wrong password")
#fgrieu's answer is probably better because it uses scrypt as KDF.