Prototype Ghost
귀신일지
Prototype Ghost
전체 방문자
오늘
어제
  • 분류 전체보기 (29)
    • Hacking (1)
      • CTF (0)
      • Wargame (0)
      • Review (1)
    • C (12)
      • Crypto (12)
    • C++ (4)
    • Python (1)
    • Swift (1)
    • Blockchain (9)
      • Solidity (9)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 암호화
  • 비트코인
  • ethereum
  • 프로그래밍
  • 암호화폐
  • 복호화
  • 정보보안
  • 솔리디티
  • 이더리움
  • openssl
  • solidity
  • C++
  • 보안
  • 접근제어
  • 리눅스
  • C
  • 블록체인
  • Blockchain
  • c언어
  • Bitcoin

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Prototype Ghost

귀신일지

Crypto Project : 1-2. 블록암호운용모드 ECB를 이용한 CBC, CTR 모드 구현
C/Crypto

Crypto Project : 1-2. 블록암호운용모드 ECB를 이용한 CBC, CTR 모드 구현

2023. 4. 13. 20:38

시작하며


  이제부터 본격적으로 크립토 라이브러리의 구현에 대해 다뤄보고자 한다. 암복호화에 관련된 함수들은 openssl을 이용하여 구현하였고 블록암호운용모드에 대한 코드들을 직접 작성하였다. 이 라이브러리에선 CBC와 CTR, 두개의 모드를 지원하였고 CTR은 스트림 암호도 가능한 것으로 알고있으나 여기선 16바이트 블록단위의 암호화 용으로만 사용하였다. 이번 글에선 이 두개의 운용모드를 어떻게 함수로써 구현하였는지 설명하겠다.

 

1. Concept


  이 라이브러리에서 CBC와 CTR을 구현하기 위해 openSSL에서 제공하는 ECB함수를 사용한다. ECB에 패딩을 붙이지 않고 무조건 16바이트의 데이터만 파라미터로 넘겨준다면 정확하게 한 블록을 암호화하는 함수로써 ECB를 사용할 수 있을 것이다. 다음은 간단하게 표현한 ECB 함수이다.

ECB 암호화

  위 그림은 대략적인 ECB 모드의 그림이다. 각 블록별로 개별적으로 암호화 처리를 한다. 각 블록단위의 평문을 인풋으로 받아 아웃풋으로 그 블록에 암호문을 만들어낸다. 이 떄 이 라이브러리에서 암호화 알고리즘은 AES128로 설정하였다. 그렇다면 16바이트 단위로 암호화하는 ECB가 있다고 가정할 때, 이 ECB에 패딩옵션을 넣지 않고 무조건 16바이트만 인풋으로 넣어준다면, 16바이트의 암호문을 아웃풋으로 내는 한블록짜리 암호화 함수로써 ECB를 사용할 수 있을 것이다. 즉 본인이 구현한 CBC, CTR 함수는 내부에서 이 ECB 함수를 암호화 함수로써 사용한다. 이해를 돕기 위해 CBC를 예시로 들어보겠다. 

CBC 암호화

  이전 암호문을 현재 평문가 xor하여 암호화를 진행하는 CBC함수를 간단하게 그린 것이다. 이 때, 그림에서 암호화를 진행하는 부분, 저 부분에서 우리는 ECB함수를 암호화 함수로써 사용한다는 것이다. 그 외 복호화 또 CTR 모드 같은 경우 궁금할 경우 인터넷을 찾아보길 바란다. 아무튼 ECB함수를 이용해 CBC와 CTR을 구현한다는 것을 이해했다고 믿고 다음으로 넘어가겠다. 

 

2. 구현


먼저 CBC부터 살펴보자

