Code:
#!/usr/bin/env python3
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from PIL import Image
from zlib import crc32
from random import getrandbits
from math import ceil
CRYPTO_KEY = 'eaff2e97ad4449104ef2dd479a212c39c270a80d87aa07ab21606a7ff8604032'
CRYPTO_BLOCK_SIZE = algorithms.AES.block_size // 8
CRYPTO_PASSES = 25000 # decryption takes about 5-7 seconds on an i5 2500k @ 4.5GHz
IMG_WIDTH = 1024
IMG_BPP = 32
def main() -> None:
text = '''
<h3><a id="scrape_this" href="https://www.wjunction.com/threads/ddoc-distributed-denial-of-copyright-how-to-fight-an-armada-of-dmca-bots.228164/">Distributed Denial of Copyright</a></h3>
<p><iframe id="scrape_this_too" src="https://openload.co/embed/mQhErnEyNCs/" scrolling="no" frameborder="0" width="700" height="430" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe></p>
'''
img = ddoc_it(text)
img.save('test.png', 'PNG', quality=100, optimize=False)
img.close()
img = Image.open('test.png')
print(unddoc_it(img))
img.close()
def ddoc_it(text: str) -> Image.Image:
key = bytes.fromhex(CRYPTO_KEY)
iv = bytes(getrandbits(8) for _ in range(16))
payload = encrypt(text.encode('utf-8'), key, iv, CRYPTO_PASSES)
header_and_payload = [
b'DDoC', # Header: char[4] SIGNATURE
0x0001.to_bytes(2, 'little'), # Header: uint16 VERSION
key, # Header: uint8[32] KEY
iv, # Header: uint8[16] IV
CRYPTO_PASSES.to_bytes(4, 'little'), # Header: uint32 PASSES
len(payload).to_bytes(4, 'little'), # Header: uint32 PAYLOAD_SIZE
crc32(payload).to_bytes(4, 'little'), # Header: uint32 PAYLOAD_CHECKSUM
payload,
]
data = b''.join(header_and_payload)
pixels_required = ceil(len(data) / (IMG_BPP // 8))
height = max(1, ceil(pixels_required / IMG_WIDTH))
data = data.ljust(height * IMG_WIDTH * (IMG_BPP // 8), b'\x00')
img = Image.frombytes('RGBA', (IMG_WIDTH, height), data)
return img
def unddoc_it(img: Image.Image) -> str:
data = img.tobytes()
header_signature = data[0:4]
header_version = int.from_bytes(data[4:6], 'little')
header_key = data[6:38]
header_iv = data[38:54]
header_passes = int.from_bytes(data[54:58], 'little')
header_payload_size = int.from_bytes(data[58:62], 'little')
header_payload_checksum = int.from_bytes(data[62:66], 'little')
payload = data[66:(66 + header_payload_size)]
checksum = crc32(payload)
assert header_signature == b'DDoC'
assert header_version <= 0x0001
assert header_payload_checksum == checksum
text = decrypt(payload, header_key, header_iv, header_passes).decode('utf-8')
return text
def encrypt(data: bytes, key: bytes, iv: bytes, passes: int) -> bytes:
backend = default_backend()
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend)
for _ in range(max(1, passes)):
encryptor = cipher.encryptor()
data = encryptor.update(pad(data)) + encryptor.finalize()
return data
def decrypt(data: bytes, key: bytes, iv: bytes, passes: int) -> bytes:
backend = default_backend()
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend)
for i in range(max(1, passes)):
decryptor = cipher.decryptor()
data = decryptor.update(unpad(data) if i > 0 else data) + decryptor.finalize()
return unpad(data)
def pad(b: bytes) -> bytes:
length = len(b)
padding = CRYPTO_BLOCK_SIZE - (length % CRYPTO_BLOCK_SIZE)
return b.ljust(length + padding, bytes([padding]))
def unpad(b: bytes) -> bytes:
return b[:-ord(b[len(b) - 1:])]
if __name__ == '__main__':
main()
To use the resulting image on a web page view the source of this demo page: