-1

I am writing a script to encrypt and decrypt an image in python3 using PIL. Here I am converting the image into a numpy array and then multiplying every element of the array with 10. Now I noticed that the default function in PIL fromarray() is converting every element of the array to the mod of 256 if its larger than the 255, so when I am trying to retrieve the original value of the matrix I'm not getting the original one. For example, if the original value is 40 then its 10 times is 400 so the fromarray() is making it as 400 mod 256, which will give 144. Now if I add 256 to 144 I will have 400 and then divided by 10 will give me 40. But if the value is 54 then 10times is 540 and 540 mod 256 is 28. Now to get back the original value I need to add 256 two times which will give me 540. 540 isn't the only number which will give me 28 when I will mod it with 256. So I will never know when to add 256 one time and when two times or more. Is there any way I can make it stop of replacing every element of the matrix with its mod of 256?

from PIL import Image
from numpy import * 
from pylab import * 

#encryption

img1 = (Image.open('image.jpeg').convert('L')) 
img1.show() #displaying the image

img = array(Image.open('image.jpeg').convert('L'))
a,b = img.shape
print(img)
print((a,b))
tup = a,b

for i in range (0, tup[0]):
   for j in range (0, tup[1]):
       img[i][j]= img[i][j]*10 #converting every element of the original array to its 10times

print(img)
imgOut = Image.fromarray(img)
imgOut.show()
imgOut.save('img.jpeg')

#decryption

img2 = (Image.open('img.jpeg'))
img2.show()

img3 = array(Image.open('img.jpeg'))
print(img3)
a1,b1 = img3.shape
print((a1,b1))
tup1 = a1,b1

for i1 in range (0, tup1[0]):
   for j1 in range (0, tup1[1]):
       img3[i1][j1]= ((img3[i1][j1])/10) #reverse of encryption
print(img3)
imgOut1 = Image.fromarray(img3)
imgOut1.show()

part of the original matrix before multiplying with 10 : [41 42 45 ... 47 41 33]

[41 43 45 ... 44 38 30]

[41 42 46 ... 41 36 30]

[43 43 44 ... 56 56 55]

[45 44 45 ... 55 55 54]

[46 46 46 ... 53 54 54]