//ecb함수를 이용해 cbc방식 암호화를 구현
I_LOCAL void enc_ecb_to_cbc(int p_cipher_id, //enc cbc_using_ecb
	AES_KEY* p_key,
	uint8_t* p_input,
	uint32_t 	p_inputlength,
	uint8_t* p_output,
	uint32_t* p_outputlength,
	uint8_t* p_iv,
	uint32_t    p_ivlength) {

	uint8_t      block[16]; //plain_text xor pre_enc_data
	uint32_t     blocklength = 16; //block 길이
	uint32_t     lastBlockLength = p_inputlength % blocklength;//블록단위로 나눈 후 남은 데이터 길이 ex) blocklength : 16, input : 33 -> lastBlockLength : 1
	uint8_t      ecb_output[16] = { 0x00, };
	uint32_t     dataIndex = 0; //block의 길이만큼 증가하는 데이터의 인덱스

	for (int i = 0; i < p_ivlength; i++)
		block[i] = p_input[i] ^ p_iv[i];

	//마지막 블록 전
	while ((int)dataIndex <= ((int)p_inputlength - (int)blocklength)) {
		if (dataIndex == 0)//first enc...Ek(p ^ iv)
			AES_ecb_encrypt(block, ecb_output, p_key, AES_ENCRYPT);
		else {//after first enc.... Ek(P(i) ^ C(i-1))
			for (int i = dataIndex; i < blocklength + dataIndex; i++)//plain_text xor pre_enc_data
				block[i % blocklength] = ecb_output[i % blocklength] ^ p_input[i];  //i%blocklength = 0~15
			AES_ecb_encrypt(block, ecb_output, p_key, AES_ENCRYPT);
		}
		for (int i = dataIndex; i < blocklength + dataIndex; i++)//put ecb_enc_data to output
			p_output[i] = ecb_output[i % blocklength];
		*p_outputlength += blocklength;

		dataIndex += blocklength; // 블록단위로 인덱스 갱신
	}
}

  라인 넘버가 없어서 굉장히 설명하기 힘든데 간단하게 말해서 이 enc_ecb_to_cbc 함수는 ecb를 이용해 cbc를 구현한 함수이다. 이 함수는 매개변수 p_output에 결과를 저장한다.  처음에 p_input과 p_iv(cbc는 처음 이전블록이 없기에 초기벡터 iv와 평문을 xor한다.)를 xor 하여 block에 담고 이를 AES_ecb_encrypt를 사용해 암호화 한 후 이를 ecb_output와 p_output에 담는다. 이후부턴 iv가 아닌 이전 암호문을 담고있는 ecb_output과 p_input을 xor하며 동일하게 AES_ecb_encrypt를 이용해 암호화한다. 이 함수는 앞에 I_LOCAL이라는 키워드가 있는데 이 함수가 외부 노출 함수가 아니기 때문이다. 이 함수는 다음 글에서 다룰 i_enc라는 함수 내부에서만 동작하며, 사용자는 이 함수를 직접 호출하는 것이 아니라 무조건 i_enc를 호출해야하기 때문에 extern이 아닌 로컬 함수라는 것을 의미하는 키워드이다. 실제로 원래라면 마지막에 패딩을 붙이고 암호화를 진행해야 하지만 이 함수에서는 패딩을 붙이는 기능이 없다. 왜냐하면 이 함수를 돌고 난 후 i_enc에서 패딩처리를 하기 때문이다. 이렇게 이 함수는 자체에서는 패딩을 처리를 하지 않는데 이를 사용자가 그냥 쓰게 한다면 분명 문제가 생길 것이다. 즉 이는 내부에서만 쓰고 밖에서 쓸 이유가 없기 때문에 사용자에게 감춰야할 이유가 충분하다. 이 프로젝트를 진행하면서 공개할 코드, 그렇지 않은 코드를 구별하는 법을 확실하게 알 수 있었다.  복호화 함수가 궁금하다면 맨 밑에 깃 링크에서 전체 코드를 볼 수 있으니 참고하기 바란다. 

 

  넘어가서 다음은 CTR모드이다 CTR 모드에서 가장 관건은 카운터 값을 증가시키는 것이다. 코드를 보자.

I_LOCAL void i_inc_counter(uint8_t* counter, uint32_t counterlength){
	//ctr is big-endian
	//openssl에서 16바이트 counter를 uint32(4바이트)단위로 4개씩 끊어 증가 시키기 때문에
	for(int i=counterlength-sizeof(uint32_t);i>=0;i-=sizeof(uint32_t)){
		uint32_t value = (*((uint32_t*)(counter+i)))++;
		uint32_t carry = !value;
		if(carry == 0) return;
	}
}

  이 함수는 CTR모드에서 counter값을 증가시키기 위한 함수이다. 카운터가 총 16바이트인데, 이를 uint32의 사이즈 4바이트 단위로 나눠서 카운터를 증가시킨다. 즉 인덱스 0~3, 4~7, 8~11, 12~15 씩 끊는다. 이때, 리틀엔디안이라면 값을 분명 0~3범위를 먼저 증가시켜야겠지만, 실제 openssl에서 확인해본 결과, 맨 뒤 12~15범위에서 먼저 값을 증가시키는 것을 알게 되었다.

