python-3.xbase64aeswritetofilepycryptodome

AES Key (Byte array) to string on Python


Ok, I'm a programmer on C#, I used to have a program that transform a byte array from the AES key and IV to a base64 string and write this on a file. But I'm with a big problem, I need to made te same thing on Python, and the Python works very bad with byte arrays. I'm with this problem, I tried to generate a key with 'Crypto.Random.get_random_bytes(16)' and convert he to a string, on the C#, its simple, I convert to base64, and the base64 is almost the same that strings, we can concatanate with normal strings, write in files and all the other things. But in the Python when I convert a byte array to a string, give me this error:

key = get_random_bytes(16).decode(encoding="utf-8")

'utf-8' codec can't decode byte 0xaf in position 2: invalid start byte

This doesn't is the same problem all the time, the position and byte change occasionally. And the problems is on the str() too. So, I tried to convert to a base64 string, I haved this problem with the linebreak maker:

breaked = '\n'.join(key[i:i+every] for i in range(0, len(key), every))

TypeError: sequence item 0: expected str instance, bytes found

So, like a distressed person I remove the linebraker, and try put all on one line.

text = "------ Crypton Key Begin ------\n"+key+"\n------ Crypton Key End ------"

TypeError: can only concatenate str (not "bytes") to str

So oook, its very problematic, so I tried write on the file only the base64.

fob.write(key)

TypeError: write() argument must be str, not bytes

I don't know more what can I do, because in the C# a simple code works fine. If help, this is the C# Code:


Aes aesAlgorithm = Aes.Create();
aesAlgorithm.KeySize = 256;
aesAlgorithm.GenerateKey();
aesAlgorithm.GenerateIV();
_key = aesAlgorithm.Key;
_vector = aesAlgorithm.IV;

FileText = string.Format("----- Start Key File -----\n<0>{0}</0>\n<1>{1}</1>\n----- End Key File -----",
     Convert.ToBase64String(_key),
     Convert.ToBase64String(_vector)
)
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "SEA file (*.sea) | *.sea";
if (saveFileDialog.ShowDialog() == true)
     File.WriteAllText(saveFileDialog.FileName, FileText);
else return;

My Python class:


from tkinter.filedialog import asksaveasfilename, askopenfilename
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from base64 import b64encode

class Crypton:
    @staticmethod
    def GetKey():
        return get_random_bytes(16).decode(encoding="utf-8")

    @staticmethod
    def Encrypt(data, key):
        if type(key) != type(str):
            if type(key) == type(get_random_bytes(16)):
                key = key.decode(encoding='utf-8')
            else:
                raise Exception("The type of the key is not supported.")
        cipher = AES.new(key, AES.MODE_EAX)
        ciphertext, tag = cipher.encrypt_and_digest(data)
        nonce = cipher.nonce
        str_decoded = Crypton.ToString([key, ciphertext, tag, nonce])
        return str_decoded

    @staticmethod
    def Decrypt(str_decoded):
        binary_decoded = Crypton.ToBinary(str_decoded)
        cipher = AES.new(binary_decoded[0], AES.MODE_EAX, binary_decoded[3])
        data = cipher.decrypt_and_verify(binary_decoded[1], binary_decoded[2])
        return data

    @staticmethod
    def Decrypt(key, ciphertext, tag, nonce):
        return Crypton.Decrypt([key, ciphertext, tag, nonce])

    @staticmethod
    def ToBinary(str_decoded):
        utf = 'utf-8'
        key = str_decoded[0].encode(encoding=utf)
        ciphertext = str_decoded[1].encode(encoding=utf)
        tag = str_decoded[2].encode(encoding=utf)
        nonce = str_decoded[3].encode(encoding=utf)
        return [key, ciphertext, tag, nonce]

    @staticmethod
    def ToString(binary_decoded):
        utf = 'utf-8'
        key = binary_decoded[0].decode(encoding=utf)
        ciphertext = binary_decoded[1].decode(encoding=utf)
        tag = binary_decoded[2].decode(encoding=utf)
        nonce = binary_decoded[3].decode(encoding=utf)
        return [key, ciphertext, tag, nonce]

    @staticmethod
    def CreateCryptonKeyFile(key):
        bricked_key = Crypton.BreakKey(key)
        text = "------ Crypton Key Begin ------\n"+bricked_key+"\n------  Crypton Key End  ------"
        file = asksaveasfilename(
            filetypes=[("Crypton Key File", ".cey")],
            defaultextension=".cey")
        with open(file, 'w') as fob:
            fob.write(text)

    @staticmethod
    def BreakKey(key):
        every = 31
        breaked = '\n'.join(key[i:i+every] for i in range(0, len(key), every))
        return breaked

    @staticmethod
    def ReadCryptonKeyFile():
        file = askopenfilename(
            filetypes=[("Crypton Key File", ".cey")],
            defaultextension=".cey")
        with open(file, 'r').read() as infile:
            return Crypton.ReadCryptonKeyString(infile)

    @staticmethod
    def ReadCryptonKeyString(string):
        key = ''.join(
            string.replace("------ Crypton Key Begin ------\n", "")\
            .replace("\n------  Crypton Key End  ------", "")\
            .split('\n')
        )
        return key


