סטגנוגרפיה ב-GIF

מבוא

Приветствую.
לא מזמן, כשלמדתי באוניברסיטה, הייתה לי עבודת קורס בנושא "שיטות תוכנה לאבטחת מידע". המטלה דרשה ליצור תוכנה שתטמיע הודעה בקבצי GIF. החלטתי לעשות זאת ב-Java.

במאמר זה אתאר כמה נקודות תיאורטיות, וכן כיצד נוצרה תוכנית קטנה זו.

החלק התיאורטי

פורמט GIF

GIF (פורמט חילופי גרפים) הוא פורמט לאחסון תמונות גרפיות, המסוגל לאחסן נתונים דחוסים ללא אובדן איכות בפורמט של עד 256 צבעים. פורמט זה פותח בשנת 1987 (GIF87a) על ידי CompuServe להעברת תמונות רסטר ברשתות. בשנת 1989 שונה הפורמט (GIF89a), נוספה תמיכה בשקיפות ואנימציה.

לקבצי GIF יש מבנה בלוקים. לבלוקים אלה תמיד יש אורך קבוע (או שזה תלוי בדגלים מסוימים), כך שכמעט בלתי אפשרי לטעות באיזה בלוק נמצא. המבנה של תמונת ה-GIF הפשוטה ביותר שאינה מונפשת בפורמט GIF89a:

סטגנוגרפיה ב-GIF

מכל הבלוקים במבנה, במקרה זה נתעניין בבלוק הצבעים הגלובלי ובפרמטרים האחראים על הצבעים:

  • CT — נוכחות של לוח צבעים גלובלי. אם דגל זה מוגדר, אזי לוח הצבעים הגלובלי אמור להתחיל מיד לאחר מתאר המסך הלוגי.
  • Size — גודל פלטת הצבעים ומספר הצבעים בתמונה. ערכי פרמטר זה:

מידה
מספר הצבעים
גודל פלטת הצבעים, בתים

7
256
768

6
128
384

5
64
192

4
32
96

3
16
48

2
8
24

1
4
12

0
2
6

שיטות הצפנה

השיטות הבאות ישמשו להצפנת הודעות בקבצי תמונה:

  • שיטת LSB (ביטים פחות משמעותיים)
  • שיטת תוספת פלטה

שיטת LSB — שיטה נפוצה של סטגנוגרפיה. היא מורכבת מהחלפת הביטים המשמעותיים האחרונים במכולה (במקרה שלנו, הבייטים של הפלטה הגלובלית) בביטים של ההודעה הנסתרת.

התוכנית תשתמש בשני הביטים האחרונים בצבעי הצבעים הגלובליים בשיטה זו. משמעות הדבר היא שעבור תמונה של 24 סיביות, שבה צבע הצבעים הוא שלושה בייטים עבור אדום, כחול וירוק, לאחר שההודעה מוטמעת בתוכה, כל רכיב צבע ישתנה בדרגה מקסימלית של 3/255. שינוי כזה, ראשית, יהיה בלתי מורגש או קשה להבחין בו עבור העין האנושית, ושנית, לא יהיה ניתן להבחנה בו בהתקני פלט מידע באיכות נמוכה.

כמות המידע תהיה תלויה ישירות בגודל פלטת הצבעים של התמונה. מכיוון שהגודל המרבי של פלטת הצבעים הוא 256 צבעים, ואם שני ביטים של ההודעה נכתבים בכל רכיב צבע, האורך המרבי של ההודעה (כאשר פלטת הצבעים המרבית נמצאת בתמונה) הוא 192 בתים. לאחר שההודעה מוטמעת בתמונה, גודל הקובץ אינו משתנה.

שיטת הרחבת פלטת הצבעים, אשר עובד רק עבור מבנה GIF. זה יהיה יעיל ביותר בתמונות עם גודל פלטת צבעים קטן. מהותו היא שהוא מגדיל את גודל פלטת הצבעים, ובכך מספק מקום נוסף להקלטת הבייטים הדרושים במקום בייט הצבעים. בהתחשב בכך שגודל פלטת הצבעים המינימלי הוא 2 צבעים (6 בייט), הגודל המקסימלי של ההודעה המוטמעת יכול להיות 256×3–6=762 בייט. החיסרון הוא אבטחה קריפטוגרפית נמוכה; ניתן לקרוא את ההודעה המוטמעת באמצעות כל עורך טקסט אם ההודעה לא הוצפנה בנוסף.

חלק מעשי

עיצוב תוכנית

כל הכלים הדרושים ליישום אלגוריתמי הצפנה ופענוח ייכללו בחבילה. com.tsarik.steganographyחבילה זו כוללת את הממשק Encryptor עם שיטות encrypt и decrypt, כיתה Binary, המספק את היכולת לעבוד עם מערכי סיביות, כמו גם עם מחלקות חריגים UnableToEncryptException и UnableToDecryptException, אשר יש להשתמש בו בשיטות ממשק Encryptor במקרה של שגיאות קידוד ופענוח בהתאמה.

