so I have been trying to build an AES encryption program based off of the github pycrypto guide link to github however when I go to decode an error shows up:
Traceback (most recent call last):
File "/home/pi/Desktop/aes/newAES.py", line 24, in <module>
print(decrypt(key,msg,iv))
File "/home/pi/Desktop/aes/newAES.py", line 13, in decrypt
cipher = AES.new(key,AES.MODE_CFB)
File "/usr/lib/python3/dist-packages/Crypto/Cipher/AES.py", line 94, in new
return AESCipher(key, *args, **kwargs)
File "/usr/lib/python3/dist-packages/Crypto/Cipher/AES.py", line 59, in __init__
blockalgo.BlockAlgo.__init__(self, _AES, key, *args, **kwargs)
File "/usr/lib/python3/dist-packages/Crypto/Cipher/blockalgo.py", line 141, in __init__
self._cipher = factory.new(key, *args, **kwargs)
ValueError: IV must be 16 bytes long
my code is:
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
def encrypt(key,msg):
if key == 0:
key=get_random_bytes(16)
print("key: "+key)
iv = get_random_bytes(16)
print('iv: '+str(iv))
cipher = AES.new(key,AES.MODE_CFB,iv)
ciphertext= cipher.decrypt(msg)
return("your encrypted message: "+str(ciphertext))
def decrypt(key,ciphertext,iv):
cipher = AES.new(key,AES.MODE_CFB)
msg = cipher.decrypt(ciphertext)
ed = input('(e)ncrypt or (d)ecrypt: ')
if ed=='e':
key = input('16 digit key: ')
msg = input('message: ')
print(encrypt(key,msg))
elif ed =='d':
key = input('16 digit key: ')
iv = bytes(input('iv: '),'utf-8')
msg = bytes(input('encrypted message:'),'utf-8')
print(decrypt(key,msg,iv))
I would appreciate any help offered on solving this issue, hopefully it isn't some stupid error
The problem with iv is that it consists of random bytes, but it is being read into your program as a string. Calling bytes on that string does not do what you expect.
>>> iv = b'\xba\x0eyO8\x17\xcf\x97=\xf2&l34#('
>>> siv = str(iv)
>>> siv
"b'\\xba\\x0eyO8\\x17\\xcf\\x97=\\xf2&l34#('" # Note 'b' is part of the string
>>> biv = bytes(siv, 'utf-8')
>>> biv
b"b'\\xba\\x0eyO8\\x17\\xcf\\x97=\\xf2&l34#('" # Now there are two 'b's!
You can resolve this by using ast.literal_eval:
>>> ast.literal_eval(siv)
b'\xba\x0eyO8\x17\xcf\x97=\xf2&l34#('
Here's a working version of your code - I removed the need to copy/paste iv, but the same observations about inputting bytes applies to the ciphertext.
import ast
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
def encrypt(key, msg):
iv = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CFB, iv)
ciphertext = cipher.encrypt(msg) # Use the right method here
return iv + ciphertext
def decrypt(key, ciphertext):
iv = ciphertext[:16]
ciphertext = ciphertext[16:]
cipher = AES.new(key, AES.MODE_CFB, iv)
msg = cipher.decrypt(ciphertext)
return msg.decode("utf-8")
if __name__ == "__main__":
ed = input("(e)ncrypt or (d)ecrypt: ")
if ed == "e":
key = input("16 digit key: ")
msg = input("message: ")
print("Encrypted message: ", encrypt(key, msg))
elif ed == "d":
key = input("16 digit key: ")
smsg = input("encrypted message: ")
msg = ast.literal_eval(smsg)
print("Decrypted message: ", decrypt(key, msg))
The code in action:
(e)ncrypt or (d)ecrypt: e
16 digit key: abcdabcdabcdabcd
message: Spam, spam, spam
Encrypted message: b'\xa4?\xa9RI>\x1f\xb5*\xb2,NWN\x0c\xfd"yB|\x1f\x82\x96\xd5\xb4\xd4\x1d&\x8bM\xdb\x07'
(e)ncrypt or (d)ecrypt: d
16 digit key: abcdabcdabcdabcd
encrypted message: b'\xa4?\xa9RI>\x1f\xb5*\xb2,NWN\x0c\xfd"yB|\x1f\x82\x96\xd5\xb4\xd4\x1d&\x8bM\xdb\x07'
Decrypted message: Spam, spam, spam
Related
Can anyone help me correct this error.I am using python 3.9 and pycryptodome to make a pyqt5 gui project. Here is the code:
from Crypto.Cipher import AES
import scrypt
import os
class AESCipher:
def __init__(self, password):
self.password = str.encode(password)
def encrypt_AES_GCM(self, msg):
kdfSalt = os.urandom(16)
secretKey = scrypt.hash(self.password, kdfSalt, N=16384, r=8, p=1, buflen=32)
aesCipher = AES.new(secretKey, AES.MODE_GCM)
ciphertext, authTag = aesCipher.encrypt_and_digest(msg)
# return kdfSalt, ciphertext, aesCipher.nonce, authTag
return ciphertext
def decrypt_AES_GCM(self, cipherText):
kdfSalt, ciphertext, nonce, authTag = cipherText, cipherText, cipherText, cipherText
secretKey = scrypt.hash(self.password, kdfSalt, N=16384, r=8, p=1, buflen=32)
aesCipher = AES.new(secretKey, AES.MODE_GCM, nonce)
plaintext = aesCipher.decrypt_and_verify(ciphertext, authTag, output=None)
return plaintext
i am calling functions from this class as follows:
def decodeImage(self):
cipher = self.cipher()
if cipher == None:
return
obj = LSB(self.image)
c = obj.extract()
cipherText = base64.decodebytes(c.encode("utf-8"))
print('Cipher text: ', cipherText)
msg = cipher.decrypt_AES_GCM(cipherText)
# show decoded secret message to message input box
self.ui.messageOutput.clear()
self.ui.messageOutput.insertPlainText(msg.decode('utf8'))
def encodeImage(self):
message = self.ui.messageInput.toPlainText()
# Make msg length a multiple of 16 by adding white space at the end
if len(message) % 16 != 0:
message += (" " * (16 - len(message) % 16))
cipher = self.encode_Cipher()
if cipher is None:
return
cipherText = cipher.encrypt_AES_GCM(message.encode("utf-8"))
print('Cipher text: ', cipherText)
print('Message: ', message)
self.ui.messageInput.insertPlainText(cipherText.hex())
obj = LSB(self.eimage)
obj.embed(base64.encodebytes(cipherText).decode("utf-8"))
self.ui.messageInput.clear()
self.eimage = obj.image
# preview image after cipher text is embedded
self.encode_updateImage()
QMessageBox.about(self, "Info", "Encoded")
I am getting the error when i try to decode which in this case involves decryption.
Error is as follows:
Traceback (most recent call last):
File "C:\Users\ntc\Desktop\HIT 400\Capstone Desing\Capstone Design HIT 400\FinalApp.py", line 455, in decodeImage
msg = cipher.decrypt_AES_GCM(cipherText)
File "C:\Users\ntc\Desktop\HIT 400\Capstone Desing\Capstone Design HIT 400\aesSteg.py", line 22, in decrypt_AES_GCM
plaintext = aesCipher.decrypt_and_verify(ciphertext, authTag, output=None)
File "C:\Users\ntc\AppData\Roaming\Python\Python39\site-packages\Crypto\Cipher\_mode_gcm.py", line 567, in decrypt_and_verify
self.verify(received_mac_tag)
File "C:\Users\ntc\AppData\Roaming\Python\Python39\site-packages\Crypto\Cipher\_mode_gcm.py", line 508, in verify
raise ValueError("MAC check failed")
ValueError: MAC check failed
I'm trying to implement a simple encryption-decryption script with pycryptodome and AES-CBC, that is:
no iv,
no padding, therefore the string to encrypt is stripped do 16 characters
key is not random and is a fixed string.
However I fail by decrypting the message.
Here is the script:
from Crypto.Cipher import AES
from Crypto import Random
#import itertools
plain_text = "This is the text to encrypt"
key = "0361231230000000"
def encrypt(plain_text, key):
key = bytes(key, "UTF-8")
cipher = AES.new(key, AES.MODE_CBC)
print("Encryption Cipher: ", cipher)
# When there is no padding, the block size must equal the cipher length
# Padding is necessary for texts with length different from 16 bytes
cbytes = cipher.encrypt(bytes(plain_text[:16], "UTF-8"))
return cbytes
def decrypt(enc_text):
k = bytes(key, "UTF-8")
cipher = AES.new(k, AES.MODE_CBC)
print("Decryption Cipher: ")
return cipher.decrypt(enc_text).decode("UTF-8")
if __name__ == "__main__":
enc_text = encrypt(plain_text, key)
print(enc_text)
dec_text = decrypt(enc_text)
print(dec_text)
The error message is the following one:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
If in the decryption process I replace "UTF-8" by "Latin-1", the output does not match:
Decryption Cipher:
Ï(¨e¨^î
What did I miss ?
Unlike ECB, CBC does require an initialization vector. As the documentation says:
If [iv argument] not provided, a random byte string is generated (you must then read its value with the iv attribute).
To apply that to your code:
from Crypto.Cipher import AES
from Crypto import Random
plain_text = "This is the text to encrypt"
key = b"0361231230000000"
def encrypt(plain_text, key):
cipher = AES.new(key, AES.MODE_CBC)
b = plain_text.encode("UTF-8")
return cipher.iv, cipher.encrypt(b)
def decrypt(iv, enc_text):
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
return cipher.decrypt(enc_text).decode("UTF-8")
if __name__ == "__main__":
iv, enc_text = encrypt(plain_text[:16], key)
dec_text = decrypt(iv, enc_text)
print(dec_text)
I have a function that encodes an encrypted string (using hazmat Cipher) in base64 representation, but when I try to decrypt it later using a different function I am getting an error:
web_1 | File "/code/cart/models.py", line 58, in decrypt_hash_id
web_1 | int_id = int(padded_id.decode('utf-8').lstrip(' '))
web_1 | UnicodeDecodeError: 'utf-8' codec can't decode byte 0x86 in position 0: invalid start byte
My encryption function is:
from django.conf import settings
from base64 import urlsafe_b64encode, urlsafe_b64decode
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
def url_safe_encrypted_id(self):
backend = default_backend()
key = settings.URL_ENCRYPTION_KEY
iv = settings.URL_ENCRYPTION_IV
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
encryptor = cipher.encryptor()
ct = encryptor.update(str(self.id).rjust(16).encode()) + encryptor.finalize()
return urlsafe_b64encode(ct)
However when I try to decrypt this base64 string I am sometimes getting an error. My method for decrypting is:
def decrypt_hash_id(hash):
# Decode order_id
backend = default_backend()
key = settings.URL_ENCRYPTION_KEY
iv = settings.URL_ENCRYPTION_IV
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
decryptor = cipher.decryptor()
padded_id = decryptor.update(urlsafe_b64decode((hash + '===').encode())) + decryptor.finalize()
int_id = int(padded_id.decode().lstrip(' '))
return int_id
Can anyone help to see where the problem might lie? Probably 99% of the time it's working correctly, but occasionally I will get the error shown above.
I got the problem you have. It is in the line:
padded_id = decryptor.update(urlsafe_b64decode((hash + '===').encode())) + decryptor.finalize()
just modify it to:
padded_id = decryptor.update(urlsafe_b64decode(hash.decode())) + decryptor.finalize()
Here is a full example code, but with some modification to run:
from base64 import urlsafe_b64encode, urlsafe_b64decode
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
class settings:
URL_ENCRYPTION_KEY=b'1234567890123456'
URL_ENCRYPTION_IV=b'1234567890123456'
def url_safe_encrypted_id(text):
backend = default_backend()
key = settings.URL_ENCRYPTION_KEY
iv = settings.URL_ENCRYPTION_IV
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
encryptor = cipher.encryptor()
ct = encryptor.update(text.rjust(16).encode()) + encryptor.finalize()
return urlsafe_b64encode(ct)
def decrypt_hash_id(hash):
# Decode order_id
backend = default_backend()
key = settings.URL_ENCRYPTION_KEY
iv = settings.URL_ENCRYPTION_IV
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
decryptor = cipher.decryptor()
padded_id = decryptor.update(urlsafe_b64decode(hash.decode())) + decryptor.finalize()
int_id = int(padded_id.decode().lstrip(' '))
return int_id
text='10'
enc=url_safe_encrypted_id(text)
dec=decrypt_hash_id(enc)
print(text)
print(enc)
print(dec)
# output:
# 10
# b'l5DfiYydiPUCzGK7eLXOFQ=='
# 10
Good Luck
You can try this:
import base64
def encrypt():
string = "Hello world this is a sample string"
str_bytes = sample_string.encode("ascii")
base64_bytes = base64.b64encode(str_bytes)
base64_string = base64_bytes.decode("ascii")
print(f"Encoded string: {base64_string}")
def decrypt():
base64_string =" R2Vla3NGb3JHZWVrcyBpcyB0aGUgYmVzdA =="
base64_bytes = base64_string.encode("ascii")
sample_string_bytes = base64.b64decode(base64_bytes)
sample_string = sample_string_bytes.decode("ascii")
print(f"Decoded string: {sample_string}")
I am using the following code to encrypt/decrypt only passwords. It works perfectly fine except special characters. As an example Pa$$w0rd returns Pa1705w0rd . Any idea how to fix it ? By the way, I have also tried PKCS1_v1_5, but same result !
def _encrypt(self, message):
public_key = RSA.importKey(self._get_public_key())
cipher = PKCS1_OAEP.new(public_key)
encrypted_message = cipher.encrypt(message)
print(base64.b64encode(encrypted_message))
def _decrypt(self, encoded_encrypted_message):
encrypted_message = base64.b64decode(encoded_encrypted_message)
private_key = RSA.importKey(self._get_private_key())
cipher = PKCS1_OAEP.new(private_key)
print(cipher.decrypt(encrypted_message))
It's hard for me to give a precise answer on what's exactly wrong, because I can't see entire program required to execute it. In particular I don't know if you pass message in as a bytes, and if so using what encoding.
However, here is working code:
import base64
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
def get_rsa_private_key():
return RSA.generate(2048)
def encrypt(plaintext, public_key):
cipher = PKCS1_OAEP.new(public_key)
ciphertext = cipher.encrypt(bytes(plaintext, encoding="UTF-8"))
return base64.b64encode(ciphertext)
def decrypt(base64_encoded_ciphertext, private_key):
ciphertext = base64.b64decode(base64_encoded_ciphertext)
cipher = PKCS1_OAEP.new(private_key)
return cipher.decrypt(ciphertext)
key = get_rsa_private_key()
ciphertext = encrypt("Pa$$w0rd", key.publickey())
print("Base64-encoded ciphertext: %s" % str(ciphertext, encoding="UTF-8"))
decrypted_plaintext = decrypt(ciphertext, key)
print("Decrypted plaintext: %s" % str(decrypted_plaintext, encoding="UTF-8"))
Output:
Base64-encoded ciphertext: hy7dhLj8Hy1n1cjcn20x+dWG/bOjXv3zFEd1T/cm4oJgDHFTviD8uexe2pG+lMmoP6qP+1uRwjgfMnGkpLhRwk1w5eN9bjphsm+ekC8B+qjfIG6TLjL0GEcJKTWf/dgNNBSbTWI2bNXREBbxkVWW+11vUfWsP5ni2exhZIMrf29B1z2FAyixdsHQ5KKlvfTGE4LFbrCTNn4qp2tsMTylitdafwYhsSIm5qlwIRU+qTB5bz8nTJHnPyksEIffHXbCJNjUwJJKRihrTZ+vY78XccTY7Bmkw5fmf3KuDRqXR/2LvjWwBtqqrMQRnmArer9Qh3uTmRNvvhUOYsh10172LQ==
Decrypted plaintext: Pa$$w0rd
I have this implementation of a reversible encoding:
# coding=utf-8
from Crypto.Cipher import AES
from Crypto import Random
import uuid
import unittest
import random
key = r'Sixteen byte key' # Keep this real secret
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CFB, iv)
def encode(role, plaintext):
'''Encode the message, prefix with the role specifier'''
msg = iv + cipher.encrypt(plaintext)
msg = msg.encode('hex')
msg = role + '-' + msg
return msg
def decode(msg):
'''Decode message, return role and plaintext'''
role, msg = msg.split('-', 1)
plaintext = cipher.decrypt(msg.decode('hex'))[len(iv):]
return role, plaintext
class TestMe(unittest.TestCase):
def test_whole(self):
ROLES = ['sales', 'vendor', 'designer']
for _ in xrange(100):
role = random.choice(ROLES)
txt = uuid.uuid4().hex
msg = encode(role, txt)
drole, dtxt = decode(msg)
self.assertEqual(role, drole)
self.assertEqual(txt, dtxt)
print 'ok'
if __name__ == '__main__':
unittest.main()
But this is failing, always on the second test round. I am doing something obviously wrong, but I do not know what.
Note
You need to:
pip install pycrypto
To run that code
The code fails with:
» python test.py
ok
F
======================================================================
FAIL: test_whole (__main__.TestMe)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 40, in test_whole
self.assertEqual(txt, dtxt)
AssertionError: 'b2e7894dd6254b259ae06350f199e6a2' != '\xa7\xcd\t\xde~\x15\xce\x9d\xcfU\x8f\xb2\xfa\x08\x98\x1c9ae06350f199e6a2'
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
The error message provides vital clues as to what is going on. As you can see, the first 16 bytes of the decrypted message are different, but the next 16 bytes are the same. This happens when the key is correct, but the IV isn't.
The problem seems to be that pyCrypto doesn't reset the state of the cipher after the encryption/decryption and the IV is some other value.
Either way, you shouldn't be setting the IV once and reusing it multiple times. The IV is there to provide randomization of the ciphertexts so that attackers who observe the ciphertexts cannot determine whether the plaintext that is encrypted has repeated.
Moving AES object creation into the function, solves this issue:
key = r'Sixteen byte key' # Keep this real secret
def encode(role, plaintext):
'''Encode the message, prefix with the role specifier'''
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CFB, iv)
msg = iv + cipher.encrypt(plaintext)
msg = msg.encode('hex')
msg = role + '-' + msg
return msg
def decode(msg):
'''Decode message, return role and plaintext'''
role, msg = msg.split('-', 1)
msg = msg.decode('hex')
iv = msg[:AES.block_size]
cipher = AES.new(key, AES.MODE_CFB, iv)
plaintext = cipher.decrypt(msg[AES.block_size:])
return role, plaintext
You should check out the 2.7-alpha release of pyCrypto which includes authenticated modes such as GCM, EAX, SIV. Ciphertext authentication is important, because it might be possible to use a padding oracle attack in your system to decrypt any ciphertext.