Solution

  • Well, before I write the solution I want to say one thing. I search for answer on a lot of places, everybody say that don't have a good solution, I don't judge, because such @Jonn Hanley say, nobody will write the code for you. The reason to I write this is, never give up, because in the majority of the times the solution don't will be easy, but will exist.

    And my thank you to @Maurice Meyer on this question, because in this question he show me a important detail, the bytes can be converted on int, and int can be converted to str, and the reverse is true.

    Thinking this, I write this simple code:

    from Crypto.Random import get_random_bytes
    _bytes = get_random_bytes(16)
    print(_bytes)
    print(type(_bytes))
    print('')
    _len = len(_bytes) #Same to the 16 on get_random_bytes
    print(_len)
    _int = int.from_bytes(_bytes, "big", signed=True)
    print(_int)
    print(type(_int))
    print('')
    _str = str(_int)
    print(_str)
    print(type(_str))
    print('')
    _int_again = int(_str)
    print(_int_again)
    print(type(_int_again))
    print('')
    _bytes_again = _int.to_bytes(_len, "big", signed=True)
    print(_bytes_again)
    print(type(_bytes_again))
    

    My output:

    b'\xfb\tn\xa3\x0e\n\xaf|J~Z\x92s\xc3@\x0b'
    <class 'bytes'>
    
    16
    -6597165323605166304435026916416471029
    <class 'int'>
    
    -6597165323605166304435026916416471029
    <class 'str'>
    
    -6597165323605166304435026916416471029
    <class 'int'>
    
    b'\xfb\tn\xa3\x0e\n\xaf|J~Z\x92s\xc3@\x0b'
    <class 'bytes'>
    

    Obvius, have ways that this string can be more coded, in my .py I will made this string more differente that the simple int, because that's too obvious, but the important is:

    Yes, have a simple way to transform a bytes on a str.

    ---> EDITED <------> 01/04/2023 9h44 <---

    Some peoples are with doubt about my code, so I will complement my answer.

    I know that its works, because I tried on a loop and I don't receive problems, and the exit every is the same. But if someone have doubt, no problems, paste this code on a python File and try for yourself:

    
    _len = 16
    _int = -6597165323605166304435026916416471029
    _str = str(_int)
    print(_str)
    print(type(_str))
    print('')
    _int_again = int(_str)
    print(_int_again)
    print(type(_int_again))
    print('')
    _bytes_again = _int.to_bytes(_len, "big", signed=True)
    print(_bytes_again)
    print(type(_bytes_again))
    
    

    output:

    
    -6597165323605166304435026916416471029
    <class 'str'>
    
    -6597165323605166304435026916416471029
    <class 'int'>
    
    b'\xfb\tn\xa3\x0e\n\xaf|J~Z\x92s\xc3@\x0b'
    <class 'bytes'>
    
    

    If you have the same output, it works, you can see that is the same bytes of my first output. I'll explaine some important things to you code don't brick. First, See the byte lenght, if this are different then you will receive problems, the same thing about "big" and "little" the calling 'byte order'. Give the signed too, because this say to the code if can be negative integer, I accept the negative, so I put True. If you put False, every that you receive a negative number the code will throw a exeption on you. Give the same arguments to the conversion and to revert the conversion and it will works.

    If you alread have some doubt about my code, I don't judge you, I think that you can use json, like the official documentation say:

    
    import json
    from base64 import b64encode
    from Crypto.Cipher import AES
    from Crypto.Random import get_random_bytes
    
    data = b"secret"
    key = get_random_bytes(16)
    cipher = AES.new(key, AES.MODE_CFB)
    ct_bytes = cipher.encrypt(data)
    iv = b64encode(cipher.iv).decode('utf-8')
    ct = b64encode(ct_bytes).decode('utf-8')
    b64key = b64encode(key).decode('utf-8')
    result = json.dumps({'key': b64key, 'iv':iv, 'ciphertext':ct})
    
    try:
        b64 = json.loads(result)
        iv = b64decode(b64['iv'])
        key = b64decode(b64['key'])
        ct = b64decode(b64['ciphertext'])
        cipher = AES.new(key, AES.MODE_CFB, iv=iv)
        pt = cipher.decrypt(ct)
        print("The message was: ", pt)
    except (ValueError, KeyError):
        print("Incorrect decryption")
    
    

    I don't try it, so I don't know if works, but such is the official documentation, is very possible that works.