חבילת תוכנית בסיסית com.tsarik.programs.gifed יכלול מחלקת תוכנית ניתנת להרצה עם מתודה סטטית main, המאפשרת את הפעלת התוכנית; מחלקה שמאחסנת פרמטרים של התוכנית; ואורזת עם מחלקות אחרות.

יישום האלגוריתמים עצמם יוצג בחבילה com.tsarik.programs.gifed.gif שיעורים GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethodשתי המחלקות הללו יממשו את הממשק Encryptor.

בהתבסס על מבנה פורמט ה-GIF, ניתן ליצור אלגוריתם כללי להטמעת הודעה בפלטת תמונות:

סטגנוגרפיה ב-GIF

כדי לקבוע את נוכחותה של הודעה בתמונה, יש צורך להוסיף רצף סיביות מסוים לתחילת ההודעה, אותו המפענח קורא תחילה ובודק את נכונותו. אם הוא אינו תואם, נחשב שאין הודעה נסתרת בתמונה. לאחר מכן, יש לציין את אורך ההודעה. לאחר מכן, את טקסט ההודעה עצמה.

דיאגרמת מחלקה של האפליקציה כולה:

סטגנוגרפיה ב-GIF

יישום התוכנית

ניתן לחלק את יישום התוכנית כולה לשני מרכיבים: יישום שיטות הצפנה ופענוח של הממשק Encryptor, בכיתות GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod, ויישום ממשק המשתמש.

בואו נבחן את הכיתה GIFEncryptorByLSBMethod.

סטגנוגרפיה ב-GIF

שדות firstLSBit и secondLSBit מכיל את מספרי הביטים של כל בייט בתמונה שאליו יש לכתוב את ההודעה וממנו יש לקרוא אותה. שדה checkSequence שומר רצף סיביות בקרה כדי להבטיח זיהוי של ההודעה המוטמעת. שיטה סטטית getEncryptingFileParameters מחזירה את הפרמטרים של הקובץ שצוין ואת המאפיינים של ההודעה הפוטנציאלית.

אלגוריתם של השיטה encrypt כיתה GIFEncryptorByLSBMethod:

סטגנוגרפיה ב-GIF

והקוד שלו:

@Override
public void encrypt(File in, File out, String text) throws UnableToEncodeException, NullPointerException, IOException {
	if (in == null) {
		throw new NullPointerException("Input file is null");
	}
	if (out == null) {
		throw new NullPointerException("Output file is null");
	}
	if (text == null) {
		throw new NullPointerException("Text is null");
	}
	
	// read bytes from input file
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// check format
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToEncodeException("Input file has wrong GIF format");
	}
	
	// read palette size property from first three bits in the 10-th byte from the file
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// calculate color count and possible message length
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2;// one byte for check and one byte for message length
	
	if (possibleTextLength < text.length()) {
		throw new UnableToEncodeException("Text is too big");
	}
	
	int n = 13;
	
	// write check sequence
	for (int i = 0; i < checkSequence.length/2; i++) {
		byte[] ba = Binary.toBitArray(bytes[n]);
		ba[firstLSBit] = checkSequence[2*i];
		ba[secondLSBit] = checkSequence[2*i+1];
		bytes[n] = Binary.toByte(ba);
		n++;
	}
	
	// write text length
	byte[] cl = Binary.toBitArray((byte)text.length());
	for (int i = 0; i < cl.length/2; i++) {
		byte[] ba = Binary.toBitArray(bytes[n]);
		ba[firstLSBit] = cl[2*i];
		ba[secondLSBit] = cl[2*i+1];
		bytes[n] = Binary.toByte(ba);
		n++;
	}
	
	// write message
	byte[] textBytes = text.getBytes();
	for (int i = 0; i < textBytes.length; i++) {
		byte[] c = Binary.toBitArray(textBytes[i]);
		for (int ci = 0; ci < c.length/2; ci++) {
			byte[] ba = Binary.toBitArray(bytes[n]);
			ba[firstLSBit] = c[2*ci];
			ba[secondLSBit] = c[2*ci+1];
			bytes[n] = Binary.toByte(ba);
			n++;
		}
	}
	
	// write output file
	OutputStream os = new FileOutputStream(out);
	os.write(bytes);
	os.close();
}

אלגוריתם וקוד מקור של השיטה decrypt כיתה GIFEncryptorByLSBMethod:

סטגנוגרפיה ב-GIF

