프로젝트

[텀프로젝트] 파일 압축&암호화 유틸리티 개발 - 3 (암호화 기능 구현)

poiri3r 2025. 12. 11. 22:19

안녕하세요, 아까 하던거에 이어서 파일 압축&암호화 유틸리티를 만들겠습니다.

오늘 구현할 부분은 암호화 로직 + @ 입니다.

구현해야 하는 로직중 가장 중요하면서도 복잡성이 높은 로직입니다.

암호화 모드는 AES-256-GCM 방식을 선택했습니다. 해당 방식은 기밀성과 무결성을 동시에 보장하기 때문에 다른 암호화 모드보다 개인보안 유틸리티로써의 효율이 좋고, CBC모드에 비해서 병렬 처리가 가능하므로 속도가 빠릅니다.

 

암호화 로직은 perform_encrypt라는 함수로 구현을 했는데 구현이 복잡해서 정리를 미리 하고 가겠습니다.

먼저 비밀번호와 Salt값을 이용해 키를 생성하고 -> 암호화하고 -> 복구에 필요한 정보를 헤더에 붙이는 과정이 필요합니다.

저번 포스팅에서 고민했던 비밀번호를 검증하는 과정은 필요한 정보를 헤더에 붙이고,그 헤더에 대한 값을 검증하는 것으로 가능합니다. 내부 구현에 대해 더 상세히 살펴보겠습니다.

  1. perform_encrypt의 인자는 plain_data, plain_len, const char* password, long* out_len
  2. 먼저 필요한 버퍼 변수 선언 : salt, iv, tag, key
  3. 이후 랜덤 값 생성, salt와 iv값(salt값은 레인보우 테이블 공격 방지, IV는 알고리즘의 시작 상태 무작위 설정)
  4. 비밀번호 + Salt 값 -> SHA256을 통한 Key 값 생성
  5. AES-256-GCM으로 암호화 수행
    • 암호와 작업을 수행할 작업 공간을 만듬
    • EVP_EncryptInit_ex를 2번 호출해 알고리즘을 세팅하고, 키와 IV 주입
    • malloc으로 암호문이 담길 임시 버퍼 생성
    • EVP_EncryptUpdate(ctx, ciphertext, &len, plain_data, plain_len)를 통한 실제 암호화 수행
  6. 암호화 과정에서 계산된 16바이트 인증 태그를 가져옴
  7. 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

 

위와같이 구현을 해줬고 빌드까지 잘 됐습니다.

갑자기 난이도가 너무 높아져서 당황하긴 했는데, 그래도 점점 완성도가 높아지는게 보입니다.

 

실행결과는 다음과 같습니다.

다음 포스팅때 복호화 기능을 구현하면서 잘 복호화가 되는지 테스트를 해보면 될 것 같습니다.

 

읽어주셔서 감사합니다.