OpenSSL은 프로젝트 이름이고, 내부적으로 크게 libcrypto, libssl로 두 가지가 있다.
libcryptoAES, RSA, SHA, ECC, HKDF, RAND 등 네트워크(TLS)와 관련 없는 순수 암호 라이브러리이다.libsslTLS/SSL 프로토콜 구현을 위한 HTTPS, TLS handshake, 인증서 검증 등 네트워크 보안을 위해 사용하는 부분으로, 내부적으로libcrypto를 사용한다.
용어 정리
Algorithms(알고리즘). SHA256이나 AES와 같은 암호학적 기본요소를 일컫는 용어다. 각 알고리즘 여러 구현을 가질 수 있다.알고리즘에 대한 서로 다른 구현은 서로 다른 provider에 속한다.
Provider. provider는 알고리즘에 대한 구현들을 구현 방식에 따라 분류한 묶음이다. 예를 들어, default provider는 일반적으로 사용하는 구현들이 담겨있고, fips provider는 FIPS 140 인증을 받은 구현들이 있다. 따라서 하나의 SHA256 알고리즘이더라도 기본 구현은 default provider에 있고, FIPS 인증을 받은 구현은 fips provider에 있을 수 있다.
Operations. 서로 다른 알고리즘들은 목적에 따라 분류될 수 있다. 예를 들어 암호화를 위한 알고리즘들이 있고, 메시지 다이제스트를 위한 알고리즘들이 있다. 이러한 묶음을 operation이라고 한다.
Fetching. 알고리즘에 대한 구현을 하나 가져오는 과정이다. Fetching은 인터넷을 통해 다운받는 것이 아니라, 이미 설치된 라이브러리 내의 여러 구현 중 가져온다. 명시적으로 구현체를 지정하는 explicit fetching과 아무거나 가져오는 implicit fetching이 있다. Implicit fetching은 이전 버전과의 호환을 위한 부분으로, explicit fetching이 권장된다.
Context(컨텍스트). 컨텍스트는 설정이나 상태 값들의 모임이다. OSSL_LIB_CTX와 같은 라이브러리 컨텍스트는 라이브러리 운용에 관련된 정보를 담는다. EVP_MD_CTX와 같은 연산 컨텍스트는 암호 연산에 대한 실행 상태를 담는다. 예를 들어 어떤 알고리즘 구현체를 사용하는지, 중간 계산 상태 등이 저장된다.
예제 분석
#include <stdio.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/err.h>
int main(void)
{
EVP_MD_CTX *ctx = NULL;
EVP_MD *sha256 = NULL;
const unsigned char msg[] = {
0x00, 0x01, 0x02, 0x03
};
unsigned int len = 0;
unsigned char *outdigest = NULL;
int ret = 1;
ctx = EVP_MD_CTX_new();
if (ctx == NULL)
goto err;
sha256 = EVP_MD_fetch(NULL, "SHA256", NULL);
if (sha256 == NULL)
goto err;
if (!EVP_DigestInit_ex(ctx, sha256, NULL))
goto err;
if (!EVP_DigestUpdate(ctx, msg, sizeof(msg)))
goto err;
outdigest = OPENSSL_malloc(EVP_MD_get_size(sha256));
if (outdigest == NULL)
goto err;
if (!EVP_DigestFinal_ex(ctx, outdigest, &len))
goto err;
BIO_dump_fp(stdout, outdigest, len);
ret = 0;
err:
OPENSSL_free(outdigest);
EVP_MD_free(sha256);
EVP_MD_CTX_free(ctx);
if (ret != 0)
ERR_print_errors_fp(stderr);
return ret;
}
OpenSSL을 사용해 0x00010203을 SHA256으로 해시한 결과를 출력하는 프로그램이다.
EVP_MD_CTX *ctx = NULL;
EVP_MD *sha256 = NULL;
const unsigned char msg[] = {
0x00, 0x01, 0x02, 0x03
};
unsigned int len = 0;
unsigned char *outdigest = NULL;
int ret = 1;
EVP_MD_CTX *ctx.EVP_MD_CTX는 메시지 다이제스트(MD) 연산을 수행할 때 가지고 가는 정보를 담는 객체이다.ctx는 이러한 객체를 가리키는 포인터로,NULL로 초기화 된다.EVP_MD *sha256.EVP_MD에는 실제로 메시지 다이제스트를 수행하는 구체적인 구현의 타입이다. 따라서sha256포인터는 구체적인 메시지 다이제스트 구현을 가리키는 포인터이다.const unsigned char msg[]. 해시를 수행할 대상 데이터이다.unsigned int len. 해시를 수행하고 수행한 길이를 반환하기 위해 그 정보를 담아둘 변수이다. OpenSSL에서는unsigned int타입을 많이 사용해서 그걸 썼다.unsigned char *outdigest. 해시의 결과물을 담을 포인터이다.int ret = 1. 반환 값을 담을 변수로, 기본적으로 오류를 나타내는1로 초기화 된다.
ctx = EVP_MD_CTX_new();
if (ctx == NULL)
goto err;
메시지 다이제스트의 컨텍스트를 하나 새로 생성해 EVP_MD_CTX * 타입 변수 ctx로 받는다.
이 컨텍스트에는 어떤 해시 알고리즘을 사용하는지, 입력으로 들어온 데이터의 상태 등을 관리한다.
sha256 = EVP_MD_fetch(NULL, "SHA256", NULL);
if (sha256 == NULL)
goto err;
EVP_MD_fetch(libctx, name, propq) 형태이다.libctx는 어떤 provider가 로드되어 있는지, 캐시, 설정 등을 담는다. NULL을 전달해 기본 컨텍스트를 전달한다.
여기에 사용된 컨텍스트는 앞서 생성한 메시지 다이제스트 컨텍스트와는 다르다. 메시지 다이제스트 컨텍스트는 해시 연산을 위한 값들이고, 여기에 사용되는 컨텍스트는 라이브러리 전체 설정에 대한 컨텍스트이다.
두 번째 name에는 "SHA256"이 전달되었는데, 이 알고리즘에 대한 구현을 가져올 것을 요청한 것이다.
세 번째 propq는 property query string으로, 어떤 provider의 구현을 가져올지 강제한다.
결과적으로 EVP_MD_fetch(NULL, "SHA256", NULL)는 기본 컨텍스트에서 SHA256에 대한 구현 아무거나 하나를 가져오게 된다.
if (!EVP_DigestInit_ex(ctx, sha256, NULL))
goto err;
EVP_DigestInit_ex는 컨텍스트에 특정 구현체를 사용할 것을 저장한다.
여기서는 이전에 fetch해온 sha256 구현체를 사용할 것을 ctx에 저장한다.
if (!EVP_DigestUpdate(ctx, msg, sizeof(msg)))
goto err;
EVP_DigestUpdate는 컨텍스트에 다이제스트를 수행할 데이터를 추가하는 함수다.ctx에 msg로부터 sizeof(msg)만큼의 데이터를 가져와 ctx에 추가한다.
아직 데이터만 추가된 것이지 해시가 수행된 것은 아니다.
outdigest = OPENSSL_malloc(EVP_MD_get_size(sha256));
if (outdigest == NULL)
goto err;
EVP_MD_get_size(sha256)과 같이 구현체에 사용하면 해시 알고리즘의 출력 크기를 얻을 수 있다.
얻은 출력 크기만큼을 OPENSSL_malloc을 통해 메모리를 할당하여 outdigest에 저장한다.
if (!EVP_DigestFinal_ex(ctx, outdigest, &len))
goto err;
EVP_DigestFinal_ex는 지금까지 컨텍스트에 누적한 데이터에 대해 최종 다이제스트를 계산하여 출력 메모리에 쓴다.
여기서는 outdigest의 메모리 공간에 쓰이며, 쓴 바이트 수를 len에 저장한다.
BIO_dump_fp(stdout, outdigest, len);
최종 결과물이 담긴 outdigest를 표준출력에 출력한다.
ret = 0;
err:
OPENSSL_free(outdigest);
EVP_MD_free(sha256);
EVP_MD_CTX_free(ctx);
if (ret != 0)
ERR_print_errors_fp(stderr);
return ret;
ret = 0. 여기까지 정상적으로 진행 되었다면goto err을 하지 않았기 때문에 반환 값을0으로 설정한다.err:. 이후err:지점을 통과하는데, 성공이든 실패이든 모든 경우에 이 지점은 통과한다. 하지만 만일goto err을 통해 왔다면ret = 1일 것이며, 그렇지 않다면ret = 0일 것이다.OPENSSL_free(outdigest). 앞서OPENSSL_malloc으로 할당한 메모리를 해제한다.EVP_MD_free(sha256). fetch로 얻은 구현체 객체는 free를 통해 해제해줘야 한다.EVP_MD_CTX_free(ctx). 컨텍스트도 마찬가지로 free해준다.if (ret != 0) ERR_print_errors_fp(stderr). 오류를 통해 왔다면 오류 내용을 출력한다.return ret. 최종ret값을 반환하고 프로그램을 종료한다.
참고문헌
'컴퓨터 > 암호학' 카테고리의 다른 글
| [2025 암호분석경진대회] A5-GMR-1 암호문 단독 공격(4번 문제) 회고 (0) | 2026.04.02 |
|---|---|
| [OpenSSL] AES-GCM 데모 프로그램 분석 (1) | 2026.01.19 |