이전 글을 통해 이미지의 픽셀에 접근하는 방법을 알아보았습니다.

 

글을 작성하면서 조금 아쉽게 느껴졌던 게 "픽셀에 접근하는 방법을 알겠지만 이를 어떻게 응용할 수 있는 것인가?" 에

 

대한 질문을 나에게 던지면 막상 떠오르는 것이 없었던 것이였습니다. 그래서 며칠을 생각한 결과, 입력받은 텍스트를

 

이미지 속 픽셀값에 변조시켜 이미지에 텍스트를 숨길 수 있는 방법에 대하여 소개하려 합니다.

 

 

 

먼저 암호화 시키는 기술에 대해 간단히 알아보도록 하겠습니다.

 

전달하려는 정보를 이미지나 MP3 파일 등에 암호화 하여 숨기는 기술을 스테가노그래피[Steganographt]이라고 합니다.

 

이 기술은 크게 삽입 기법수정 기법으로 나뉘어집니다.

 

 

삽입 기법은 이미지 파일 데이터를 변경하지 않고 추가 데이터를 파일 앞이나 뒤에 붙이는 방식입니다.

다시 말해,  jpg, png 등의 파일에서 데이터의 시작을 알리는 SOI(Start Of Image) 이전에 또는 끝을 알리는

EOI(End Of Image) 뒤에 전달하려는 정보를 추가하는 것입니다.

 

이러한 방법은 정보를 숨기려는 이미지에 가시적인 영향을 주진 않지만 이미지의 크기가 커지게 됩니다.

 

 

수정 기법은 이미지 파일에서 RGB 값의 최하위 비트(LSB)를 수정하여 추가 데이터를 입력하는 기법입니다.

 

이는 육안으로 알아차리기 힘들기 때문에 많이 이용되는 방식입니다.

 

다시 말해, 픽셀 값의 표현 가능한 수의 범위인 8비트(0~255)에서 특정 값이 255일 경우 최하위비트를 수정해 254 변조시켜도 우리 눈에는 255과 254가 거의 동일하게 보인다는 것입니다.

 

 

 

아래 코드는 스테가노그래피의 수정 기법을 이용한 방법입니다.

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

 

< Image Encording >

#include <opencv2/opencv.hpp> 
#include <iostream>
#include <time.h>
#include <math.h>

using namespace cv;
using namespace std;

