I am working on an encryption program with Pycryptodome in Python 3. I am trying to encrypt a (byte) string and then decrypt it and verify the MAC tag. When I get to verify it, an error is thrown.
This is the code:
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
aes_key = get_random_bytes(24)
aes_cipher = AES.new(aes_key, AES.MODE_GCM)
encrypted, MACtag = aes_cipher.encrypt_and_digest(b"A random thirty two byte string.")
# Imagine this is happening somewhere else
new_aes_cipher = AES.new(aes_key, AES.MODE_GCM, nonce=aes_cipher.nonce)
new_aes_cipher.verify(MACtag)
decrypted = new_aes_cipher.decrypt(encrypted)
And this is the error:
Traceback (most recent call last):
File "aespractice.py", line 10, in <module>
new_aes_cipher.verify(tag)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-
packages/Crypto/Cipher/_mode_gcm.py", line 441, in verify
raise ValueError("MAC check failed")
ValueError: MAC check failed
I've looked at the documentation, and I it looks to me like everything is all right. Why do you think the program is acting this way? Any help would be appreciated.
If you look at the state diagram for authenticated modes:
You see that verify() should be called at the very end, after any decrypt() has taken place.
So, either you invert the calls or you replace them with a combined decrypt_and_verify().
Related
Can someone help me with this problem:
I am encrypting a JSON in NiFi with AES_GCM algorithm and using a KDF PBKDF2. The idea is to decrypt this JSON with a python script using PyCryptodome.
The following code is an attempt to see if the NiFi encrypted message can be decrypted:
import base64
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Cipher import AES
# Key used in NiFi to encrypt the JSON
key = '-JaNcRfUjXn2r5u8'
# Result value after the EncryptContent processor encrypts
# the JSON: {"id":"123", "name":"Ronald"}
value = 'nTJuV9l9uQJrWv2HXD37PE5pRmlTQUxUg7ir0oDxuxSnmuqZpbUfVk5pRmlJVn1yT10SdXKjobe9o/QHybJqwhPgCifGz2yiY9JICGehICb/zEsYUivERcMKhg=='
decodeValue = base64.b64decode(value)
salt = decodeValue[:16]
IV = decodeValue[24:40]
dataToDecrypt = decodeValue[46:-16]
tag = decodeValue[-16:]
mainKey = PBKDF2(key, salt)
cipher = AES.new(mainKey, AES.MODE_GCM, IV)
cipher.update(salt)
decryptedData = cipher.decrypt_and_verify(dataToDecrypt, tag)
print(decryptedData)
I understand that in NiFi with NIFI_PBKDF2_AES_GCM_128 encryption the cipher text output consists of the salt (16-byte length), followed by the salt delimiter, UTF-8 string “NiFiSALT” (0x4E 69 46 69 53 41 4C 54) and then the IV, followed by the IV delimiter, UTF-8 string “NiFiIV” (0x4E 69 46 69 49 56), followed by the cipher text, followed by the autehnticaion tag (16-byte length), but when trying to run the above script using this structure, I get the following error:
Traceback (most recent call last):
File "/home/ronald/anaconda3/envs/Quind/lib/python3.10/code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 4, in <module>
File "/home/ronald/anaconda3/envs/Quind/lib/python3.10/site-packages/Crypto/Cipher/_mode_gcm.py", line 567, in decrypt_and_verify
self.verify(received_mac_tag)
File "/home/ronald/anaconda3/envs/Quind/lib/python3.10/site-packages/Crypto/Cipher/_mode_gcm.py", line 508, in verify
raise ValueError("MAC check failed")
ValueError: MAC check failed
I don't understand why the authentication tag check fails.
Decryption fails for two reasons:
For PBKDF2 neither the iteration count nor the digest are explicitly specified, so the default values 1000 and SHA1 are used. However, the values to be applied are 160000 and SHA512 according to the documentation:
from Crypto.Hash import SHA512
...
mainKey = PBKDF2(key, salt, count=160000, hmac_hash_module=SHA512)
The salt is not authenticated, i.e. the line cipher.update(salt) must be removed.
With these changes, authentication and decryption is successful and decryptedData is b'{"id": "123", "name": "Ronald"}'.
Note that for GCM the length 16 bytes is allowed for the nonce/IV, but the recommended length is 12 bytes. But maybe a change is beyond your control.
Sorry to waste everybody's time because I don't know python. I use jrnla lot to write journal entries, and a while ago I encrypted a file that I know the password to, but don't know how to use this script (provided by the developer) to decrypt. I realized I had to make it executable to use it (genius right) but then got an error when I used it. Any help would be much appreciated.
Traceback (most recent call last):
File "/home/usr/src/jrnl_decrypt", line 16, in
with open(args.filepath, 'rb') as f:
AttributeError: 'Namespace' object has no attribute 'filepath'
Here's the script. I also found an alternative one in older documentation. It's below the larger script.
import argparse
from Crypto.Cipher import AES
import getpass
import hashlib
import sys
parser = argparse.ArgumentParser()
parser.add_argument("~", help="old-journal-encrypted.txt")
args = parser.parse_args()
pwd = getpass.getpass()
key = hashlib.sha256(pwd.encode('utf-8')).digest()
with open(args.filepath, 'rb') as f:
ciphertext = f.read()
crypto = AES.new(key, AES.MODE_CBC, ciphertext[:16])
plain = crypto.decrypt(ciphertext[16:])
plain = plain.strip(plain[-1:])
plain = plain.decode("utf-8")
print(plain)
# Python script to manually decrypt jrnl files
//Alt script
import hashlib, Crypto.Cipher
key = hashlib.sha256(my_password).digest()
with open("old-journal-encrypted.txt") as f:
cipher = f.read()
crypto = AES.new(key, AES.MODE_CBC, iv = cipher[:16])
plain = crypto.decrypt(cipher[16:])
plain = plain.strip(plain[-1])
plain = plain.decode("utf-8")
First of all: you're not wasting anyone's time. None of us known everything from the beginning ^^
In this line parser.add_argument("~", help="old-journal-encrypted.txt") you are adding your argument as ~, so you may use it as args.~ (if it was the case). That's not intuitive, so try changin that line to parser.add_argument("filepath", help="old-journal-encrypted.txt"). After that try to run the script again.
Edit: you're not setting the value with the 'help parameter', it's just a tooltip. You must pass the value when you call the script I.E: python my_prog.py old-journal-encrypted.txt. Make sure that the file is reacheable as well
Hope this helps you!
P.S: You have an example here in case you still have some doubts (don't hesitate to add a comment as well if that's the case ^^)
So i'm using the JIRA-Python module to connect to my company's instance on JIRA and it requires me to pass the certificate and key for this.
However using the OpenSSL module,i'm unable to read my local certificate and key to pass it along the request.
the code for reading is below
import OpenSSL.crypto
c = open('/Users/mpadakan/.certs/mpadakan-blr-mpsot-20160704.crt').read()
cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, c)
the error i get is
Traceback (most recent call last):
File "flaskApp.py", line 19, in <module>
cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, c)
TypeError: must be X509, not str
could someone tell me how to read my local .crt and .key file into x509 objects?
#can-ibanoglu was right on:
import OpenSSL.crypto
cert = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM,
open('/tmp/server.crt').read()
)
>>> cert
<OpenSSL.crypto.X509 object at 0x7f79906a6f50>
Which format in your .crt file.
Are there:
text starting with -----BEGIN CERTIFICATE-----
base64 text started with MI chars
binary data starting with \x30 byte?
In first two case there are PEM format, but in second one you are missing staring line, just add it to get correct PEM file or convert file to binary with base64 and get third case.
In third case you have DER format, so to load it you should use OpenSSL.crypto.FILETYPE_ASN1
I am trying to generate a SSH key pair with the python module paramiko. There doesn't seem to be much info about key generation. I've read through the paramiko docs but can't figure out whats wrong. I can generate a private and public key without password encryption. However, when I try to encrypt the private key I get the following error.
ValueError: IV must be 8 bytes long
I believe the above error is from pycrypto. I've looked through the relevant code in paramiko.pkey and pycrypto without any luck.
Here is a small example.
import paramiko
def keygen(filename,passwd=None,bits=1024):
k = paramiko.RSAKey.generate(bits)
#This line throws the error.
k.write_private_key_file(filename,password = 'cleverpassword')
o = open(fil+'.pub' ,"w").write(k.get_base64())
traceback
Traceback (most recent call last):
File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Documents/test.py", line 14, in keygen
k.write_private_key_file(filename,password = 'cleverpassword')
File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/paramiko/rsakey.py", line 127, in write_private_key_file
self._write_private_key_file('RSA', filename, self._encode_key(), password)
File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/paramiko/pkey.py", line 323, in _write_private_key_file
self._write_private_key(tag, f, data, password)
File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/paramiko/pkey.py", line 341, in _write_private_key
data = cipher.new(key, mode, salt).encrypt(data)
File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/Crypto/Cipher/DES3.py", line 114, in new
return DES3Cipher(key, *args, **kwargs)
File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/Crypto/Cipher/DES3.py", line 76, in __init__
blockalgo.BlockAlgo.__init__(self, _DES3, key, *args, **kwargs)
File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/Crypto/Cipher/blockalgo.py", line 141, in __init__
self._cipher = factory.new(key, *args, **kwargs)
ValueError: IV must be 8 bytes long
The Problem
This looks like a bug in paramiko.
If you look at the line that threw the error in pkey.py, it is the following line:
data = cipher.new(key, mode, salt).encrypt(data)
Let us now look at the lines before it, which set the mode by first selecting a cipher_name.
# since we only support one cipher here, use it
cipher_name = list(self._CIPHER_TABLE.keys())[0]
cipher = self._CIPHER_TABLE[cipher_name]['cipher']
keysize = self._CIPHER_TABLE[cipher_name]['keysize']
blocksize = self._CIPHER_TABLE[cipher_name]['blocksize']
mode = self._CIPHER_TABLE[cipher_name]['mode']
Here are the contents of _CIPHER_TABLE.
_CIPHER_TABLE = {
'AES-128-CBC': {'cipher': AES, 'keysize': 16, 'blocksize': 16, 'mode': AES.MODE_CBC},
'DES-EDE3-CBC': {'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC},
}
Observe how the comment contradicts the code. Two ciphers are available, and the line above which selects the cipher_name assumes there is only one.
Based on the error, it appears that 'DES-EDE3-CBC' is selected. If we look at the comment in DES3.py, we see the following requirement for an IV.
IV : byte string
The initialization vector to use for encryption or decryption.
It is ignored for `MODE_ECB` and `MODE_CTR`.
For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption
and `block_size` +2 bytes for decryption (in the latter case, it is
actually the *encrypted* IV which was prefixed to the ciphertext).
It is mandatory.
From paramiko's source, we observe that no IV is passed, and hence the error we saw.
Workaround
Change the following line in pkey.py to hardcode the 'AES-128-CBC' cipher instead.
# cipher_name = list(self._CIPHER_TABLE.keys())[1]
cipher_name = 'AES-128-CBC'
I have a little Python 3.3 script that successfully sends (SendMessage) a WM_COPYDATA message (inspired from here , works with XYplorer):
import win32api
import win32gui
import struct
import array
def sendScript(window, message):
CopyDataStruct = "IIP"
dwData = 0x00400001 #value required by XYplorer
buffer = array.array("u", message)
cds = struct.pack(CopyDataStruct, dwData, buffer.buffer_info()[1] * 2 + 1, buffer.buffer_info()[0])
win32api.SendMessage(window, 0x004A, 0, cds) #0x004A is the WM_COPYDATA id
message = "helloworld"
sendScript(window, message) #I write manually the hwnd during debug
Now I need to write a receiver script, still in Python. The script in this answer seems to work (after correcting all the print statements in a print() form). Seems because it prints out all properties of the received message (hwnd, wparam, lparam, etc) except the content of the message.
I get an error instead, UnicodeEncodeError. More specifically,
Python WNDPROC handler failed
Traceback (most recent call last):
File "C:\Python\xxx.py", line 45, in OnCopyData
print(ctypes.wstring_at(pCDS.contents.lpData))
File "C:\Python\python-3.3.2\lib\encodings\cp850.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_map)[0]
UnicodeEncodeError: 'charmap' codec can't encode characters in position 10-13: character maps to <undefined>
I don't know how to fix it, also because I'm not using "fancy" characters in the message so I really can't see why I get this error. I also tried setting a different length of message in print(ctypes.wstring_at(pCDS.contents.lpData)) as well as using simply string_at, but without success (in the latter case I obtain a binary string).
ctypes.wstring (in the line print (ctypes.wstring_at(pCDS.contents.lpData))) may not be the string type that the sender sent. Try changing it to:
print (ctypes.string_at(pCDS.contents.lpData))