1

I'm using the pycryptodome module and its AES functionality to encrypt some data. However I need to generate a key for the AEScipher that I can later retrieve. The project stores all private data (including keys) in the form of an image. Basically we use an array of pixels and create the image using PIL and retrieve the pixel_list using the getdata() function.

To create the image:-

array = numpy.array(pixels, dtype = numpy.uint8)
new_image = Image.fromarray(array)
new_image.save('user_key.png')

Note that pixels is a list of list of tuples of integers [[(...), (...)], [(...), (...)], ...] and this is the object that carries keys To get keys from the image:-

im = Image.open(image_path)
return list(im.getdata())

Now I'm not able to directly store the AES key, assuming I generate it with Random.get_random_bytes(AES.key_size) from the Crypto module.

How can I generate a cryptographically secure key but also retrieve it by using one of the keys in pixels, as in a tuple of integers?

Edit:-

To elaborate, the pixels object is a list of list of tuples of integers, each tuple contains 3 integers and each integer can range from 0 to 255. The 0th index of the pixels object may look like this- [(69, 147, 245), (120, 212, 198), ...] The key_list object I'm referring to is actually list(im.getdata()). This is a list of tuples of integers, each tuple contains 3 integers and each integer can range from 0 to 255. This looks like this- [(69, 147, 245), (120, 212, 198)....] Hence, the 0th index of key_list will be (69, 147, 245)

I need to store the AES key on par with these values. I'd ideally like to store the AES key as a tuple of 3 integers ranging from 0 to 255. So yes, I need to convert the AES key into a tuple and then store it in pixels.

One more key detail, the tuples contain 3 integers because they represent the RGB values respectively to create the image. I believe the tuple can be made with 4 integers as well to represent RGBA values. So that will solve the multiple of 3 problem.

But there's another problem. Each tuple in pixels is actually generated through [i for i in itertools.product(range(256), repeat=3)]. In order to generate a tuple of 4 integers instead of 3 I'll have to change repeat=3 to repeat=4, this will raise MemoryError.

martineau
  • 119,623
  • 25
  • 170
  • 301
Chase
  • 5,315
  • 2
  • 15
  • 41
  • What is preventing you from directly store the `AES key`? How are the keys stored in the image (or list of tuples)? – martineau Dec 12 '19 at 08:39
  • @martineau I cannot store an AES key that I get from `get_random_bytes` since it is a byte string like so `b'\x80in\xbe\x06b\x8f\x8fZ}l-\xb4j\xb5\x1f'`. In the list of tuples, everything is an integer, so for instance `key_list[0] = [(196, 202, 432)]` – Chase Dec 12 '19 at 08:44
  • So you want to know how to convert a byte string into list of tuples—right? What data is each tuple? How long are they? You've now described the input, and detail description of the output is also required. – martineau Dec 12 '19 at 08:47
  • @martineau Added an edit to elaborate! – Chase Dec 12 '19 at 08:59
  • That a vast improvement. Still need to know what to do when the number of bytes in the key isn't a multiple of 3. The sample key you showed in your previous comment is 16 bytes long, which is not a multiple of 3. How should this be handled with respect to converting the bytes to 3-tuples? – martineau Dec 12 '19 at 09:04
  • @martineau I added another edit, I tried making the image with a tuple of 4 integers and it will work great, my problem will be on generating the list however, but that's offtopic on this question, so I'll just be glad with the answer to my original question – Chase Dec 12 '19 at 09:25
  • If the number of bytes in the AES byte string is not a multiple of the tuple size (whether that's 3, 4, or whatever), then there needs to be some defined way to handle the remaining bytes when the number of them isn't an exact multiple of the size of each tuple. This can be as simple as specifying the use of `0` a as fill-value. – martineau Dec 12 '19 at 09:32
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/204110/discussion-between-chase-and-martineau). – Chase Dec 12 '19 at 09:42

1 Answers1

3