int main(int argc, char *argv[]) {

	// 이미지 입력
	/*
	if (argc < 2) {
		printf("이미지 경로를 입력하세요.\n");
		printf("파일 이름, 이미지 경로\n\n");
		exit(0);
	}
	else if (argc > 2) {
		printf("인자를 초과하여 작성하였습니다.\n");
		printf("파일 이름, 이미지 경로\n\n");
		exit(0);
	}
	*/
	
	Mat img = imread("C:\\test\\bridge.bmp");

	int max_text = img.cols*img.rows;  // 텍스트 작성 최대 범위 변수
	int t_length; // 입력 텍스트 길이
	int t_length_clone = 0;
	int inf_lo = 0; // 위치 정보 변수
	int data_text = 0;
	int data_start = 0;
	int x = 0, y = 0; // 좌표
	int cnt = 0;

	char str[10000];
	char e_bit_text[8] = { 0, };
	char e_bit_lo[8] = { 0, };
	char text_length[14] = { 0, };

	if (max_text < 50 * 50) {
		printf("50x50 크기 이상의 이미지를 입력하세요.\n");
		exit(0);
	}

	// 최대 텍스트 입력 수
	if (((max_text - 14) / 25) > 10000)
		max_text = 10000;
	else
		max_text = (max_text - 14) / 25;

	max_text = max_text - 1; // 널문자 제외
	do {
		printf("입력 가능 텍스트 수(띄어쓰기 포함) : %d \n\n", max_text);
		printf("------------------------------------------------------------------------\n");
		printf("TEXT : ");
		fgets(str, sizeof(str), stdin);
		t_length = strlen(str);
		printf("\n\nText length : %d\n", t_length - 1);
		printf("------------------------------------------------------------------------\n");

		if (t_length == 1) {
			printf("텍스트를 입력하지 않았습니다.\n\n");
			printf("다시 입력해주세요.\n\n\n\n");
		}

		else if (t_length  > max_text + 1) {
			printf("입력 가능 텍스트 수를 초과하였습니다.\n\n");
			printf("다시 입력해주세요.\n\n\n\n");
		}
	} while ((t_length == 1) || (t_length > max_text + 1));



	///////////////////////////////////////
	// 14bit 내, 위치 정보 저장  (범위  0~37,777) //
	//////////////////////////////////////

	t_length = t_length - 1; // 널문자 제외
	t_length_clone = t_length; // 복사

    // 입력 텍스트 길이 2진수 14비트로 저장
	for (int t = 0; t < 14; t++) {
		if (t_length_clone > 0) {
			text_length[13 - t] = t_length_clone % 2;
			t_length_clone = t_length_clone / 2;
		}
		else
			text_length[13 - t] = 0;
	}

	// 입력 텍스트 길이 데이터 좌표에 입력
	for (int t = 0; t < 14; t++) {
		if (img.at<Vec3b>(y, x)[0] % 2 == 1) {
			if (text_length[x] == 0)
				img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] - 1;
		}
		else if (img.at<Vec3b>(y, x)[0] % 2 == 0) {
			if (text_length[x] == 1)
				img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] + 1;
		}

		x++;
		if (x == img.cols) {
			x = 0;
			y++;
		}
	}
	srand(time(NULL)); // 랜덤값

	while (cnt < t_length)
	{
		// 첫 번째 정보(텍스트 정보가 Y축에 어느 X축 위치인지에 대한 정보
		inf_lo = rand();
		while (inf_lo > 10)
			inf_lo = inf_lo / 10;
		//inf_lo = inf_lo % 10;
		inf_lo = 9;
		//printf("난수 %d \n", inf_lo);

		// 위치 정보와 텍스트 데이터 위치 사이 좌표 거리
		data_start = inf_lo;

		// 문자 1개 변수에 대입
		data_text = str[cnt];

		// 위치 정보, 문자 8비트로 표현
		for (int t = 0; t < 8; t++)
		{
			// 위치
			if (inf_lo > 0) {
				e_bit_lo[7 - t] = inf_lo % 2;
				inf_lo = inf_lo / 2;
			}
			else
				e_bit_lo[7 - t] = 0;

			// 문자
			if (data_text > 0) {
				e_bit_text[7 - t] = data_text % 2;
				data_text = data_text / 2;
			}
			else
				e_bit_text[7 - t] = 0;
		}

		// 위치 정보 입력 // (y,0)을 기준으로 8픽셀 맨 하위 비트에 입력
		for (int q = 0; q < 8; q++) {

			if (img.at<Vec3b>(y, x)[0] % 2 == 1) {
				if (e_bit_lo[q] == 0)
					img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] - 1;
			}
			else if (img.at<Vec3b>(y, x)[0] % 2 == 0) {
				if (e_bit_lo[q] == 1)
					img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] + 1;
			}

			x++;
			if (x == img.cols) {
				x = 0;
				y++;
			}
		}


		x += data_start;
		if (x >= img.cols) {
			x = x - img.cols;
			y++;
		}

		// 데이터 정보 입력 // (y,8+위치정보)를 기준으로 8픽셀 맨 하위 비트에 입력
		for (int q = 0; q < 8; q++) {

			if (img.at<Vec3b>(y, x)[0] % 2 == 1) {
				if (e_bit_text[q] == 0)
					img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] - 1;
			}
			else if (img.at<Vec3b>(y, x)[0] % 2 == 0) {
				if (e_bit_text[q] == 1)
					img.at<Vec3b>(y, x)[0] = img.at<Vec3b>(y, x)[0] + 1;
			}

			x++;
			if (x == img.cols) {
				x = 0;
				y++;
				if (y == img.rows)
					printf("텍스트 입력 가능 범위 초과\n");
			}
		}
		cnt++;
	}

	imwrite("Encording_image.bmp", img);

	printf("텍스트 인코딩 완료!!!\n\n");
}

 

입력 텍스트

 

원본 이미지

 

수정 기법이 적용된 이미지

 

< Image Decording>

#include <opencv2/opencv.hpp> 
#include <iostream>
#include <math.h>

using namespace cv;
using namespace std;

int main() {

	// 데이터 입력
	Mat img = imread("Encording_image.bmp");

	if (img.empty() == 1) {
		printf("이미지를 로드하지 못했습니다.\n\n");
		exit(0);
	}

	int text_length[14] = { 0, };
	int lo_inf[8] = { 0, };
	int text_inf[8] = { 0, };
	int length = 0;
	int cnt = 0;
	int x = 0, y = 0;
	int text = 0;
	int lo = 0;
	int data_start = 0;
	char str[10000] = { 0, };


	// 텍스트 길이 정보 비트 추출
	for (int t = 0; t < 14; t++) {
		if (img.at<Vec3b>(0, t)[0] % 2 == 1)
			text_length[t] = 1;
		else if (img.at<Vec3b>(0, t)[0] % 2 == 0)
			text_length[t] = 0;
	}

	// 텍스트 길이 값
	for (int t = 0; t < 14; t++)
		length += text_length[t] * pow(2, 13 - t);

	// 텍스트 길이가 포함된 14비트 길이
	x = 14;

	while (cnt < length)
	{
		// 위치 정보 비트 추출
		for (int t = 0; t < 8; t++) {
			if (img.at<Vec3b>(y, x)[0] % 2 == 1)
				lo_inf[t] = 1;
			else if (img.at<Vec3b>(y, x)[0] % 2 == 0)
				lo_inf[t] = 0;

			x++;
			if (x == img.cols) {
				x = 0;
				y++;
			}
		}

		// 위치 정보 값
		for (int t = 0; t < 8; t++)
			lo += lo_inf[t] * pow(2, 7 - t);


		x += lo;
		if (x >= img.cols) {
			x = x - img.cols;
			y++;
		}

		// 텍스트 정보 비트 추출
		for (int t = 0; t < 8; t++) {
			if (img.at<Vec3b>(y, x)[0] % 2 == 1)
				text_inf[t] = 1;
			else if (img.at<Vec3b>(y, x)[0] % 2 == 0)
				text_inf[t] = 0;

			x++;
			if (x == img.cols) {
				x = 0;
				y++;
				if (y == img.rows)
					printf("디코딩 오류..\n");
			}
		}

		//텍스트 정보 값
		for (int t = 0; t < 8; t++)
			text += text_inf[t] * pow(2, 7 - t);

		// 정보 저장
		str[cnt] = text;

		//초기화
		lo = 0;
		text = 0;

		cnt++;
	}
	printf("받은 텍스트 : ");
	for (int i = 0; i < length; i++)
		printf("%c", str[i]);
	printf("\n\n");
}

 

