การแนะนำ
Приветствую
ไม่นานมานี้ ตอนที่ฉันเรียนอยู่ที่มหาวิทยาลัย มีรายวิชาในสาขาวิชา “วิธีซอฟต์แวร์เพื่อความปลอดภัยของข้อมูล” งานมอบหมายนี้กำหนดให้เราต้องสร้างโปรแกรมที่ฝังข้อความในไฟล์ GIF ฉันตัดสินใจที่จะทำใน Java
ในบทความนี้ ผมจะอธิบายประเด็นทางทฤษฎีบางประการ ตลอดจนวิธีการสร้างโปรแกรมขนาดเล็กนี้
ส่วนทฤษฎี
รูปแบบ GIF
GIF (Graphics Interchange Format - รูปแบบสำหรับการแลกเปลี่ยนรูปภาพ) เป็นรูปแบบสำหรับจัดเก็บภาพกราฟิกที่สามารถจัดเก็บข้อมูลที่ถูกบีบอัดได้โดยไม่สูญเสียคุณภาพในรูปแบบสูงสุด 256 สี รูปแบบนี้ได้รับการพัฒนาในปี 1987 (GIF87a) โดย CompuServe สำหรับการส่งภาพแรสเตอร์ผ่านเครือข่าย ในปี 1989 รูปแบบได้รับการแก้ไข (GIF89a) เพิ่มการรองรับความโปร่งใสและภาพเคลื่อนไหว
ไฟล์ GIF มีโครงสร้างบล็อก บล็อกเหล่านี้มีความยาวคงที่เสมอ (หรือขึ้นอยู่กับธงบางอัน) ดังนั้นจึงแทบจะเป็นไปไม่ได้เลยที่จะทำผิดพลาดเกี่ยวกับตำแหน่งของแต่ละบล็อก โครงสร้างของภาพ GIF ที่ไม่ใช่ภาพเคลื่อนไหวที่ง่ายที่สุดในรูปแบบ GIF89a:

จากบล็อกทั้งหมดของโครงสร้าง ในกรณีนี้ เราจะสนใจบล็อกจานสีส่วนกลางและพารามิเตอร์ที่รับผิดชอบสำหรับจานสี:
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 (บิตที่มีนัยสำคัญน้อยที่สุด)
- วิธีการเพิ่มจานสี
วิธีแอลเอสบี - วิธีการทั่วไปในการสุริยคติ ประกอบด้วยการแทนที่บิตสำคัญสุดท้ายในคอนเทนเนอร์ (ในกรณีของเราคือไบต์ของจานสีส่วนกลาง) ด้วยบิตของข้อความที่ซ่อนอยู่
โปรแกรมจะใช้สองบิตสุดท้ายในไบต์ของจานสีส่วนกลางซึ่งเป็นส่วนหนึ่งของวิธีนี้ ซึ่งหมายความว่าสำหรับรูปภาพ 24 บิต โดยที่ชุดสีคือ 3 ไบต์สำหรับสีแดง น้ำเงิน และเขียว หลังจากฝังข้อความลงไปแล้ว ส่วนประกอบแต่ละสีจะเปลี่ยนไปตามการไล่ระดับสูงสุด 255/XNUMX การเปลี่ยนแปลงดังกล่าว ประการแรกจะมองไม่เห็นหรือสังเกตเห็นได้ยากด้วยตามนุษย์ และประการที่สอง จะไม่สามารถมองเห็นได้บนอุปกรณ์ส่งออกข้อมูลคุณภาพต่ำ
จำนวนข้อมูลจะขึ้นอยู่กับขนาดของจานสีโดยตรง เนื่องจากขนาดสูงสุดของพาเล็ตคือ 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 คุณสามารถสร้างอัลกอริธึมทั่วไปสำหรับการแนะนำข้อความในจานสีรูปภาพ:

ในการพิจารณาการมีอยู่ของข้อความในรูปภาพ จำเป็นต้องเพิ่มลำดับบิตที่จุดเริ่มต้นของข้อความ ซึ่งตัวถอดรหัสจะอ่านก่อนและตรวจสอบความถูกต้อง ถ้าไม่ตรงกันก็ถือว่าไม่มีข้อความซ่อนอยู่ในภาพ ถัดไปคุณต้องระบุความยาวของข้อความ แล้วข้อความของข้อความนั้นเอง
แผนภาพคลาสของแอปพลิเคชันทั้งหมด:

การนำโปรแกรมไปใช้
การใช้งานโปรแกรมทั้งหมดสามารถแบ่งออกเป็นสองส่วน: การใช้งานการเข้ารหัสอินเทอร์เฟซและวิธีการถอดรหัส Encryptorในชั้นเรียน GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethodและการนำส่วนติดต่อผู้ใช้ไปใช้
พิจารณาชั้นเรียน GIFEncryptorByLSBMethod.

สนาม firstLSBit и secondLSBit มีจำนวนบิตของแต่ละไบต์ของรูปภาพที่ควรป้อนข้อความและตำแหน่งที่ควรอ่านข้อความ สนาม checkSequence เก็บลำดับบิตตรวจสอบเพื่อให้แน่ใจว่าสามารถจดจำข้อความที่ฝังได้ วิธีแบบคงที่ getEncryptingFileParameters ส่งคืนพารามิเตอร์ของไฟล์ที่ระบุและลักษณะของข้อความที่เป็นไปได้
อัลกอริธึมวิธีการ encrypt ชั้น GIFEncryptorByLSBMethod:

และรหัสของมัน:
@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:

@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ความแตกต่างหลักอยู่ที่วิธีการเขียนและอ่านไบต์ของข้อความจากพาเล็ต
การทำงานของโปรแกรม
วิธีปอนด์
สมมติว่ามีภาพเช่นนี้:

ในภาพนี้ จานสีประกอบด้วย 256 สี (ในขณะที่ Paint บันทึก) สี่สีแรกคือ: ขาว, ดำ, แดง, เขียว สีอื่นๆเป็นสีดำ ลำดับบิตของจานสีส่วนกลางจะเป็นดังนี้:
11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000...

เมื่อข้อความถูกฝัง บิตที่ขีดเส้นใต้จะถูกแทนที่ด้วยบิตจากข้อความ ภาพที่ได้แทบไม่แตกต่างจากต้นฉบับเลย
ดั้งเดิม
รูปภาพพร้อมข้อความที่ฝังไว้

![]()
วิธีการขยายจานสี
เมื่อคุณเปิดรูปภาพที่มีข้อความโดยใช้วิธีนี้ คุณจะเห็นรูปภาพต่อไปนี้:

เห็นได้ชัดว่าวิธีนี้ใช้ไม่ได้กับกิจกรรมการจารกรรมเต็มรูปแบบ และอาจต้องมีการเข้ารหัสข้อความเพิ่มเติม
การเข้ารหัส/ถอดรหัสในภาพเคลื่อนไหวทำงานเหมือนกับภาพนิ่งทั่วไป แต่ภาพเคลื่อนไหวจะไม่เสียหาย
แหล่งที่มาที่ใช้:
ดาวน์โหลด:
ที่มา: will.com