Here's a function that can be used to divide the values in a byte string up into tuples of a specified size. The byte string is first printed out as list of integers, followed by the list of tuples corresponding to that. Note how in the example the last two values of the final tuple when it's divided by 3 have been filled-in with zero since the byte string length (16) isn't a multiple of that. That doesn't happen when dividing it up into tuples of size 4 (so no fill-values were appended).

Also note that the grouper() function below is a slightly different implementation of the recipe by the same name in the itertools documentation.

from itertools import zip_longest

def grouper(n, iterable, fillvalue=None):
    "s -> (s0, s1...sn-1), (sn, sn+1...s2n-1), (s2n, s2n+1...s3n-1), ..."
    return zip_longest(*[iter(iterable)]*n, fillvalue=fillvalue)

aes_key = b'\x80in\xbe\x06b\x8f\x8fZ}l-\xb4j\xb5\x1f'

ints = list(aes_key)
print(ints)

tuples = list(grouper(3, aes_key, fillvalue=0))
print(tuples)

tuples = list(grouper(4, aes_key, fillvalue=0))
print(tuples)

Output:

[128, 105, 110, 190, 6, 98, 143, 143, 90, 125, 108, 45, 180, 106, 181, 31]
[(128, 105, 110), (190, 6, 98), (143, 143, 90), (125, 108, 45), (180, 106, 181), (31, 0, 0)]
[(128, 105, 110, 190), (6, 98, 143, 143), (90, 125, 108, 45), (180, 106, 181, 31)]

Since it appears you want to make an image out of this data, you likely will still need to format that data further depending on what the number of pixels there are in each row of the image.

You can convert a list of tuples back into a byte string like this:

# To convert a list of tuples back into a byte string.
from itertools import chain
print(bytes(chain.from_iterable(tuples)))

Output:

b'\x80in\xbe\x06b\x8f\x8fZ}l-\xb4j\xb5\x1f'

However this will only be the same as the original byte string if no fill values were added (as was the case using 4-tuples).

martineau
  • 119,623
  • 25
  • 170
  • 301
  • Thanks for the answer, I've a few questions however, since the aes_key is supposed to be randomized, will there be a time when the tuple can contain an integer value >255? – Chase Dec 12 '19 at 10:36
  • Also, what would be the way to go back to the string from the tuple? – Chase Dec 12 '19 at 10:37
  • I've tried converting the tuple back to the byte string using `bytes(key_tuple).decode(CODEC)` where key_tuple is the 0th index of `tuples` in your code. But I need to know which codec to use. 'utf-8' doesn't work – Chase Dec 12 '19 at 11:07
  • Going from the tuple back to the byte string is impossible because the fill values aren't part of the original data but there's currently no way to determine how many of them are being added. Unfortunately to make an image out of the data, each tuple need to be fully formed (the rows in an image file always contain a whole number of pixels). You could probably work around that by prefixing the byte string with its length. Unfortunately showing you how to do that as well as how to do the conversion the other way is getting too far beyond the scope of this question. – martineau Dec 12 '19 at 11:47
  • @martinneau oh but that's solvable, I can make sure the `aes_key` is always exactly 16 bytes long and I can also make sure each tuple contain exactly 4 integer values to avoid fill values. I just need to know how to get back to the byte string from this tuple (or list like the `ints` object you showed). specifically, what **codec** should I use to decode back to string? – Chase Dec 12 '19 at 11:53
  • I don't know of a codec that specifically does that — what makes you think there is one? It would be possible to do it manually, however. – martineau Dec 12 '19 at 11:55
  • Oh I just thought that since it's possible to turn the byte string into a list of integers using list(), then there must be a specific conversion table where each integer represent a byte. If there isn't any codec to do that, could you please tell me where I can find the equivalent byte for each integers (or vice versa) so I can manually create a function? – Chase Dec 12 '19 at 12:01
  • That not really called encoding…but regardless, it not quite as easy, but not too hard either. See update to my answer. – martineau Dec 12 '19 at 12:06