3

I want to do an AES encryption and decryption to string. but the key and message must be in bytes, so I converted the message to bytes by doing this:

b"string"

This is my AES code:

# Encryption
encryption_suite = AES.new(b'1234567812345678', AES.MODE_OCB)
cipher_text = encryption_suite.encrypt(b"A really secret message. Not for prying eyes.")

# Decryption
decryption_suite = AES.new(b'1234567812345678', AES.MODE_OCB)
plaintext = decryption_suite.decrypt(cipher_text)

however i need to turn the decrypted plain text back to string to be readable. Currently the plaintext looks like this:

b'x\x85\x92\x9d\xe6\x0bJ\xfe\x9b(\x10G\x8e\x05\xc5\xf4\xcdA9\xc18\xb8_\xf9vbmK\x16\xf8\xa3\xb6'

I tried using

plaintext.decode(encoding='windows-1252')

and

plaintext.decode("utf-8").strip('\x00')

but all i get is this:

UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 3: character maps to

or this:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb1 in position 1: invalid start byte

I need to convert these bytes back to a readable string.. if you can help, that would be appreciated.

MNS
  • 45
  • 2
  • 2
  • 8
  • this question was a lot of help but i couldnt understand how to apply it to my case.. also a lot of the answers were python 2 https://stackoverflow.com/questions/1220751/how-to-choose-an-aes-encryption-mode-cbc-ecb-ctr-ocb-cfb – MNS May 23 '18 at 06:37
  • Hi, if you are getting back the plaintext from above code then something is wrong in your script. The plaintext should look the same as the paintext you use as input at the beginning... so you should be getting – Zapho Oxx May 23 '18 at 06:54
  • @ZaphoOxx as you can see the encryption and decryption methods are pre-defined so i am sure the output is correct. the problem is that the input has to look like those bytes in order for the functions to work, so naturally the output looks like that too. – MNS May 23 '18 at 07:02
  • Which module are you using to do the crypto? It looks like pycrypto, but I have pycrypto 2.6.1, and it doesn't support AES.MODE_OCB. – PM 2Ring May 23 '18 at 07:30
  • @MNS, I tried to reproduce your code above using from Crypto.Cipher import AES but that wont recognize MODE_OCB. Can you provide some more details on what module(s) you are using. – Zapho Oxx May 23 '18 at 07:32
  • @PM2Ring from Crypto.Cipher import AES – MNS May 23 '18 at 07:32
  • @MNS, according to the documentation OCB requires a nonce and mac_len as additional arguments. That might be the issue why you do not the get the same plaintext as decrypted output. Again, the plainttext needs to be same as your plaintext. If it is not then something is wrong. See also link to documentation for OCB here: [link](http://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html#ocb-mode) Best – Zapho Oxx May 23 '18 at 07:42
  • you have to install it from the cmd.. pip install crypto – MNS May 23 '18 at 07:46
  • as for whether it works or not, the fact that it produces an output means it is working. the problem is that it needs its input in the form of bytes, and its output is also bytes. I managed string to byte but it's the byte to string that i have an issue with – MNS May 23 '18 at 07:48
  • Your original plaintext is the `bytes` string `b"A really secret message. Not for prying eyes."` So if it's being encrypted and decrypted correctly you should get that same `bytes` string back, which can be decoded to a text string with `.decode('ascii')`. – PM 2Ring May 23 '18 at 07:50
  • UnicodeDecodeError: 'ascii' codec can't decode byte 0xd6 in position 1: ordinal not in range(128) – MNS May 23 '18 at 07:57
  • Are you sure that you are using that [crypto](https://pypi.org/project/crypto/) package? I haven't installed it, but it doesn't appear to be consistent with your code. ` from Crypto.Cipher import AES` looks like [`pycrypto`](https://pypi.org/project/pycrypto/), but it's quite old, and as I said earlier, the latest version (2.6.1) doesn't have `AES.MODE_OCB` – PM 2Ring May 23 '18 at 07:58
  • I've given you all my code, and again since there's an output i think it does work. If you can solve the aes problem another way i'm open to suggestions. – MNS May 23 '18 at 08:05
  • 1
    Seriously, how can you think it works if the initial input and decrypted output dont match ? Think about it. b"my message" is encrypted. once you decrypt the cipher you should get back exactly the same. otherwise the whole encryption doesnt make sense. does it ? – Zapho Oxx May 23 '18 at 08:14

3 Answers3

12

The main issue with your code is that you don't supply a nonce to AES.new(). OCB requires a nonce; if you don't supply one a random nonce will be created every time you create a new AES object, and so decryption will fail.

From the docs:

nonce (byte string): a non-repeatable value, of length between 1 and 15 bytes.. If not present, a random nonce of the recommended length (15 bytes) will be created.

You have two options, either create a nonce and pass it to AES.new() (in encryption and decryption) or use the random nonce created by AES during encryption.

Next, OCB is an authenticated encryption algorithm but it seems that you don't check the MAC. This is important because the MAC verifies the integrity of the ciphertext.

The encryption and decryption methods of AES accept and return bytes. You can convert the plaintext (if it is text) to string with .decode(). If you want to convert the ciphertext to string you'll have to base64-encode it first, in order to encode the raw bytes to ASCII characters (just remember to decode before decryption). b64encode() also returns bytes but can be converted to string easily.

An example,

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from base64 import b64encode

key = get_random_bytes(16) # A 16 byte key for AES-128
nonce = get_random_bytes(15)
message = "A really secret message. Not for prying eyes.".encode()

cipher = AES.new(key, AES.MODE_OCB, nonce=nonce)
ciphertext, mac = cipher.encrypt_and_digest(message)

cipher = AES.new(key, AES.MODE_OCB, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, mac)

print(b64encode(ciphertext).decode())
#CSwHy3ir3MZ7yvZ4CzHbgYOsKgzhMqjq6wEuutU7vJJTJ0c38ExWkAY1QkLO
print(plaintext.decode())
#A really secret message. Not for prying eyes.

Note that if .decrypt_and_verify() fails to verify the MAC a ValueError exception will be raised, so you may want to use a try-except block. The nonce and mac values are not secret and it's safe to store them next to the ciphertext.

Finally, if you plan to derive a key from a passphrase you should use a password-based KDF. KDFs create strong keys, use salt and iterations, and they are very resistant to fruteforce attacks. You will find KDF functions in Crypto.Protocol.KDF.

t.m.adam
  • 15,106
  • 3
  • 32
  • 52
  • thanks that did work! I have another question if you could help: do you know how i could rid the string of " b' ' " ? i tried string[2: ] but it just removes part of the string itself not the b – MNS May 23 '18 at 08:24
  • The `b''` means that your string is binary, and it's not part of the actual string. If you want to turn your decrypted plaintext to unicode string, use `decode`. See updated code. If you want to convert the ciphertext to string, you'll have to base64 encode it first. – t.m.adam May 23 '18 at 08:28
  • Hi @t.m.adam, hope you are doing well. I will be glad if you care to give [this post](https://stackoverflow.com/questions/60815608/log-in-to-a-problematic-site-using-requests) a go to offer any solution. Thanks. – asmitu Mar 23 '20 at 15:13
1

You can't convert binary data to a string because it is inherently not a string. It's binary data. The chances of the output of your encryption just happening, by chance, to be a correctly formatted UTF8 string is pretty unlikely.

You should look at base64 instead. It bloats the data (3 bytes to 4 characters) but is better suited for your use case.

EDIT:. My mistake, I misunderstood your question. The output you have isn't correct, the first byte is not a '1' in UTF8. This is likely an issue with the encryption/decryption.

Luke Joshua Park
  • 9,527
  • 5
  • 27
  • 44
  • thank you for your response.. i am not trying to convert the cipher text , only the plain text from the decryption. – MNS May 23 '18 at 06:47
  • i tried base64 but its ouptut is like so: ZGF0YSB0byBiZSBlbmNvZGVk. which the aes does not accept – MNS May 23 '18 at 07:55
0

I think your encoding is "ISO-8859-1". So you can do:

plaintext.decode("ISO-8859-1")
Pelonomi Moiloa
  • 516
  • 5
  • 12