Crypto Art

.//`+++++++++++++++++++++++++++++++++++++// s
-+/.h+..................................-os s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h+                                   ss s
-+/                            :-.: +:oo-   s
  s                        .::::/osso::::. .+
`.y                        +yyyyhNNNmyyyy+ .+
 .o                    s s::::::::::::s.+ - +
-/y     .o             s ::::::::::::oss+::.+
`-y     .o             y            `+//:  .+


As with each of the articles, it's worth reading them in full, but this time there's a special offer waiting at the end!

Theoretical cryptography and Renaissance art history. Two of the most exciting topics ever known to mankind. And as if that were not enough, these two are now finally combined here in this article.

This work attempts to show how concepts of IT security can be used to make art. The approach has already been briefly mentioned here, but in the following, the possibilities of crypto art will be explained again in detail. For this, we will mainly use the technique of manipulating images at the byte level and then using the properties of the BMP file format to create a new image.


First, a few words about art history. My favorite epoch is the Renaissance. Therefore, I would like to briefly summarize this era here.

Before the Renaissance in Europe there was mainly the so-called icon painting. It was very symbolically charged. The figures were flat and did not correspond to the real perception. Their size and position in the picture was usually related to social status.

Giotto di Bondone made a break by suggesting a first form of perspective in his frescoes. He also placed Christ on a level with the other figures. In this way he became the forerunner of the radical change that was now in the offing and that was to characterize the Renaissance.

A little later, Massaccio invented central perspective. This was something completely new at the time. For the first time, painters were able to calculate perspective mathematically. The basis for this was a new, scientific way of looking at the world. His fresco must have had an incredible effect on the viewers, because the central perspective spread in no time.

The new way of looking at the world and at art also spread to Northern Europe. Van Eyck's sense of realistic painting, perfected to a high degree of detail, is visible in the example of the Famous Mirror, in which the entire scene is reflected.

The play with perspective and the characters, as well as their facial expressions, was pushed further and further. Botticelli combines in his Annunciation diverse motives in one. Both a reference back to one of the most common scenes of the time, as well as an extreme architectural perspective and a kind of landscape painting, up to almost exaggerated details in faces and robes.

This is topped only by Mantegna, whose lamentation over the Dead Christ, distorted in an extreme perspective, leaves the viewer at the foot of the bed. This was not only a new form of exhaustion of perspective possibilities, but also a previously quite outrageous representation of Christ himself.

And then... Then comes Michelangelo, the greatest troll in the history of art of all time. He simply gives a f*ck about everything and paints in the Vatican, the holiest place in the world for christians of this time, nothing but naked men's butts...

A meme about art history

art history meme


So far, this has nothing to do with crypto at all, but that's about to change because we will now encrypt the digital representations of Renaissance artworks.

Encryption ensures the confidentiality of data. This is usually done by encrypting a file with a password. Of course, a password that is easy to guess does not provide reliable protection. But cryptographic algorithms can also have weaknesses on their own. And that can lead to interesting artistic possibilities.

The following is a very basic introduction to some concepts of cryptography and an illustration of their weaknesses using an image example.



Hashing itself basically has nothing to do with encryption. Nevertheless, it comes up frequently in connection with it. This is due to the characteristics that a hash brings with it.

A hashing function is a mathematical calculation that produces a specific output with a fixed length from any input. If the input changes even minimally, the output changes completely. Furthermore, no conclusions about the original input can be drawn from a hash. However, one and the same input always generates the same hash.

These properties allow to use hashes for different checks. In the following code we see how a hash is generated from an image file.

md5sum cambrai.jpg
8c05b44e7276e3b10f7231cd318d0990 cambrai.jpg

8c05b44e7276e3b10f7231cd318d0990 is the hash of the file. If even a single bit is changed, the result is a completely different hash. This way it can also be ensured that the file has not been manipulated. It is the real original, which calculates only this particular hash.

However, a single hash does not yet look very interesting. Therefore, the following script builds a completely new image from the hashes of the bytes of the source file. The file is divided into blocks of 16 bytes each, these are hashed and then appended to each other again.

The result is similar to an encryption method shown later, but there is no decryption for this, because it is hashed not encrypted. The original data cannot be recontructed from the hashes. The only way to recover the file would be to guess each original bytes individually and compare them against the hash.

import sys
from itertools import zip_longest
import hashlib

data  = bytearray(open(sys.argv[1], 'rb').read())
head = data[:54]
body = data[54:]

a = len(body)
c = 0
hashed = b""
# print("This may take a while... (because it's probably the most inefficient way doing this shit)")
while body:
    tmp = body[:16]
    hashed += bytes.fromhex(hashlib.md5(tmp).hexdigest())
    body = body[16:]
    c += 1
    # prnt(f"{c} / {a}", end='                                 \r')

open("output.md5.bmp", "wb").write(head + hashed)
Cambrai icon painting, every 16 byte block is hashed to md5

hashed icon painting

Encoding --> RW5jb2Rpbmc=

First of all, encoding is not encryption, even if it sometimes looks like it. Encoding is a way of representing characters using another system. For example, the binary system is another way to represent a number that we usually know in the decimal system. If you assign a number to a letter, it can thus also be represented in binary.

"" is in binary "01101000 01100001 01100011 01101011 01101001 01101110 01100111 00101110 01100001 01110010 01110100". Even if you can't read it anymore and it looks somehow "encrypted", it is only transferred from one representation system to another. In the same way, it can be translated back based on these encoding rules. Encoding does not provide confidentiality as encryption does.

A well-known and often used encoding is base64. It is used to transfer data that contains bytes that cannot be represented in ASCII without encoding. It typically looks something like this:


But let's try it out in practice and create Crypto Art with it. For this we again apply the technique already mentioned and modify the bytes of a bmp file, restore the header and look at the result.

import sys
import base64

data = bytearray(open(sys.argv[1], 'rb').read())
head = data[:54]
body = data[54:]

encoded = base64.b64encode(body)
open("output.b64.bmp", 'wb').write(head + encoded)
kiss of judas base64 encoded

kiss of judas base64 encoded

Ceasar Cipher

Prnfne Pvcure

Ok, let's finally get to the first more or less real encryption. The principle is quite simple. It is a simple symmetric encryption method. As one of the simplest and least secure methods, it is mainly used today to illustrate basic principles of cryptology. During encryption, each letter of the plaintext is mapped to a ciphertext letter.

For decryption, the alphabet is rotated to the left by the same number of characters.

ceasar cipher

ceasar cipher

Like all monoalphabetic encryption methods, the shift cipher does not provide sufficient security against unauthorized decipherment and can be "cracked" very easily. The unequal distribution of letters in natural language is not hidden by this type of encryption, so that a frequency analysis reveals the action of a simple monoalphabetic substitution.

In the following script 130 is added to each byte, because only 13 had no visible effect. If the value is greater than 255, the rest starts again at 0. Thus each byte is "shifted" by the value. The overall structure of the image is still visible because of the "bad" encryption.

import sys

data = bytearray(open(sys.argv[1], 'rb').read())
head = data[:54]
body = data[54:]

rot = []
for i in body:
    if(i+130 < 255):

open("output.r13.bmp", 'wb').write(head + bytearray(rot))
holy trinity bits shifted by 130

holy trinity bits shifted by 130

01010010 01000101 01011000

01100001 01110010 01110100


Exclusive or / exclusive disjunction is a logical operation that is true if and only if its arguments differ (one is true, the other is false). This is also sometimes used for cryptography.

The encryption goes through the image byte by byte and applies the XOR method. The result is modified bytes, which at first glance looks like encryption.

It works like this:

XOR explained

XOR explained

But as can be seen in the following image, the structures are also clearly visible. In addition, XOR is very easy to decode again. You only need the ciphertext and the plaintext. If you apply XOR to these two again, you get the key.

import sys

def str_xor(data, key):
    for i in range(len(data)):
        data[i] ^= key[i % len(key)]
    return data

data  = bytearray(open(sys.argv[1], 'rb').read())
key = b"ART" # or other key/password

head = data[:54]
body = data[54:]

encoded = str_xor(body, key)
open("output.xor.bmp", "wb").write(head + encoded)
Arnolfini Portrait XORed

Arnolfini Portrait XORed

Block Cipher - Electronic Codebook

Now things are moving more in the direction of encryption. The Advanced Encryption Standard (AES) is basically considered a secure encryption algorithm. However, there are different modes in which it can be operated.

The simplest and most insecure mode is called: Electronic codebook (ECB). Here, the plaintext is divided into equal-sized blocks, each of which is encrypted with the key.

Even if the AES algorithm is good, this particular mode has weaknesses. This is because patterns can be detected when there are several blocks in the plaintext that are the same. The same blocks are encrypted exact the same, which allows conclusions to be drawn about the plaintext.

AES ECB explained

AES ECB explained

Let's see how it looks like.

import sys
from Crypto.Cipher import AES

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS).encode()

class AESCipher:
def __init__( self, key ):
self.key = key

def encrypt( self, raw ):
raw = pad(raw)
cipher = self.key, AES.MODE_ECB)
return cipher.encrypt( raw ) 

nerv = AESCipher("Holy Trinity1234")

data = bytearray(open(sys.argv[1], 'rb').read())
head = data[:54]
body = data[54:]

crypt = nerv.encrypt(bytes(body))
open("output.ecb.bmp", 'wb').write(head + crypt)
Cestello Annunciation AES ECB encrypted

Cestello Annunciation ECB

Block Cipher - Chaining

To remedy the weakness of the ECB mode, the cipher block chaining (CBC) mode was developed. In this mode, the individual encrypted blocks are chained together in such a way that patterns are no longer recognizable. For this purpose, a so-called initialization vector is used at the beginning and each following block is linked to the previous one by an XOR operation.

Cipher block chaining

block chaining

Also from the image you can see that it has created a complete encryption. This is how it should look when it is secure.

mantegna encrypted

The Lamentation over the Dead Christ full encryption

But encryption alone is not enough to protect data from being manipulated. It is interesting to note that even encrypted data can be modified, despite encryption. This means that when the data is decrypted, the result is different from the original plaintext. A classic attack that exploits this is the so-called bit flipping attack. An encrypted message is manipulated in such a way that other data arrives at the recipient than intended. For example, if the right bytes are swapped in the right block, the original "login=false" can become "login=true" after decryption.

This is not so easy to show with images, but nevertheless we can observe the effect when bytes are exchanged in the encrypted image. Each glitch in the image is a byte, which was decrypted "wrongly".

import sys
import base64
from Crypto.Cipher import AES
from Crypto import Random

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS).encode()
unpad = lambda s : s[:-ord(s[len(s)-1:])]

class AESCipher:
    def __init__( self, key ):
        self.key = key

    def encrypt( self, raw ):
        raw = pad(raw)
        iv = AES.block_size )
        cipher = self.key, AES.MODE_CBC, iv )
        return iv + cipher.encrypt( raw ) 

    def decrypt( self, enc ):
        iv = enc[:16]
        cipher =, AES.MODE_CBC, iv )
        return unpad(cipher.decrypt( enc[16:] ))

nerv = AESCipher("andrea mantegna!")

data = bytearray(open(sys.argv[1], 'rb').read())

head = data[:54]
body = data[54:]

if(sys.argv[2] == "enc"):
    crypt = nerv.encrypt(bytes(body))
    open("aescbc.enc.bmp", 'wb').write(head + crypt)
elif(sys.argv[2] == "dec"):
    crypt = nerv.decrypt(bytes(body))
    open("output.cbc.bmp", 'wb').write(head + crypt)

################# BASH ###########################
# python3 ~/cryptoart/mantegna.bmp enc
# sed -i 's/\xAC/\xAB/g' aescbc.enc.bmp
# python3 aescbc.enc.bmp dec
mantegna bitfliped and decrypted

bitflip creating glitches after decryption


Now, finally my favorite encryption method developed by the awesome Ange Albertini: Angecryption.

Ange Albertini has managed to transform an image by encrypting it into another image.

And to return to Michelangelo going crazy in Vatican mentioned at the beginning, we will turn an image of one of his butt paintings into an image of a super tiny dick USING CRYPTO!!!11


python butt.png dick.png lol.png "ange albertini12" aes

cat > <<EOF
from Crypto.Cipher import AES

algo ='ange albertini12', AES.MODE_CBC, '\x93ted\xfb\xe6\xd4l4d\r?\xcd`\xab\xdf')