Image Decording으로 추출한 텍스트

 

 

코드를 살펴보면, Image Encording을 수행할 때 가장 먼저 텍스트를 입력받고 텍스트의 길이와 각각의 글자들을

바이너리로 표현한다. 이후 아래 그림과 같이 바이너리 값을 원본 이미지의 픽셀에 입력시켜 변조합니다.

 

아래 그림은 이미지의 픽셀단위로 표현한 것이며, 위 결과의 입력 텍스트인 "I am Groot!!!"에서 일부인 글자 I와

 

띄어쓰기가 입력된 바이너리 값을 나타 것입니다. 아래 그림을 참고하면 이해 하는 데 도움이 될 것입니다.

 

 

 

파란색은 입력 받은 텍스트의 길이 정보(14비트) 

 

노란색은 조금의 보안성을 높일 수 있도록 텍스트 데이터를 입력시킬 픽셀의 위치 정보(8비트)

* 위치 정보는 랜덤값을 호출하여 나타냄

 

초록색은 텍스트 데이터의 정보(8비트)

 

 

이미지 픽셀에 입력된 정보의 바이너리 값

 

 

 

아래 구글 드라이브 올라간 파일은 위 코드의 exe파일입니다

 

위 코드를 작성하여 확인하는 것 보다 실행파일을 받아 하는 게 더 빠르게 확인이 가능할 거 같아 올렸습니다.

 

cmd창에서 실행 파일 경로로 들어가 [Image Encording.exe] [입력시킬 bmp 이미지] 순으로 작성하여 실행해 

 

텍스트를 입력한다. 그러면 새로운 Encording_image.bmp 파일이 생성됩니다.

 

그리고 다시 cmd창에서 Image Decording.exe을 실행하면 같은 폴더에 있는 Encording_image.bmp를 입력받아

 

받은 텍스트를 나타냅니다.

 

https://drive.google.com/file/d/1DG-47-y3NrUEZinmrq8rYmONPyN7JQ86/view?usp=sharing

 

Hiding Text.zip

 

drive.google.com

 

 

< 정리글 >

   

   오늘은 이미지의 픽셀에 접근해 입력받은 텍스트를 암호화 시키는 코드를 작성해보았습니다.  약간의 고찰이 있었고,

 

바로 JPG 파일 형식의 이미지를 불러와 픽셀을 변조시킨 후 다시 저장을하면 픽셀 값이 내가 변조한 값과 다르게

나타난다는 것입니다. 확실히 파악하진 못했지만 Opencv에서 사진을 저장할 때 JPG 형식이 압축 기법이 들어간 것이기

 

때문이라고 짐작하고 있습니다.

 

참고로 위 방법을 사용하실거면 압축되지 않은 bmp 형식의 이미지를 이용해야 해야 하며, 중간에 오류가 발생하는

 

부분을 해결하지 못해 이미지의 크기는 50x50 이상인 경우에만 동작하도록 제한을 걸어놓았습니다.

 

추후에 디버깅을 하여 수정된 코드를 올리도록 하겠습니다.

 

 

 

 

< 참고 사이트 >

https://gnu-cse.tistory.com/36

 

이미지 파일 안에 file, text 숨기기 - 스테가노 그래피

사진, 음악, 동영상 등의 일반적인 파일안에 텍스트나 파일을 숨기는 암호화 기법을 steganography라고 한다. 이미지에 파일을 숨기는 경우 어떤 테크닉이 있는지 간단히 알아보자. 이미지 파일에 메세지를 숨기는..

gnu-cse.tistory.com

https://blue-shadow.tistory.com/21

 

스테가노그래피 (Steganography)

참고 링크 : - [보안뉴스] [보안용어 A to Z] 스테가노그래피(Steganography) (2015-08-26 18:01) - [위키백과] 스테가노그래피 스테가노그래피 데이터 은폐 기술 중 하나이며, 데이터를 다른 데이터에 삽입하는..

blue-shadow.tistory.com

 

+ Recent posts