@Override
public String decrypt(File in) throws UnableToDecodeException, NullPointerException, IOException {
	if (in == null) {
		throw new NullPointerException("Input file is null");
	}
	
	// read bytes from input file
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// check format
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToDecodeException("Input file has wrong GIF format");
	}
	
	// read palette size property from first three bits in the 10-th byte from the file
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// calculate color count and possible message length
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2;	// one byte for check and one byte for message length
	
	int n = 13;
	
	// read check sequence
	byte[] csBits = new byte[checkSequence.length];
	for (int i = 0; i < 4; i++) {
		byte[] ba = Binary.toBitArray(bytes[n]);
		csBits[2*i] = ba[firstLSBit];
		csBits[2*i+1] = ba[secondLSBit];
		n++;
	}
	byte cs = Binary.toByte(csBits);
	
	if (cs != Binary.toByte(checkSequence)) {
		throw new UnableToDecodeException("There is no encrypted message in the image (Check sequence is incorrect)");
	}
	
	// read text length
	byte[] cl = new byte[8];
	for (int i = 0; i < 4; i++) {
		byte[] ba = Binary.toBitArray(bytes[n]);
		cl[2*i] = ba[firstLSBit];
		cl[2*i+1] = ba[secondLSBit];
		n++;
	}
	byte textLength = Binary.toByte(cl);
	
	if (textLength < 0) {
		throw new UnableToDecodeException("Decoded text length is less than 0");
	}
	if (possibleTextLength < textLength) {
		throw new UnableToDecodeException("There is no messages (Decoded message length (" + textLength + ") is less than Possible message length (" + possibleTextLength + "))");
	}
	
	// read text bits and make text bytes
	byte[] bt = new byte[textLength];
	for (int i = 0; i < bt.length; i++) {
		byte[] bc = new byte[8];
		for (int bci = 0; bci < bc.length/2; bci++) {
			byte[] ba = Binary.toBitArray(bytes[n]);
			bc[2*bci] = ba[firstLSBit];
			bc[2*bci+1] = ba[secondLSBit];
			n++;
		}
		bt[i] = Binary.toByte(bc);
	}
	
	return new String(bt);
}

יישום מחלקה GIFEncryptorByPaletteExtensionMethod יהיה דומה, רק שיטת שמירת/קריאת המידע שונה.

בכיתה MainFrame שיטות העטיפה מתוארות: encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor), עיבוד תוצאות של שיטות ממשק Encryptor ואינטראקציה עם המשתמש, כלומר פתיחת תיבת דו-שיח לבחירת קבצים, הצגת הודעות שגיאה וכו'; כמו גם שיטות אחרות: openImage(), המאפשר למשתמש לבחור תמונה, exit(), אשר יוצא מהיישום. שיטות אלו נקראות מ Actionפריטי התפריט המתאימים. מחלקה זו מממשת בנוסף שיטות עזר: createComponents() - יצירת רכיבי טופס, loadImageFile(File f) — טעינת תמונה לתוך רכיב מיוחד מקובץ. יישום מחלקה GIFEncryptorByPaletteExtensionMethod דומה למימוש המחלקה GIFEncryptorByLSBMethod, ההבדל העיקרי הוא באופן שבו בתים של הודעות נכתבים ונקראים מהלוח.

תפעול התוכנית

שיטת LBS

נניח שיש תמונה כזו:

סטגנוגרפיה ב-GIF

בתמונה זו, פלטת הצבעים מורכבת מ-256 צבעים (כפי שנשמרו על ידי Paint). ארבעת הצבעים הראשונים הם לבן, שחור, אדום וירוק. שאר הצבעים הם שחורים. רצף הביטים של פלטת הצבעים הגלובלית יהיה כדלקמן:

11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000...

סטגנוגרפיה ב-GIF

לאחר הטמעת ההודעה, החלקים המסומנים בקו תחתון יוחלפו בחלקים מההודעה. התמונה המתקבלת כמעט בלתי ניתנת להבחנה מהמקור.

מקורי
תמונה עם הודעה מוטמעת

סטגנוגרפיה ב-GIF
סטגנוגרפיה ב-GIF

שיטת הרחבת פלטת הצבעים

כאשר תפתחו תמונה המכילה הודעה באמצעות שיטה זו, תראו את התמונה הבאה:

סטגנוגרפיה ב-GIF

ברור ששיטה זו לא תעבוד עבור פעילויות ריגול מלאות וייתכן שתדרוש הצפנה נוספת של ההודעה.

הצפנה/פענוח בתמונות מונפשות פועלים באותו אופן כמו בתמונות סטטיות רגילות, והאנימציה אינה מקולקלת.

מקורות בהם נעשה שימוש:

הורדה:

מקור: www.habr.com

קנה אירוח אמין לאתרים עם הגנת DDoS, שרתי VPS VDS 🔥 קנה אחסון אתרים אמין עם הגנת DDoS, שרתי VPS VDS | ProHoster