Decrypt in Golang what was encrypted in Python AES CFB - python

Based on the Golang documentation on CFB decryption I wrote a minimal working example to decrypt a string that was encrypted with AES CFB and then base 64 encoded in python3.
The golang decryption works fine when the message was encrypted within Golang (with the encryption function from the Golang doc example).
However when I encrypt the message in a python script using the python crypto package, I am unable to decrypt it in the golang script successfully. I don't get the right bytes back.
$ python3 stack.py
Going to encrypt and base64 "This is not encrypted" result:
b'jf9A5LCxKWPuNb1XiH+G3APAgR//'
Now going to call the Golang script:
b'Hello from Golang, going to decrypt: jf9A5LCxKWPuNb1XiH+G3APAgR//
Result: Tl!\xca/\xf1\xc0\xb2\xd01Y\x02V\xec\xdf\xecy\xd38&\xd9\n'
Blocksize is 16 by default for both AES implementations.
So the question: What is going wrong?
Golang script:
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"os"
)
func main() {
key := []byte("TfvY7I358yospfWKcoviZizOShpm5hyH")
iv := []byte("mb13KcoviZizvYhp")
payload_python := os.Args[1]
fmt.Println("Hello from Golang, going to decrypt: "+payload_python+" Result: "+string(decrypt(key, payload_python, iv)))
}
func decrypt(key []byte, cryptoText string, iv []byte) []byte {
ciphertext, _ := base64.StdEncoding.DecodeString(cryptoText) //decode base64 coding
//prepare decryption based on key and iv
block, _ := aes.NewCipher(key)
stream := cipher.NewCFBDecrypter(block, iv)
//decrypt
stream.XORKeyStream(ciphertext, ciphertext)
return ciphertext
}
Python script:
#!/usr/bin/env python3
import base64
from Crypto.Cipher import AES
from subprocess import check_output
original_message = 'This is not encrypted'
key = 'TfvY7I358yospfWKcoviZizOShpm5hyH'
iv = 'mb13KcoviZizvYhp'
#prepare encryption
cfb_cipher_encrypt = AES.new(key, AES.MODE_CFB, iv)
#encrypt and base64 encode
encryptedpayload = base64.b64encode(cfb_cipher_encrypt.encrypt(original_message))
print('Going to encrypt and base64 "{}" result:\n{}\n'.format(original_message,encryptedpayload))
print('Now going to call the Golang script:')
print(check_output('go run stack.go {}'.format(encryptedpayload.decode()),shell=True))

Try encrypting from Python like this.
The result can then be unencrypted from Go successfully.
#!/usr/bin/env python3
import base64
from Crypto.Cipher import AES
MODE = AES.MODE_CFB
BLOCK_SIZE = 16
SEGMENT_SIZE = 128
def _pad_string(value):
length = len(value)
pad_size = BLOCK_SIZE - (length % BLOCK_SIZE)
return value.ljust(length + pad_size, '\x00')
def encrypt(key, iv, plaintext):
aes = AES.new(key, MODE, iv, segment_size=SEGMENT_SIZE)
plaintext = _pad_string(plaintext)
encrypted_text = aes.encrypt(plaintext)
return encrypted_text
key = 'TfvY7I358yospfWKcoviZizOShpm5hyH'
iv = 'mb13KcoviZizvYhp'
original_message = 'This is not encrypted'
encryptedpayload = base64.b64encode(encrypt(key, iv, original_message))
print('Going to encrypt and base64 "{}" result:\n{}\n'.format(original_message,encryptedpayload))
Source: http://chase-seibert.github.io/blog/2016/01/29/cryptojs-pycrypto-ios-aes256.html

Related

Struggling with AES in python