반복문에서 i가 counterlength-sizeof(uint32_t) 로 초기화 되는데, 이를 계산하면 12이다. 카운터모드의 표준이 빅엔디안이기 때문에 맨 마지막 구간부터 값을 올리는 것이다. 값을 증가시키며 캐리가 발생하면 다음 구간으로, 즉 4바이트를 뺀 곳으로 이동하여 값을 증가시키고, 캐리가 발생하지 않으면 바로 리턴을 한다. 4바이트씩 끊은 이유는 결국 1씩 증가시키면서 uint32 범위를 벗어나면, 즉 4바이트가 꽉 찬다면, 캐리가 발생하는 것이기 때문이다. 이 때 4바이트가 맥시멈인 상태에서 값이 증가하면 해당 4바이트는 0이 될 것이며, 그렇게 되면 carry는 반대로 1이 되어 함수가 종료되지 않고 반복을 한 번 더 돌게 되는 원리이다.

여러 잡기술들이 섞여있는데 조금만 생각하면 코드가 금방 이해가 될 것이다. 이제 카운터값을  증가시키는 함수를 만들었기 때문에 CTR 함수는 금방 구현할 수 있다. 

I_LOCAL void enc_ctr_mode(int p_cipher_id,
	AES_KEY* p_key,
	uint8_t* p_input,
	uint32_t 	p_inputlength,
	uint8_t* p_output,
	uint32_t* p_outputlength,
	uint8_t* p_counter,
	uint32_t p_counterlength) {

	uint8_t      block[16];
	uint32_t     blocklength = 16;
	uint8_t      ecb_output[16] = { 0x00, };
	uint32_t     dataIndex = 0; //block의 길이만큼 증가하는 데이터의 인덱스
	uint32_t	 blockNum = p_inputlength / blocklength;

	//마지막 블록 전까지
	for (int i = 0; i < blockNum; i++) {
		AES_ecb_encrypt(p_counter, ecb_output, p_key, AES_ENCRYPT);
		i_inc_counter(p_counter, p_counterlength);
		for (int j = dataIndex; j < blocklength + dataIndex; j++)
			p_output[j] = ecb_output[j % blocklength] ^ p_input[j];
		*p_outputlength += blocklength;
		dataIndex += blocklength;
	}
}

  CTR모드가 정확히 어떻게 동작하는지 궁금하다면 검색을 통해 확인해보길 바란다. (그림그리기가 귀찮아서 위에 cbc까지만 그렸슴다ㅠ)

CTR은 평문부터가 아니라 카운터 값을 먼저 암호화하고 이 암호화한 결과를 평문과 xor한다. 코드도 보면 먼저 p_counter를 AES_ecb_encrypt로 암호화 한 후 이 결과를 p_input과 최종적으로 하여 p_output에 넣는 것을 볼 수 있다. 이 때 암호화 함수를 호출하여 카운터 값을 암호화 했다면 바로 밑에 i_inc_counter 함수를 호출해 카운터값을 증가시키는 것을 확인할 수 있다. 

  위의 두 함수도 다른 함수 내부에서만 동작하고 사용자가 직접 호출하지 못하는 함수이므로 I_LOCAL 키워드가 붙은 것을 알 수 있다. 이 다음글에서는 이들 함수를 사용하는 i_enc와 i_dec 함수에 대한 글을 작성하겠다.

 

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

 

728x90
반응형

'C > Crypto' 카테고리의 다른 글

Crypto Project : 1-5. 암복호화 init, update, final 구현  (0) 2023.04.16
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-1. i_crypto library - 구조 및 문서화 예시  (0) 2023.04.11
Crypto Project : Prologue  (0) 2023.04.10
    'C/Crypto' 카테고리의 다른 글
    • Crypto Project : 1-4. C언어에서 멤버변수 접근을 막는 방법과 init, update, final의 개념
    • Crypto Project : 1-3. 암호화, 복호화 함수(enc, dec)
    • Crypto Project : 1-1. i_crypto library - 구조 및 문서화 예시
    • Crypto Project : Prologue
    Prototype Ghost
    Prototype Ghost

    티스토리툴바