Tistory View

openssl

복호화(C/C++) : aes256 cbc openssl

God Dangchy What should I do? 2020. 3. 23. 11:51

명령줄에서 다음과 같이 암호화 작업을 했다면


openssl enc -e -aes-256-cbc -in plain.txt -out encrypted.data -k "my_password"

이렇게 생성된 파일을 복호화하는 코드를 작성해 보자.


실제 위의 명령줄은 문제가 많지만, 처음 openssl을 접하는 사용자는 위의 명령을 통해 암호화작업을 많이 하게 될 것이다. 그러다 보니, 이 문제많은 방식에서 다른 방식으로 변경할 경우 일단 복호화작업을 해야 하니, 복호화 프로그래밍도 익힐 겸 복호화하는 코드를 생성해보자.



저장된 파일의 구조는 이전 포스트에서도 언급했듯이 다음의 구조를 가진다.



검은색 부분에 "Salt__"(8bytes)가 있으며, 이어서 실제 Salt값(노란색)이 8bytes가 따라온다.

그 다음 빨간색부분이 암호화된 데이터이다.



일단 header파일을 불러온다.

#include <openssl/evp.h>


다음의 함수는 이 암호화된 파일의 내용(파일이 아니고) 복호화하는 함수이다.

리턴값은 성공의 여부를 나타내고, 성공할 경우 decrypted에 복호화된 결과가 들어가게 된다.



bool decrypt_def_openssl_encoded_content( std::string* decrypted, const uint8_t* encrypted, int len, const char* szPassword )

{

bool ret = false;

uint8_t key[32];

uint8_t iv[16];

uint8_t salt[8];

int srcRemain;

uint8_t* pEnc;


uint8_t decBuf[1024+16]; // EVP_DecryptUpdate는 입력값보다 16byte[ciper-size] 더 큰 buffer를 넣어줘야한다.

// ciper size 는 -> EVP_CIPHER_block_size(EVP_aes_256_cbc()) 


int nread;

int outLen;

EVP_CIPHER_CTX *ctx = NULL;

       // salt때문에 크기는 16바이트보다 클 수밖에 없다.

if( len < 16 ) {

fprintf( stderr, "file is too short < 16 bytes\n" );

return false;

}

// salt값을 빼온다.

// "Salt__"라는 signature를 체크하는 부분은 생략했다.

memcpy( salt, encrypted + 8, 8 );

// salt와 넘어온 비밀번호로 key와 iv를 추출한다.

// count 값으로 1을 넣었는 데, 이는 openssl명령이 이 값을 1로 처리하고 있어서다.

// 여기서는 md(message digest)로 sha256을 사용하지만, 구버전에서 만들어진 파일을 풀려면, md5를 넘겨야한다.

if( 0 == EVP_BytesToKey( EVP_aes_256_cbc(),EVP_sha256(), salt, (uint8_t*)szPassword, strlen(szPassword), 1, key, iv ) ) {

fprintf( stderr, "key iv deriving failure\n" );

goto last;

}


    if(!(ctx = EVP_CIPHER_CTX_new())) {

     fprintf( stderr, "create context failure" );

     goto last;

    }



EVP_DecryptInit_ex( ctx, EVP_aes_256_cbc(), NULL, key, iv );



// 암호화된 데이터를 밀어넣으면서, 복호화된 정보를 만든다.

srcRemain = len - 16; // 처음 salt부분은 포함하지 않는다.


pEnc = (uint8_t*)encrypted + 16; // 처음 salt부분은 포함하지 않는다


while( srcRemain > 0 ) 

{

nread = 1024;

if( srcRemain < nread ) {

      nread = srcRemain;

}

        

if (1 != EVP_DecryptUpdate(ctx, decBuf, &outLen, pEnc, nread ) ) {

fprintf( stderr, "error while decrypting\n" );

goto last;

}

decrypted->append( (char*)decBuf, outLen );

pEnc      += nread;

srcRemain -= nread;

}

        // 암호화된 정보는 다 밀어 넣었으니, 남아있는 자투리 복호화데이터를 뽑는다.

// 이부분에서 비밀번호가 올바른지, 복호화가 잘 됐는지 체크할 수 밖에 없다.

if (1 != EVP_DecryptFinal_ex( ctx, decBuf, &outLen ) ) {

fprintf( stderr, "error while final can be invalid password, or corrupted\n" );

   goto last;

}

decrypted->append( (char*)decBuf, outLen );

     

ret = true;

last:

if( ctx ) {

EVP_CIPHER_CTX_free( ctx );

}

return ret;


}