To keep it short: I am sending an encrypted message (AES with CBC) to another service, and it returns me an encrypted response, but I can't decrypt it because they are not using padding? (to be honest, I don't know much about encryption and its mechanisms).
This is my implementation (based on the documentation) of a class used to encrypt and decrypt messages.
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
class AESCipher:
def __init__(self, key, iv):
self.key = bytes.fromhex(key)
self.iv = bytes.fromhex(iv)
def encrypt(self, msg):
msg = pad(msg.encode(), AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return cipher.encrypt(msg)
def decrypt(self, msg):
cipher = AES.new(self.key, AES.MODE_CFB, self.iv)
return unpad(cipher.decrypt(msg), AES.block_size)
I can easily encrypt (and even decrypt my own message) using this implementation, but the actual response from the host always fails to decrypt. First I thought it was a problem with the host, but when I use an AES online tool, it kinda works (I used this one)
To reproduce the issue, I send a malformed message and receive an encrypted error:
msg = b"X\xb4\xc6\xc9j\x92\x8f\xe5\x84\xe5\\N7\x8bv\xb8\x02\x0e\xed*\xe7\x92\xdd/\xf1\xff\xdfj 5\x00\x91\xb5;\xb6Q\x08\xc8\xf1PFF\x1aw\x93\xa7\xbe\xa7\xafD\xe7:=\x8b\x1d\x86i\xa8\x95\x107\xf2\xbcF1\x80D\x8c\x98\x1f\xfc\x80\xc3\xd6\x81'\xf3\xd98\x93\x8bv\xf7P\xc9\xb1L,\x8aJ\x05\xd8\xd0P\x10\rQ\xba\xf5&4\x0e\xf0\x97\xf5\xa5B\xb7\xbda_?\xcbk~\xe6\xfe\xf6\x8f\x92\x1b;#\xd2\x87\xc6^\n"
The key and iv are:
key = AEC273769C9C4E9830D5FA3929BE1F5115E4BF085BCBA6ACCBAEF63E654D8AE3
iv = ACE499278E5FDC6849DDF23A8966D7CF
I get this error:
File "/home/richter/Code/Test_encryption/encryptation.py", line 22, in decrypt
return unpad(uncrypt, AES.block_size)
File "/home/richter/Code/Test_encryption/.venv/lib64/python3.10/site-packages/Crypto/Util/Padding.py", line 92, in unpad
raise ValueError("Padding is incorrect.")
ValueError: Padding is incorrect.
I also tried a different library, one written entirely in python (and boy, it's slow) but got the same issue with the size of the package.

pycrypto AES CBC

I have written some code using the pycrypt library and I must be doing something wrong, but I can't figure out what it is that I am doing wrong. I can (nearly) decrypt messages with the wrong initialization vector even though I believe I am following their examples.
from Crypto.Cipher import AES
import os
from string import ascii_letters
key, iv = os.urandom(32), os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = ascii_letters
plaintext += ' ' * (-len(plaintext) % 16) # Padding
ciphertext = cipher.encrypt(plaintext)
cipher = AES.new(key, AES.MODE_CBC, os.urandom(16))
text = cipher.decrypt(ciphertext)
# text[16:] = b'qrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ '
Now aside from the first 16 bytes of the decrypted text, you have everything decrypted correctly with a totally random choice of IV. Can someone help me figure out where I am going wrong?
That's normal for cipher block chaining. In CBC decryption, the IV is only necessary to reconstruct the first block of plaintext. The computation of other blocks of plaintext doesn't actually involve the IV. Here's a diagram (source: Wikimedia user WhiteTimberwolf, public domain):

Decrypt a file encrypted in Python using only OpenSSL

I have a file encrypted in Python and pycryptodome like this:
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP
key = RSA.generate(2048)
secret_key = key.exportKey(passphrase='letmein', pkcs=8, protection='scryptAndAES128-CBC')
public_key = key.publickey().exportKey()
rsa_key = RSA.importKey(public_key)
session_key = get_random_bytes(16)
cipher_rsa = PKCS1_OAEP.new(rsa_key)
cipher_aes = AES.new(session_key, AES.MODE_EAX)
ciphertext, tag = cipher_aes.encrypt_and_digest(data)
dst.write(cipher_rsa.encrypt(session_key))
dst.write(cipher_aes.nonce)
dst.write(tag)
dst.write(ciphertext)
And I am able to decode it like this:
rsa_key = RSA.importKey(secret_key, 'letmein')
enc_session_key, nonce, tag, ciphertext = [
src.read(x) for x in (rsa_key.size_in_bytes(), 16, 16, -1)
]
cipher_rsa = PKCS1_OAEP.new(rsa_key)
session_key = cipher_rsa.decrypt(enc_session_key)
cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
decoded = cipher_aes.decrypt_and_verify(ciphertext, tag)
Is there a way to decrypt the file using command line with openssl? Or how should I modify the code so that it would be possible?
You could base 64 encode the separate components and then splitting them using a separator. Command line is mainly text based, so it would be easier to program that in e.g. Bash.
EAX mode is not directly supported so trying CBC mode would make it easier. OpenSSL command line doesn't seem to support any AEAD cipher for now so you would lose the authenticity it may have offered.
Finally, the combination of OAEP and a cipher doesn't seem supported, so you may have to handle the binary result and convert it to a symmetric cipher, e.g. in hexadecimals.

Decrypting ActiveSupport::MessageEncryptor encrypted values in python

I have a rails project that has sensitive string type values stored on a remote Postgresql database. I encrypted these strings using the ActiveSupport::MessageEncryptor (http://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html) functions. I have the key I used to encrypt them and trying to find a way to retrieve them from the database and decrypt them in a python script.
I'am open for any suggestions on how to achieve this in any other way using rails and python. And much appreciated for any advice on how to decrypt these values in python.
Thanks,
So we managed to solve this with a lot of hit and trial and lot of help from some outdated or similar codes on internet.
Versions used of different libraries:
Rails version(from which messages were being encrypted): 5.2.x
Python version we are using: 3.8
We are also using Django rest framework(3.12.2).
Versions of libraries used in the script(this bit us hard, because some libraries' new versions were not working as expected, didn't dig in much detail as to why):
pycryptodomex: 3.9.7
cryptography: 3.3.1
rubymarshal: 1.2.7
Actual encryptor/decryptor
# pylint: disable=missing-module-docstring,too-few-public-methods
import base64
import hashlib
import os
from Cryptodome.Cipher import AES
from cryptography.hazmat.primitives.ciphers import Cipher
from rubymarshal.reader import loads
from rest_framework.response import Response
from rest_framework import status
from rubymarshal.writer import writes
from dotenv import load_dotenv
load_dotenv()
class MyRailsEncryptor():
"""
This is a class for providing encryption/decryption functionality.
"""
#classmethod
def get_encrypted_data(cls, data):
"""
This method handles encryption algorithm takes in data and return encrypted data
"""
key = cls.get_key()
iv = os.urandom(16)
auth_tag = os.urandom(16)
cipher = AES.new(key, AES.MODE_GCM, iv)
ciphertext = cipher.encrypt(writes(data))
ciphertext = base64.b64encode(ciphertext)
iv = base64.b64encode(iv)
auth_tag = base64.b64encode(auth_tag)
blob = f'{ciphertext.decode("utf-8")}--{iv.decode("utf-8")}--{auth_tag.decode("utf-8")}'
return blob
#classmethod
def get_decrypted_data(cls, data):
"""
This method handles decryption algorithm takes in encrypted_data and return decrypted plain text
"""
key = cls.get_key()
ciphertext, iv, auth_tag = data.split("--")
ciphertext = base64.b64decode(ciphertext)
iv = base64.b64decode(iv)
cipher = AES.new(key, AES.MODE_GCM, iv)
try:
decrypted_data = cipher.decrypt(ciphertext)
except AssertionError as err:
return Response({"Assertion Error": err.message_dict}, status=status.HTTP_400_BAD_REQUEST)
plaintext = loads(decrypted_data)
return plaintext
#classmethod
def get_key(cls):
"""
Returns key generated by Encryption key and Encryption secret using hashlib on rails methodology
"""
return hashlib.pbkdf2_hmac('sha1', os.getenv("ENCRYPTION_KEY").encode(),
os.getenv("ENCRYPTION_SECRET").encode(), 65536, 32)
Keys will obviously be synced/provided by the encryption party, this contains a method for encryption as well, though we only need decryption.

Python 2.7 crypto AES

I got a problem with aes in python 2.7
import pyelliptic
iv = pyelliptic.Cipher.gen_IV('aes-256-cfb')
ctx = pyelliptic.Cipher("secretkey", iv, 1, ciphername='aes-256-cfb')
ciphertext = ctx.update('test1')
ciphertext += ctx.final()
ctx2 = pyelliptic.Cipher("secretkey", iv, 0, ciphername='aes-256-cfb')
Now I don't know how to send this msg to server, and decrypt it on server, because I don't know the IV and my server can't decrypt it. The server has the secret key.
The IV does not need to be kept secret, but it needs to unique (random) for every encrypt operation with the same key.
Many implementations just add the IV bytes to the front of the ciphertext. You have to know how long the IV is for your implementation so that you can slice it off before decrypting.
# encrypt
ciphertext = iv + ciphertext
# decrypt
blocksize = pyelliptic.Cipher.get_blocksize('aes-256-cfb')
iv = ciphertext[0:blocksize]
ciphertext = ciphertext[blocksize:]
From the code it is apparent that the IV is generated in the same size as the cipher blocksize, so it is safe to slice a block from the ciphertext to get the IV.

Categories

Resources