part of the matrix after multiplying with 10 : [[154 164 194 ... 214 154 74]

[154 174 194 ... 184 124 44]

[154 164 204 ... 154 104 44]

[174 174 184 ... 48 48 38]

[194 184 194 ... 38 38 28]

[204 204 204 ... 18 28 28]

part of the expected matrix after dividing by 10 : [41 42 45 ... 47 41 33]

[41 43 45 ... 44 38 30]

[41 42 46 ... 41 36 30]

[43 43 44 ... 56 56 55]

[45 44 45 ... 55 55 54]

[46 46 46 ... 53 54 54]

part of th output the script is providing: [[41 41 45 ... 48 40 33]

[41 43 44 ... 44 37 31]

[41 41 48 ... 41 35 30]

[44 42 43 ... 30 30 29]

[44 42 45 ... 29 29 29]

[45 47 44 ... 28 28 28]]

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
Sandipan
  • 683
  • 8
  • 25
  • Share your code plz – Alderven Mar 18 '19 at 12:29
  • Done, any help will be greatly appreciated. – Sandipan Mar 18 '19 at 12:35
  • Can you share expected and your actual result? – Alderven Mar 18 '19 at 12:37
  • done. Hope it will help. – Sandipan Mar 18 '19 at 12:48
  • 1
    What do you expect should happen to a value >255 saved to a JPEG file that support only values in the range [0,255]? – Cris Luengo Mar 18 '19 at 12:56
  • i know that and i just want the greyscale value of the image and then i will apply RSA algo or any other algo, but to do that i need to have a matrix which will not make mod of the elements automatically. – Sandipan Mar 18 '19 at 13:09
  • 1
    The RSA encoding will change (increase a lot, most likely) the size of your data. You can store it anyway byte by byte, so that the problem that you are having here does not even arise. Since the image will have a different size, do not forget to store the original image size or other metadata needed to restore the image. – Wyrzutek Mar 18 '19 at 13:12

3 Answers3

3

There are several problems with what you're trying to do here.

PIL images are either 8 bit per channel or 16 bit per channel (to the best of my knowledge). When you load a JPEG, it's loaded as 8 bits per channel, so the underlying data type is an unsigned 8-bit integer, i.e. range 0..255. Operations that would overflow or underflow this range wrap, which looks like the modulus behavior you're seeing.

You could convert the 8-bit PIL image to a floating point numpy array with np.array(img).astype('float32') and then normalize this to 0..1 by dividing with 255.

At this point you have non-quantized floating point numbers you can freely mangle however you wish.

However, then you still need to save the resulting image, at which point you again have a format problem. I believe TIFFs and some HDR image formats support floating point data, but if you want something that is widely readable, you'd likely go for PNG or JPEG.

For an encryption use case, JPEGs are not a good choice, as they're always inherently lossy, and you will, more likely than not, not get the same data back.

PNGs can be 8 or 16 bits per channel, but still, you'd have the problem of having to compress a basically infinite "dynamic range" of pixels (let's say you'd multiplied everything by a thousand!) into 0..255 or 0..65535.

An obvious way to do this is to find the maximum value in the image (np.max(...)), divide everything by it (so now you're back to 0..1), then multiply with the maximum value of the image data format... so with a simple multiplication "cipher" as you'd described, you'd essentially get the same image back.

Another way would be to clip the infinite range at the allowed values, i.e. everything below zero is zero, everything above it is, say, 65535. That'd be a lossy operation though, and you'd have no way of getting the unclipped values back.

AKX
  • 152,115
  • 15
  • 115
  • 172
2

First of all, PIL only supports 8-bit per channel images - although Pillow (the PIL fork) supports many more formats including higher bit-depths. The JPEG format is defined as only 8-bit per channel.

Calling Image.open() on a JPEG in PIL will therefore return an 8-bit array, so any operations on individual pixels will be performed as equivalent to uint8_t arithmetic in the backing representation. Since the maximum value in a uint8_t value is 256, all your arithmetic is necessarily modulo 256.

If you want to avoid this, you'll need to convert the representation to a higher bit-depth, such as 16bpp or 32bpp. You can do this with the NumPy code, such as:

img16 = np.array(img, dtype=np.uint16)
# or
img32 = np.array(img, dtype=np.uint32)

That will give you the extended precision that you desire.

However - your code example shows that you are trying to encryption and decrypt the image data. In that case, you do want to use modulo arithmetic! You just need to do some more research on actual encryption algorithms.

gavinb
  • 19,278
  • 3
  • 45
  • 60
  • yes, actually my actual project is to encrypt an image using rsa algo. I was just trying this so that I can write the actual project effectively and then i ran into this problem. – Sandipan Mar 18 '19 at 13:03
  • 2
    Array of those types cannot be saved into jpeg directly. You can improve your answer by providing the code to store this data into an RGB image and retrieve the data back. – Wyrzutek Mar 18 '19 at 13:04
1

As none of the answers have helped me that much and I have solved the problem I would like to give an answer hoping one day it will help someone. Here the keys are (3, 25777) and (16971,25777). The working code is as follows:

from PIL import Image
import numpy as np 

#encryption
img1 = (Image.open('image.jpeg').convert('L')) 
img1.show()

img = array((Image.open('image.jpeg').convert('L'))) 
img16 = np.array(img, dtype=np.uint32)
a,b = img.shape
print('\n\nOriginal image: ')
print(img16)
print((a,b))
tup = a,b

for i in range (0, tup[0]):
    for j in range (0, tup[1]):
        x = img16[i][j] 
        x = (pow(x,3)%25777)
        img16[i][j] = x
print('\n\nEncrypted image: ')
print(img16)
imgOut = Image.fromarray(img16)
imgOut.show()

#decryption

img3_16 = img16
img3_16 = np.array(img, dtype=np.uint32)
print('\n\nEncrypted image: ')
print(img3_16)
a1,b1 = img3_16.shape
print((a1,b1))
tup1 = a1,b1

for i1 in range (0, tup1[0]):
     for j1 in range (0, tup1[1]):
         x1 = img3_16[i1][j1] 
         x1 = (pow(x,16971)%25777)  
         img3_16[i][j] = x1
print('\n\nDecrypted image: ')
print(img3_16)
imgOut1 = Image.fromarray(img3_16)y
imgOut1.show()

Feel free to point out the faults. Thank you.

Sandipan
  • 683
  • 8
  • 25