Text in random image encoder: is it flawed? I have created a small cryptographic programme: 


You input your text
A random image is generated
Random coordinate pairs in number equal to the chars of the message are generated.
The red component of the points at the coordinates given above is modified to become equal to the ASCII value of the letter.
The text can now be read only knowing which points actually encode the letters and which other points are just noise.


Can you tell me any flaws in the approach that would make my message crackable (having only the image and not knowing the key used)?

If you are wondering about the implementation details, the actual programme is provided down below:

from __future__ import print_function
from PIL import Image
import random

SIZE = 500
MESSAGE = "Boh i don't know what I know to be knowing"
RELEVANT_POSITIONS = [(random.randint(0,SIZE),random.randint(0,SIZE)) for _ in range(len(MESSAGE))]
FILENAME = "Crypted.png"

IMAGE = Image.new("RGB", (SIZE,SIZE))


def from_letter_to_colour(letter):
    value = ord(letter)
    return (value,random.randint(0,256),random.randint(0,256))

def from_colour_to_letter(colour):
    return chr(colour[0])

def draw(position,colour):
    IMAGE.putpixel(position,colour)

def random_colour():
    return (random.randint(1,256),random.randint(1,256),random.randint(1,256))

def all_positions():
    return ( (x,y) for x in range(SIZE) for y in range(SIZE) )

def get_pixels(image_name):
    image = Image.open(image_name)
    pixels = image.load()
    return pixels

def create_random_image():
    for position in all_positions():
        draw(position,random_colour())

def encode_message(message):
    create_random_image()
    for index,position in enumerate(RELEVANT_POSITIONS):
        try:
            draw(position,from_letter_to_colour(message[index]))
        except IndexError:
            break
    IMAGE.save(FILENAME)

def test_encode_decode():
    for char in ( chr(i) for i in range(1,128)):
        assert from_colour_to_letter(from_letter_to_colour(char)) == char

def decode_message():
    message = []
    pixels = get_pixels(FILENAME)
    for position in RELEVANT_POSITIONS:
        x,y = position
        message.append(from_colour_to_letter(pixels[x,y]))
    return ''.join(message)

def main():
    print("The key is: {}".format(RELEVANT_POSITIONS))
    encode_message(MESSAGE)
    print(decode_message())

if __name__ == "__main__":
    test_encode_decode()
    main()
This is flawed:

Cracking: The format leaks information on how often specific characters occur.
For instance, if input message contains 6 o letters, there is likely much more 111 values than the most other values present. Such a small biases are sufficient for cryptoanalysis to break the message in many cases.

Also, random.randint does not return random number suitable for cryptographic purposes, it means the scheme can be possibly broken. (See https://docs.python.org/2/library/random.html for guidance, which tells the function shall not be used for cryptographic purposes). In current versions of Python, the generator is Mersenne Twister, which is easy to reverse (break). (See http://b10l.com/reversing-the-mersenne-twister-rng-temper-function/). Long story short, an attacker can easily use the produced image to find the original seed of the random number generator and thus, be able to generate the key. 

The system is not very space efficient: there is large overhead, the produced message is much larger than the input. (The technique primarily sounds more like steganography instead of cryptography.) Also, the key is longer than the message, and variable length.