[텀프로젝트] 파일 압축&암호화 유틸리티 개발 - 3 (암호화 기능 구현)
안녕하세요, 아까 하던거에 이어서 파일 압축&암호화 유틸리티를 만들겠습니다.
오늘 구현할 부분은 암호화 로직 + @ 입니다.
구현해야 하는 로직중 가장 중요하면서도 복잡성이 높은 로직입니다.
암호화 모드는 AES-256-GCM 방식을 선택했습니다. 해당 방식은 기밀성과 무결성을 동시에 보장하기 때문에 다른 암호화 모드보다 개인보안 유틸리티로써의 효율이 좋고, CBC모드에 비해서 병렬 처리가 가능하므로 속도가 빠릅니다.
암호화 로직은 perform_encrypt라는 함수로 구현을 했는데 구현이 복잡해서 정리를 미리 하고 가겠습니다.
먼저 비밀번호와 Salt값을 이용해 키를 생성하고 -> 암호화하고 -> 복구에 필요한 정보를 헤더에 붙이는 과정이 필요합니다.
저번 포스팅에서 고민했던 비밀번호를 검증하는 과정은 필요한 정보를 헤더에 붙이고,그 헤더에 대한 값을 검증하는 것으로 가능합니다. 내부 구현에 대해 더 상세히 살펴보겠습니다.
- perform_encrypt의 인자는 plain_data, plain_len, const char* password, long* out_len
- 먼저 필요한 버퍼 변수 선언 : salt, iv, tag, key
- 이후 랜덤 값 생성, salt와 iv값(salt값은 레인보우 테이블 공격 방지, IV는 알고리즘의 시작 상태 무작위 설정)
- 비밀번호 + Salt 값 -> SHA256을 통한 Key 값 생성
- AES-256-GCM으로 암호화 수행
- 암호와 작업을 수행할 작업 공간을 만듬
- EVP_EncryptInit_ex를 2번 호출해 알고리즘을 세팅하고, 키와 IV 주입
- malloc으로 암호문이 담길 임시 버퍼 생성
- EVP_EncryptUpdate(ctx, ciphertext, &len, plain_data, plain_len)를 통한 실제 암호화 수행
- 암호화 과정에서 계산된 16바이트 인증 태그를 가져옴
- Salt값 + IV + 인증 Tag + 암호문을 하나의 버퍼에 담음
복호화는 이 역순으로 진행하면 됩니다.
너무 이론적인 부분이라 글이 너무 길어졌는데 전체 함수 첨부하겠습니다.
unsigned char* perform_encrypt(const unsigned char* plain_data, long plain_len, const char* password, long* out_len) {
// 1. 필요한 버퍼 변수 선언
unsigned char salt[SALT_LEN];
unsigned char iv[IV_LEN];
unsigned char tag[TAG_LEN];
unsigned char key[KEY_LEN];
// 2. 랜덤 값 생성(salt, iv)
if (!RAND_bytes(salt, sizeof(salt)) || !RAND_bytes(iv, sizeof(iv))) {
printf("[Error] 랜덤 값 생성 실패\n");
return NULL;
}
// 3. 키 유도 (비밀번호 + Salt -> SHA256 -> Key)
// 비밀번호 뒤에 Salt를 붙여서 해시를 돌린다.
unsigned char* key_material = (unsigned char*)malloc(strlen(password) + SALT_LEN);
if (key_material == NULL) return NULL;
memcpy(key_material, password, strlen(password)); // 비밀번호 복사
memcpy(key_material + strlen(password), salt, SALT_LEN); // 뒤에 Salt 붙이기
// SHA256으로 최종 Key 생성
SHA256(key_material, strlen(password) + SALT_LEN, key);
free(key_material); // 임시 메모리 해제
// 4. 암호화 수행 (OpenSSL EVP)
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return NULL;
// AES-256-GCM 모드 세팅
if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) return NULL;
if (1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) return NULL;
// 암호문이 담길 임시 버퍼
unsigned char* ciphertext = (unsigned char*)malloc(plain_len);
if (!ciphertext) return NULL;
int len = 0;
int ciphertext_len = 0;
// 실제 데이터 암호화
if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plain_data, plain_len)) {
free(ciphertext);
return NULL;
}
ciphertext_len = len;
//암호화 마무리
if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) {
free(ciphertext);
return NULL;
}
ciphertext_len += len;
//GCM 태그 추출(데이터 무결성 검증용)
// 중요: GCM 태그(Tag) 추출 (데이터 무결성 검증용)
if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, TAG_LEN, tag)) {
free(ciphertext);
return NULL;
}
EVP_CIPHER_CTX_free(ctx);
// 5. 최종 데이터 패키징 (파일에 저장될 구조)
// 구조: [Salt(16)] + [IV(12)] + [Tag(16)] + [암호문(N)]
long total_len = SALT_LEN + IV_LEN + TAG_LEN + ciphertext_len;
unsigned char* final_packet = (unsigned char*)malloc(total_len);
if (!final_packet) {
free(ciphertext);
return NULL;
}
unsigned char* ptr = final_packet;
memcpy(ptr, salt, SALT_LEN); ptr += SALT_LEN;
memcpy(ptr, iv, IV_LEN); ptr += IV_LEN;
memcpy(ptr, tag, TAG_LEN); ptr += TAG_LEN;
memcpy(ptr, ciphertext, ciphertext_len);
free(ciphertext); // 암호문 임시 버퍼는 이제 필요 없음
// 결과 반환
*out_len = total_len;
return final_packet;
}
함수에 대한 설명은 주석을 참고하면서 보시면 될 것 같습니다.
이제 해당 기능을 process_file에 구현하면 되는데 상세한 내용을 함수 안에 다 구현해놨기 때문에 간단한 호출만 해주면 됩니다.
//TODO : 여기서 암호화 함수 호출
long encrypted_len = 0;
unsigned char* encrypted_data = perform_encrypt(compressed_data, compressed_len, password.c_str(), &encrypted_len);
//압축 저장 기능 수행하기
//C++ std::string을 사용하면 문자열 합치기가 쉬움.
//파일의 확장자를 커스텀 가능, string.poir로 설정
if (encrypted_data != NULL) {
printf(" >> 암호화 성공! (최종 크기: %ld bytes)\n", encrypted_le
위와같이 구현을 해줬고 빌드까지 잘 됐습니다.
갑자기 난이도가 너무 높아져서 당황하긴 했는데, 그래도 점점 완성도가 높아지는게 보입니다.

실행결과는 다음과 같습니다.
다음 포스팅때 복호화 기능을 구현하면서 잘 복호화가 되는지 테스트를 해보면 될 것 같습니다.
읽어주셔서 감사합니다.