with open('lol.png', "rb") as f:
d =

d = algo.encrypt(d)

with open('', "wb") as f:


encrypt some special paintings

encrypt some special paintings

Buy Crypto-Crypto-Art

To all you art collectors: There is now a once-in-a-lifetime chance to buy an original Crypto-Artwork and support my work.

You will get one unique, original and signed image of an artwork from, encrypted with a password of your choice.

And now the best part!

It will be stored and displayed with your owner information encrypted in the

Encrypted Gallery

When sold, a hash is generated from the name of the buyer, the price and date of sale, as well as the resulting Crypto-Artwork. Following this structure.

        "owner": "",
        "date": "",
        "price": "",
        "name_artwork": "",
        "hash_artwork": ""

These infos will be published here on this website, with a screenshot of the sold image, the hash and the corresponding publickeys.

To verify the authenticity, the hash is additionally signed with the sellers privatekey.

This way everyone can verify the correct owner of a Crypto-Artwork.

The original image is securely encrypted again and sent exclusively to the new owner!

The particular feature is: the artwork is not just a boring gif with some metadata in a blockchain, but

The artwork itself is encrypted and additionally cryptographically signed.

This way the art is bulletproof millitary grade double encrypted!
This will make them encrypted Crypto-Art!
Or better call it Crypto-Crypto-Art.
100% Secure, Unhackable, Encrypted and Crypto-Signed Crypto-Crypto-Art!!!