주의해야할 점은 

1. 예제 코드에서도 언급 했듯이, EVP_DecryptUpdate를 사용할 경우 dec버퍼가 enc의 입력값보다 16byte가 더 커야 한다.(aes-256-cbc 일 경우16bytes)

2. 구버전의 경우 sha256이 아닌 md5로 해시를 지정해야 한다.



순서는 

1. key와 iv를 만들고 

2. cipher context를 초기화 

3. 모든 암호화 데이터가 들어갈 때까지 EVP_DecryptUpdate

4. EVP_DecryptFinal_ex 로 마무리하면 된다.




다음의 함수는 위의 함수에 파일의 내용을 전달하기 위한 함수이다.

bool decrypt_def_openssl_encoded_file( std::string* decrypted, const char* szEncFileName, const char* szPassword ) {

bool ret = false;

uint8_t* encContent = NULL;

FILE* pf = NULL;

size_t fileSize;

pf = fopen( szEncFileName, "rb" );

if( !pf ) {

fprintf( stderr, "can't open file %s", szEncFileName );

goto last;

}

// 메모리 할당으 위해 파일의 크기를 구한다.

fseek( pf, 0, SEEK_END );

fileSize = ftell( pf );

encContent = (uint8_t*)malloc( fileSize );

  // 원래 위치로 되돌리고

fseek( pf, 0, SEEK_SET );

// 몽땅 읽어버린다.

fread( encContent, 1, fileSize, pf );

ret = decrypt_def_openssl_encoded_content( decrypted, encContent, fileSize, szPassword );

last:

if( encContent ) {

free( encContent );

}

if( pf ) {

fclose(pf);

pf = NULL;

}

return ret;


}

그냥 몰빵으로 읽어서 전달하는 방식으로 만들었다.

만약 복호화할 파일이 크다면, 위 두개의 함수를 적절히 섞어서 변경하길 바란다.




C/C++ 코드다 보니 적절한 Library를 링크해줘야 하는 데,... (안그러면 undefined symbol 이 뜰 테니..)

이게 워낙에 다채로워서??



다음의 것들을 모두 시도해보기 바란다. 필자는 cygwin에서는 -lcrypto로 링크하였다.

-lssl

-lopenssl

-lcrypto




만약 -iter 나 -pbkdf2 를 지정하였다면 위의 EVP_BytesToKey함수 대신에 다음과 같이 적절히 교체해서 사용하면 된다.


int iter = 1; // 암호화시 지정한 횟수

uint8_t keyiv[32+16];


  PKCS5_PBKDF2_HMAC( password, strlen(password), salt, sizeof(salt), iter, EVP_sha256(), sizeof(keyiv), keyiv );

 

  uint8_t* key = keyiv;

  uint8_t* iv  = keyiv + 32;



기본 openssl명령은 PKCS5_PBKDF2_HMAC 함수를 이용해서 한번에 key와 iv를 만들어 나눠서 사용하고 있다.


(에궁.. 그리고보니 필자의 작업중에 잘못 만든 부분이 떠올랐다....아 그거 바꾸면, 10GiB데이터 다 바꿔야 하는데..ㅠㅠ)

'openssl' 카테고리의 다른 글

복호화(PHP) : aes256 cbc openssl  (4) 2020.03.23
복호화(C/C++) : aes256 cbc openssl  (0) 2020.03.23
명령줄 : aes256 cbc openssl  (0) 2020.03.23
Replies
Reply Write