""" Das Modul stellt Funktionen und Prozeduren bereit, um Bytes in den Least-Significant-Bits von Pixeldaten-Bytes zu verstecken. """ def ppmDaten(s): """ liefert zu den Bytes s (bytes) einer ppm-Datei [Breite w (int), Höhe h (int), Farbtiefe mv (int), Liste der Pixeldaten (bytes)] """ # Formatbeschreibung: http://netpbm.sourceforge.net/doc/ppm.html # zur Kommentarbehandlung """ Before the whitespace character that delimits the raster, any characters from a "#" through the next carriage return or newline character, is a comment and is ignored. Note that this is rather unconventional, because a comment can actually be in the middle of what you might consider a token. Note also that this means if you have a comment right before the raster, the newline at the end of the comment is not sufficient to delimit the raster. """ def skipcomment(s,i): """ löscht in s (bytearray), wenn Byte Nummer i (int) gleich ord('#') ist, alle Bytes bis zum ersten Auftreten von ord('\r')==13 oder ord('\n')==10 einschließlich """ if s[i] == ord('#'): ac = i while not (s[i] in [10,13]): i = i + 1 ec = i # print(s[ac:ec+1]) # DEBUG del s[ac:ec+1] s = bytearray(s) # damit man Teile löschen kann # Phase 1 magic number i = 0 # i Byte-Nummer skipcomment(s,i) i = 1 skipcomment(s,i) if str(s[0:2],'ASCII') != 'P6': raise RuntimeError('Fehler in magic number') # Phase 2 whitespace ord('\t')==9, ord('\n')==10, ord('\r')==13, ord(' ')==32 i = 2 while s[i] in [9,10,13,32]: skipcomment(s,i) i = i + 1 # Phase 3 width a = i while not(s[i] in [9,10,13,32]): skipcomment(s,i) i = i + 1 e = i w = int(str(s[a:e],'ASCII')) # Phase 4 whitespace while s[i] in [9,10,13,32]: skipcomment(s,i) i = i + 1 # Phase 5 height a = i while not(s[i] in [9,10,13,32]): skipcomment(s,i) i = i + 1 e = i h = int(str(s[a:e],'ASCII')) # Phase 6 whitespace while s[i] in [9,10,13,32]: skipcomment(s,i) i = i + 1 # Phase 7 MaxVal a = i while not(s[i] in [9,10,13,32]): skipcomment(s,i) i = i + 1 e = i mv = int(str(s[a:e],'ASCII')) # Phase 8 single whitespace character if not (s[i] in [9,10,13,32]): raise RuntimeError('Fehler in single whitespace character') # Phase 9 raster d = s[i+1:] return [w,h,mv,d] def Bytes2Bits(a): """ liefert zum a (bytes) das zugehörige b (bytes), das die Einzelbits enthält """ b = bytes(0) n = len(a) for i in range(n): h = a[i] for j in range(8): b = b + bytes([(h&128)//128]) h = h*2 return b def Bits2Bytes(b): """ baut aus b (bytes) der Einzelbits a (bytes) der Bytes """ a = bytes(0) n = len(b)//8 for i in range(n): h = 128*b[i*8] + 64*b[i*8+1] + 32*b[i*8+2] + 16*b[i*8+3] + \ 8*b[i*8+4] + 4*b[i*8+5] + 2*b[i*8+6] + 1*b[i*8+7] a = a + bytes([h]) return a def encodeLSB(a,b): """ liefert c (bytearray) zurück, das aus a (bytes) entsteht, wenn das LSB jeweils entsprechend b (bytes) gewählt wird """ h = Bytes2Bits(b) n = len(h) c = bytearray(0) for i in range(n): c.append( ( a[i]&254 ) + h[i]) # Achtung + bindet stärker als & c = c + a[n:] return c def decodeLSB(a,n): """ liefert b (bytes) der Länge n (int) zurück, dessen Bytes aus den LSBs von a bestehen """ b = bytes(0) for i in range(n): h = 128*(a[i*8]%2) + 64*(a[i*8+1]%2) + 32*(a[i*8+2]%2) + 16*(a[i*8+3]%2) + \ 8*(a[i*8+4]%2) + 4*(a[i*8+5]%2) + 2*(a[i*8+6]%2) + 1*(a[i*8+7]%2) b = b + bytes([h]) return b def encodePPM(ppm1,daten,ppm2): """ erzeugt aus einer ppm-Datei ppm1 (str) und einer Datendatei daten (str) eine ppm-Datei ppm2 (str), die die Daten enthält """ # ppm-Datei zerlegen datei = open(ppm1,'rb') s = datei.read() datei.close() (b,h,ft,pdaten) = ppmDaten(s) # Pixel-Daten # Nutz-Daten einlesen datei = open(daten,'rb') ndaten = datei.read() # Nutz-Daten datei.close() # Nutz-Daten verbergen sdaten = encodeLSB(pdaten,ndaten) # stegano-Daten # neue ppm-Datei erzeugen header = bytes('P6\n'+str(b)+' '+str(h)+'\n'+str(ft)+'\n','ASCII') datei = open(ppm2,'wb') datei.write(header) datei.write(sdaten) datei.close() def decodePPM(ppm,daten,n): """ extrahiert aus einer ppm-Datei ppm (str) eine Datendatei daten (str) der Länge n (int) """ # ppm-Datei zerlegen datei = open(ppm,'rb') s = datei.read() datei.close() (b,h,ft,pdaten) = ppmDaten(s) # Pixel-Daten # Nutz-Daten extrahieren ndaten = decodeLSB(pdaten,n) # Nutz-Daten # daten-Datei erzeugen datei = open(daten,'wb') datei.write(ndaten) datei.close() def hidePPM(ppm1,daten,ppm2): """ erzeugt aus einer ppm-Datei ppm1 (str) und einer Datendatei daten (str) eine ppm-Datei ppm2 (str), die die Daten enthält, dabei sind die ersten 6 Bytes die Anzahl der Nutzdaten in ASCII-Ziffern """ # ppm-Datei zerlegen datei = open(ppm1,'rb') s = datei.read() datei.close() (b,h,ft,pdaten) = ppmDaten(s) # Pixel-Daten # Nutz-Daten einlesen datei = open(daten,'rb') ndaten = datei.read() # Nutz-Daten datei.close() # Nutz-Daten-Header erzeugen n = len(ndaten) # Anzahl der Nutzdaten head = str(n) while len(head) < 6: head = '0'+head header = bytes(head,'ASCII') ndaten = header + ndaten # Nutz-Daten verbergen sdaten = encodeLSB(pdaten,ndaten) # stegano-Daten # neue ppm-Datei erzeugen header = bytes('P6\n'+str(b)+' '+str(h)+'\n'+str(ft)+'\n','ASCII') datei = open(ppm2,'wb') datei.write(header) datei.write(sdaten) datei.close() def discoverPPM(ppm,daten): """ extrahiert aus einer ppm-Datei ppm (str) eine Datendatei daten (str), dabei wird erwartet, dass die ersten 6 Bytes der Daten die Groesse der Datendatei in ASCII-Ziffern angibt """ # ppm-Datei zerlegen datei = open(ppm,'rb') s = datei.read() datei.close() (b,h,ft,pdaten) = ppmDaten(s) # Pixel-Daten # Nutz-Daten-Header extrahieren und auswerten n = int(str(decodeLSB(pdaten,6),'ASCII')) # Nutz-Daten extrahieren ndaten = decodeLSB(pdaten,n+6)[6:] # erste 6 Bytes weglassen # daten-Datei erzeugen datei = open(daten,'wb') datei.write(ndaten) datei.close() if __name__ == "__main__": # ppmDaten s = b'P# seltsam\r6\n# CREATOR: GIMP PNM Filter Version 1.1\n6 1\n25#1234\r5\r\xf3\x08\x08\xf3\xac\x08\xec\xf3\x08\x08\xf3\x08\x08\x16\xf3\xf3\x08\xf3' pd = b'\xf3\x08\x08\xf3\xac\x08\xec\xf3\x08\x08\xf3\x08\x08\x16\xf3\xf3\x08\xf3' d = ppmDaten(s) if d == [6,1,255,pd]: print('ppmDaten ok') # Bytes2Bits, Bits2Bytes a = bytes([65,131,66]) b = Bytes2Bits(a) c = Bits2Bytes(b) if a == c: print('Bytes2Bits, Bits2Bytes ok') # encodeLSB, einfach a = bytes([0,0,0,0,0,0,0,0]) bitmuster = '01001101' b = encodeLSB(a,bytes([int(bitmuster,2)])) if b == bytes([0,1,0,0,1,1,0,1]): print('encodeLSB, einfach ok') # encodeLSB, decodeLSB import random a = bytearray([]) for i in range(2000): a.append(random.randint(0,255)) st = 'Hallö Welt!€€€' inhalt = bytes(st,'utf8') n = len(inhalt) en = encodeLSB(a,inhalt) de = decodeLSB(en,n) if st == str(de,'utf8'): print('encodeLSB, decodeLSB ok') # encodePPM encodePPM('spec5.ppm','abc.txt','spec6.ppm') decodePPM('spec6.ppm','abc1.txt',6) datei = open('abc1.txt','rb') dat = datei.read() datei.close() if dat == bytes('qwertz','ASCII'): print('encodePPM ok') # decodePPM decodePPM('staufine.ppm','daten0.txt',4) datei = open('daten0.txt','rb') dat = datei.read() datei.close() if dat == bytes('LK12','ASCII'): print('decodePPM ok') # hidePPM, discoverPPM hidePPM('staufine.ppm','daten1.txt','staufine2.ppm') discoverPPM('staufine2.ppm','daten2.txt') datei1 = open('daten1.txt','rb') dat1 = datei1.read() datei1.close() datei2 = open('daten2.txt','rb') dat2 = datei2.read() datei2.close() if dat1 == dat2: print('hidePPM, discoverPPM ok')