시작하며
지난 글에서 init, update, final의 개념과 C언어에서 구조체 멤버변수의 접근을 막는 방법 등에 대해 다루었다. 이번 글에서는 이 세개의 인터페이스의 구현을 살펴보겠다. 혹시 이전글을 보고 오지 않았다면 꼭 보고 오는 것을 추천한다.
1. context 구조체를 다루는 함수, new, reset, free
우선 세 개의 함수를 설명하기 전에 지난 글에서 잠깐 봤던 ctx 구조체를 다루기 위해 사용자에게 제공해야 하는 함수에 대해 설명했었다. 이는 구현부로, new로 구조체를 할당해주고, reset은 말그대로 reset, free는 할당한 구조체를 해제해주는 것으로, 이러한 인터페이스를 제공함으로써 사용자가 멤버변수에 접근을 못하더라도 구조체를 이용할 수 있게끔 해야 한다.
2. init 함수
다음은 init 함수이다. 매개변수 값을 바탕으로 ctx 구조체를 초기화 해주는 역할을 한다. 사실 그냥 초기화 함수이기 때문에 딱히 살펴볼건 없고 지난번에 잠깐 봤던 ctx구조체의 멤버변수가 각각 어떤 역할을 하는지 알아보겠다.
현재 상태를 저장하는 ctx 구조체에는 먼저 암호알고리즘이 뭔지 저장하는 cipher_id(이 라이브러리는 aes_128만 지원한다.) aes 키를 담는 key, 그리고 buffer와 buffer 사이즈가 있는데 이게 굉장히 중요하다. 결국 init, update, final은 최종적으로 final을 호출해야만 패딩처리를 완료하고 하나의 암호화를 마무리한다. 그렇다면 16바이트 단위로 암호화가 되는데 만약 24바이트를 update했다고 가정해보자.
그렇다면 16바이트는 암호화 되지만 8바이트는 암호화가 되지 않는다. 기존 enc함수라면, 이 남은 8바이트에 8바이트 패딩을 붙여 암호화를 진행했겠지만, init, update, final은 이 이후에 더 암호화할 평문이 나올수도 있고 final전에는 절대 패딩 처리를 하지 않기 때문에, 16바이트가 안되는 나머지 데이터를 저장하고 있어야 한다. 그 역할을 하는 것이 buffer이며 버퍼에 8바이트 남은 데이터가 남아있다면 이 이후에 오는 데이터를 버퍼에 넣어 16바이트가 되면 암호화를 진행하는 방식이다.
lastDecBlock은 마지막 복호문을 저장하고 있어야 원활한 패딩처리를 할 수 있기때문에 필요한 변수이고 param변수는 이전에 말한 암복호화에 필요한 iv 등과 같은 변수들이 들어있다.
3. update
I_EXPORT int i_enc_update(I_CIPHER_CTX* p_context, uint8_t* p_input, uint32_t p_inputlength, uint8_t* p_output, uint32_t* p_outputlength) {
int ret = 0;
uint8_t block[16]; //plain_text xor pre_enc_data
uint32_t blocklength = 16; //block 길이
uint32_t remainLength = 0; //암호화하고 남은 input 데이터의 길이
uint32_t index = 0;
uint32_t remainDataStartIndex = 0;//암호화하거나 버퍼에 쌓을 input 데이터의 시작점
ret = check_parameters("i_enc_update", &(p_context->key), &(p_context->param), p_input, p_output);
if(ret != 0) return ret;
*p_outputlength = 0;//update시 p_outputlength는 0으로 초기화
remainLength = p_inputlength;
index = p_context->bufferSize;
if (p_context->bufferSize != 0 || p_inputlength < blocklength) {//버퍼가 비어있지 않거나 input이 blocklength보다 작으면 버퍼부터 채운다.
for (int i = index; i < blocklength; i++) {
if (i-index == p_inputlength) break;
p_context->buffer[i] = p_input[i - index];
p_context->bufferSize++;
remainLength--;
remainDataStartIndex++;
}
}
while(p_context->bufferSize == blocklength || remainLength >= 16){//버퍼가 블록단위로 가득 찼거나, 블록에 넣고 남은 데이터가 blocklength 보다 크면 암호화 진행
switch (p_context->param.mode) {
case I_CIPHER_MODE_CBC:
if(p_context->bufferSize == blocklength){//우선 버퍼가 찼으면 버퍼부터 암호화
for(int i=0;i<p_context->param.ivlength;i++)//iv는 초기벡터이거나 이전 암호문
p_context->buffer[i] ^= p_context->param.iv[i];
AES_ecb_encrypt(p_context->buffer, p_output, &(p_context->key), AES_ENCRYPT);
p_context->bufferSize = 0;//버퍼는 암호화했으므로 0 초기화
}
else{//버퍼가 비어있고 남은 데이터가 16보다 길면
for(int i=remainDataStartIndex;i<remainDataStartIndex+blocklength;i++)//iv는 초기벡터이거나 이전 암호문
block[i - remainDataStartIndex] = p_input[i] ^ p_context->param.iv[i-remainDataStartIndex];
AES_ecb_encrypt(block, p_output + *p_outputlength, &(p_context->key), AES_ENCRYPT);
remainLength -= blocklength;
remainDataStartIndex += blocklength;
}
for (int i = 0; i < p_context->param.ivlength; i++)//첫블록 이후 iv는 이전 암호문
p_context->param.iv[i] = p_output[*p_outputlength + i];
break;
case I_CIPHER_MODE_CTR:
//CTR mode -> counter부터 암호화
AES_ecb_encrypt(p_context->param.iv, p_output + *p_outputlength, &(p_context->key), AES_ENCRYPT);
if (p_context->bufferSize == blocklength){
for(int i=0;i<blocklength;i++)
p_output[i] ^= p_context->buffer[i];
p_context->bufferSize = 0;
}
else{
for(int i=0;i<blocklength;i++)
p_output[i + *p_outputlength] ^= p_input[i + remainDataStartIndex];
remainLength -= blocklength;
remainDataStartIndex += blocklength;
}
//counter 증가
i_inc_counter(p_context->param.iv, p_context->param.ivlength);
break;
}
*p_outputlength += blocklength;//암호화가 되었다면 outputlength 증가
}
//버퍼에 남은 데이터 저장
for (int i = remainDataStartIndex; i < remainDataStartIndex + remainLength; i++) {
p_context->buffer[i - remainDataStartIndex] = p_input[i];
p_context->bufferSize++;
}
return ret;
}
라인 넘버가 없어 캡쳐로 하려 했지만 그러면 한 화면에 다 담기지 않아 코드 블록으로 복붙하였다. 최대한 잘 설명해 보겠다. 블록 단위로 보면 for문을 감싸고 있는 큰 if문이 있다. 이 블록이 하는 역할은 버퍼에 대한 처리인데, 이 함수는 만약 버퍼에 데이터가 있다면, 이전에 update하고 남은 잔여데이터가 있다는 것이기 때문에 우선 버퍼부터 16바이트가 찰때까지 채우고 버퍼부터 암호화를 한다. 그렇지 않고 만약 버퍼가 비어있고, input이 16이상으로 들어온다면, input을 암호화하고 그 후 남은 잔여 데이터를 버퍼에 채운다. 마지막 경우로 버퍼가 비어있는데 input이 16바이트보다 작아 암호화가 안된다면 input을 버퍼에 넣기만 하고 함수가 종료된다. 그것이 처음에 for문을 감싸고 있는 if문 블록 전체가 하고 있는 것이다.
두번째 while문은 결국 위에서 말한 경우의 수대로 처리하는 것이다. 버퍼가 차있다면, 버퍼부터 암호화한다. 혹은 input의 남은 길이를 계속 체크하면서 남은 길이가 16바이트 이상이라면 암호화를 진행하고 남은 데이터는 다시 버퍼에 채우고 마무리한다. 내부는 그러면서 암호화를 진행하는 코드이고 내부에서 cbc냐 ctr이냐에 따라 case를 나눠 진행하게 된다. 처음 구현할 때, 이 경우의 수를 따지면서 함수를 짜는 것이 어려웠지만, 정작 어떤 경우의 수가 있고 어떻게 처리하는 지를 천천히 생각한다면 쉽게 이해할 수 있을 것이다.
4. enc final
위의 init, update 는 암복호화가 비슷하게 이루어져 enc만 예로 들었지만 final은 패딩에 대한 것 때문에 조금 다르기 때문에 두개로 나눠서 글을 써보겠다.
첫번째 for문은 패딩에 대한 처리로, 우선 버퍼에 잔여 데이터가 남아있다면 block 배열에 잔여 데이터를 대입한다. 그러고 남은 데이터를 모두 넣었다면, blocklencth, 즉 16바이트에서 버퍼에 남아있던 잔여 데이터의 사이즈, 즉 buffer 사이즈를 빼면 그것이 곧 패딩값이 될것이고 이를 block에 대입한다. 이미 update에서 buffer가 꽉 찼을 때는 무조건 암호화가 진행됐을 것이므로 버퍼가 꽉 찬 것에 대한 처리는 필요가 없다. 버퍼가 아예 비었거나, 버퍼에 16바이트 미만의 남은 데이터가 있을 경우 처리하는 부분이라고 보면 된다. 그렇게 패딩 처리를 한다면 아래 switch-case에서 각 블록암호 모드에 맞게 처리를 한다.
5. dec final
dec final은 포인터로 p_paddinglength를 파라미터로 받아 패딩 검증을 거치면 이 p_paddinglength에 패딩 길이(이면서 동시에 패딩값)을 넣고 사용자는 이를 이용해 최종적으로 output의 길이에서 패딩 길이를 빼면서 복호문을 완벽하게 복호화할 수 있게된다. 여기서 dec update는 update할 때마다 마지막으로 복호화한 16바이트 복호문을 ctx 구조체의 lastDecBlock에 저장해두는데 이 lastDecBlock이 dec final을 하기 위해 필요한 변수였다. 만약 이것이 없었다면, output과 outputlength를 매개변수로 넘겨줘 매개변수 개수가 늘어나는 번거로움이 생길 것이다. 그렇기 때문에 편의를 위해 이런식으로 코드를 작성하게 되었다.
6. 마치며
이렇게 init, update, final을 설명함으로써, i_crypto_library에 대한 글이 끝났다. 사실 이 전에 썼던 enc와 dec도 내부에서 init, update, final을 한번에 호출하는 것으로 이들을 래핑하여 만들 수 있으나, cbc와 ctr을 직접 만들고 이 둘을 enc, dec로 래핑하는 것으로 구현했고 이 init, update, final도 내부에서 cbc와 ctr 함수 만든 것을 그대로 사용할 수 있으나, 그러면 매개변수들이 여럿 중복되고 사이즈 체크도 한 번 더하는 번거로움이 있어 그냥 ecb로 내부에서 자체 처리 하도록 하였다. 여러가지 장단이 있으며, 해당 라이브러리에 c소스파일에 init update final을 기존에 만든 cbc, ctr을 래핑하여 만든 버전의 함수도 맨 밑에 기술해놨으니 어떻게 다른지 직접 비교해봐도 좋을 것 같다. 이제 다음 글부턴 이렇게 만든 라이브러리를 활용하여 tls 키교환 방식을 모방하여 소켓 통신에 대하여 다루겠다.
github : https://github.com/0xGh-st/I_CRYPTO.git
GitHub - 0xGh-st/I_CRYPTO
Contribute to 0xGh-st/I_CRYPTO development by creating an account on GitHub.
github.com
'C > Crypto' 카테고리의 다른 글
Crypto Project : 2-1. 멀티쓰레드 기반 1대n 소켓 프로그램 (1) | 2023.04.25 |
---|---|
Crypto Project : 1-6. crypto library makefile과 테스트 (0) | 2023.04.23 |
Crypto Project : 1-4. C언어에서 멤버변수 접근을 막는 방법과 init, update, final의 개념 (0) | 2023.04.15 |
Crypto Project : 1-3. 암호화, 복호화 함수(enc, dec) (1) | 2023.04.14 |
Crypto Project : 1-2. 블록암호운용모드 ECB를 이용한 CBC, CTR 모드 구현 (0) | 2023.04.13 |