본문 바로가기

리눅스 멀티스레드 파일 공유 프로그램 - 10(완) 본문

개인 프로젝트 공부

리눅스 멀티스레드 파일 공유 프로그램 - 10(완)

Seongjun_You 2024. 6. 16. 17:10

마지막 간단한 암호화를 적용해 보았다.

대칭키인 AES를 이용해 파일을 암호화를 진행하고 클라이언트에서는 복호화를 진행한다.

따로 cpp파일로 만들어 사용했다.

 

 

라이브러리를 다운로드하여준다.

여러 암호화를 위한 툴을 제공해 준다.

인자값도 추가해 준다.

 

256비트의 키를 미리 만들어 주었다.

 

 

#include <openssl/aes.h>
#include <openssl/rand.h>
#include <iostream>
#include <fstream>
#include <vector>

#define AES_KEY_LENGTH 256  // AES 키 길이를 256비트로 설정

std::string keyFile = "/home/sj/aes_key/aes_key.txt";  // AES 키 파일

bool encryptFileAES(const std::string& inputFile, const std::string& outputFile) {

    
    
    // AES 키 읽기
    unsigned char aesKey[AES_KEY_LENGTH / 8];
    std::ifstream keyStream(keyFile, std::ios::binary);
    if (!keyStream) {
        std::cerr << "AES 키 파일을 열 수 없음" << std::endl;
        return false;
    }
    keyStream.read(reinterpret_cast<char*>(aesKey), AES_KEY_LENGTH / 8);
    keyStream.close();

    // AES 암호화 키 설정
    AES_KEY aesKeyStruct;
    if (AES_set_encrypt_key(aesKey, AES_KEY_LENGTH, &aesKeyStruct) < 0) {
        std::cerr << "AES 키 설정 실패" << std::endl;
        return false;
    }

    // 입력 파일 열기
    std::ifstream inFile(inputFile, std::ios::binary);
    if (!inFile) {
        std::cerr << "입력 파일을 열 수 없음" << std::endl;
        return false;
    }

    // 출력 파일 열기
    std::ofstream outFile(outputFile, std::ios::binary);
    if (!outFile) {
        std::cerr << "출력 파일을 열 수 없음" << std::endl;
        inFile.close();
        return false;
    }

    // AES CBC 모드로 데이터 암호화
    unsigned char iv[AES_BLOCK_SIZE];
    if (!RAND_bytes(iv, AES_BLOCK_SIZE)) {
        std::cerr << "IV 생성 실패" << std::endl;
        inFile.close();
        outFile.close();
        return false;
    }
    outFile.write(reinterpret_cast<char*>(iv), AES_BLOCK_SIZE);

    std::vector<unsigned char> buffer(AES_BLOCK_SIZE);
    while (true) {
        inFile.read(reinterpret_cast<char*>(buffer.data()), AES_BLOCK_SIZE);
        int bytesRead = inFile.gcount();
        if (bytesRead <= 0) break;

        if (bytesRead < AES_BLOCK_SIZE) {
            int padding = AES_BLOCK_SIZE - bytesRead;
            for (int i = bytesRead; i < AES_BLOCK_SIZE; ++i) {
                buffer[i] = padding;
            }
        }

        AES_cbc_encrypt(buffer.data(), buffer.data(), AES_BLOCK_SIZE, &aesKeyStruct, iv, AES_ENCRYPT);
        outFile.write(reinterpret_cast<char*>(buffer.data()), AES_BLOCK_SIZE);
    }

    // 파일 닫기
    inFile.close();
    outFile.close();
    return true;
}


bool decryptFileAES(const std::string& inputFile, const std::string& outputFile) {
    // AES 키 읽기
    unsigned char aesKey[AES_KEY_LENGTH / 8];
    std::ifstream keyStream(keyFile, std::ios::binary);
    if (!keyStream) {
        std::cerr << "AES 키 파일을 열 수 없음" << std::endl;
        return false;
    }
    keyStream.read(reinterpret_cast<char*>(aesKey), AES_KEY_LENGTH / 8);
    keyStream.close();

    // AES 복호화 키 설정
    AES_KEY aesKeyStruct;
    if (AES_set_decrypt_key(aesKey, AES_KEY_LENGTH, &aesKeyStruct) < 0) {
        std::cerr << "AES 키 설정 실패" << std::endl;
        return false;
    }

    // 입력 파일 열기
    std::ifstream inFile(inputFile, std::ios::binary);
    if (!inFile) {
        std::cerr << "입력 파일을 열 수 없음" << std::endl;
        return false;
    }

    // 출력 파일 열기
    std::ofstream outFile(outputFile, std::ios::binary);
    if (!outFile) {
        std::cerr << "출력 파일을 열 수 없음" << std::endl;
        inFile.close();
        return false;
    }

    // 초기화 벡터(IV) 읽기
    unsigned char iv[AES_BLOCK_SIZE];
    inFile.read(reinterpret_cast<char*>(iv), AES_BLOCK_SIZE);

    // AES CBC 모드로 데이터 복호화
    std::vector<unsigned char> buffer(AES_BLOCK_SIZE);
    while (true) {
        inFile.read(reinterpret_cast<char*>(buffer.data()), AES_BLOCK_SIZE);
        int bytesRead = inFile.gcount();
        if (bytesRead <= 0) break;

        AES_cbc_encrypt(buffer.data(), buffer.data(), AES_BLOCK_SIZE, &aesKeyStruct, iv, AES_DECRYPT);

        // 복호화된 데이터를 출력 파일에 쓰기
        if (inFile.eof()) {
            // 마지막 블록일 경우 패딩 제거
            int padding = buffer[AES_BLOCK_SIZE - 1];
            bytesRead -= padding;
        }
        outFile.write(reinterpret_cast<char*>(buffer.data()), bytesRead);
    }

    // 파일 닫기
    inFile.close();
    outFile.close();
    return true;
}