If you buy one you will also support my next project: turning the NFT market into a forkbomb!

For more Information visit the Encrypted Gallery.

Below are the hashes and signatures of the of the original pieces from this article.


hashes were generated like this: sha512sum >

hashes were signed like this: gpg -s

signed hash
d292cc1f273fc58ece1acb59288f69922e91db76b6467a3a4a204d434eb6793c8ff93fa2a6b8c46d30843d59505b0445ab35d0a73977a547325ede153867e5d9 output.md5.bmp

signed hash
398f9b2b24e6077fc70474dc0755209a2a954e3dd5dcf6a315eecb1661f917f5ca4307f138a48af963603b98739404d1ba341989aa4af174f9b02319e7de4d54 output.b64.bmp

signed hash
50a86122e45d5a22b1261f34bc2bfd35fb36191dc63bcd6322a7d7b9d1d4cc6ca58e05598c0c777eb01a550e88e8b6492caaac8af857e4da7f0a74be577bf701 output.r13.bmp

signed hash
db37b683ef2fc0b2988222de17bb6cc4b2f29461267198d944642eeb093b8ebfd808899f4c468f18b179c4f071460a2a8c2d1d6d7a843d9427a71fedbbb27b23 output.xor.bmp

signed hash
ca9d0b23b1b4782a129e558f15592b1128e641468e39df652f8a3f1b7ea583ebabd63a5e49b9b883dffdc0e47dc9148d5dd17b7be55eab78c471f31e7d1e78c9 output.ecb.bmp

signed hash
46aaa17324c6a835bdbd82270f44333230ea5658bf160b227eb0a22d4be0ecef304fd5e4e2639156d7ebf03af6cec71582a454de38e3ee74ece14dc0012a72ec output.cbc.bmp

signed hash