암호화 복호화를 위한 전체 코드이다.

 

 

// AES 키 읽기
    unsigned char aesKey[AES_KEY_LENGTH / 8];
    std::ifstream keyStream(keyFile, std::ios::binary);
    if (!keyStream) {
        std::cerr << "AES 키 파일을 열 수 없음" << std::endl;
        return false;
    }
    keyStream.read(reinterpret_cast<char*>(aesKey), AES_KEY_LENGTH / 8);
    keyStream.close();

키를 읽어오는 로직이다.

keystream으로 키를 읽어와 aeskey에 넣어준다.

(char*) 타입으로 aeskey에 넣어주는데

이게 (char *)aeskey 하고 같은 로직이다.

c++에서는 이렇게 쓴다고 한다.

 

 

 

// AES 암호화 키 설정
    AES_KEY aesKeyStruct;
    if (AES_set_encrypt_key(aesKey, AES_KEY_LENGTH, &aesKeyStruct) < 0) {
        std::cerr << "AES 키 설정 실패" << std::endl;
        return false;
    }

AES_set_encrypt_key()는 AES 암호화 키를 설정하는 함수이다.

매개변수 차례대로 키를 카리키는 포인터, 키의 길이, 초기화할 구조체이다.

구조체에는 키를 실질적으로 사용하기 위한 정보가 들어가게 된다.

 

 

// 입력 파일 열기
    std::ifstream inFile(inputFile, std::ios::binary);
    if (!inFile) {
        std::cerr << "입력 파일을 열 수 없음" << std::endl;
        return false;
    }

    // 출력 파일 열기
    std::ofstream outFile(outputFile, std::ios::binary);
    if (!outFile) {
        std::cerr << "출력 파일을 열 수 없음" << std::endl;
        inFile.close();
        return false;
    }

암호화할 파일과 그 결과를 위한 출력 파일을 열어준다.

 

 

 

// AES CBC 모드로 데이터 암호화
    unsigned char iv[AES_BLOCK_SIZE];
    if (!RAND_bytes(iv, AES_BLOCK_SIZE)) {
        std::cerr << "IV 생성 실패" << std::endl;
        inFile.close();
        outFile.close();
        return false;
    }
    outFile.write(reinterpret_cast<char*>(iv), AES_BLOCK_SIZE);

IV벡터를 생성해 준다.

이는 블록 암호화에서 사용하는 중요한 함수이다.

 

IV의 원리를 보면

첫 번째 평문 블록은 IV와 XOR연산 후 암호화된다.

각 다음 평문 블록은 이전 암호문 블록과 XOR 연산 후 암호화된다.

이 결과는 출력파일에 저장되며 나중에 복호화를 할 때 필요하다.

 

 

 

std::vector<unsigned char> buffer(AES_BLOCK_SIZE);
    while (true) {
        inFile.read(reinterpret_cast<char*>(buffer.data()), AES_BLOCK_SIZE);
        int bytesRead = inFile.gcount();
        if (bytesRead <= 0) break;

        if (bytesRead < AES_BLOCK_SIZE) {
            int padding = AES_BLOCK_SIZE - bytesRead;
            for (int i = bytesRead; i < AES_BLOCK_SIZE; ++i) {
                buffer[i] = padding;
            }
        }

        AES_cbc_encrypt(buffer.data(), buffer.data(), AES_BLOCK_SIZE, &aesKeyStruct, iv, AES_ENCRYPT);
        outFile.write(reinterpret_cast<char*>(buffer.data()), AES_BLOCK_SIZE);
    }

입력을 받기 위한 임시 buffer 변수를 만들어준다.

inFile에 데이터를 블록 크기에 맞게 buffer에 넣어준다.

bytesRead에는 inFile이 read를 통해 읽은 바이트 수가 들어가며

0이면 반복문을 깬다.

 

만약 읽어온 바이트 수가 32바이트 이하면

패딩을 추가해주어야 하는데

규칙이 있다.

남은 공간의 수를 넣어주어야 한다.

만약 3바이트가 부족하면

남은 3바이트 부분을 0x03으로 채워주어야 한다.

 

이게 PKCS7 패딩 방식이라고 한다.

 

AES_cbc_encrypt()는 실제 암호화를 진행하는 함수이다. AES CBC 모드로 진행한다.

buffer.data()는 입력 데이터와 출력 데이터를 저장할 버퍼를 가리킨다.

AES_BLOCK_SIZE는 처리할 데이터의 크기이다.

&aesKeyStruct는 AES 암호화 키 구조체이다.

IV는 초기화 벡터이다.

AES_ENCRYPT는 암호화 모드를 지정한다.

 

그다음 암호화된 데이터 buffer를 outFile에 저장한다.

복호화 코드는 암호화를 이해하면 쉽다.

저장해 둔 IV를 먼저 읽어와서 복호화 함수에 넣어주면 되고

패딩이 나올 경우 그 수만큼 지워주면 된다.

 

 

server에서 압축을 하고 암호화하며 클라이언트로 전송을 해주면

클라이언트는 복호화를 진행해 준다.

 

이로써 다음 프로젝트 진행 시 

카페든 집이든 편하게 진행할 수 있을 것 같다.

git을 쓰면 편하겠지만 그냥 만들어보고 싶었다.

Comments