출처 : http://ssabro.tistory.com/35
OpenSSL기반 사인 값 생성 및 검증 C 예제 코드
1. 개요
일반적으로 OpenSSL을 사용하여 프로그래밍을 할 경우 서버 클라이언트 모델에서 보다 안전한 통신을 위해 해당 라이브러리를 사용한다. 하지만 OpenSSL은 보안 통신뿐만 아니라 데이터 암/복호화 등에도 많이 사용되며, 이와관련 다양한 API를 제공하고 있다. 본 문서에서는 인증서 기반의 데이터에 대한 사인값를 생성하고 이를 검증할 수 있는 C기반의 예제 코드를 설명한다.
2. 관련지식
각 사용자는 공개키 쌍을 가지게 되며 이 쌍은 개인키, 공개키로 구성된다. PKI 체계에서는 사용자의 공개키는 인증서로 관리되며 이 인증서는 사용자 공개키의 유효성(이 공개키가 정말 사용자의 공개키 인지 여부 등)을 보증할 수 있는 CA로부터 발급받는다. 실제 인증서는 해당 사용자의 공개키 + CA의 서명 값 + 유효기간 등의 정보가 포함된 데이터이다.
생성자 : 생성자의 개인키(생성자만이 알 수 있는 키)
검증자 : 생성자의 공개키(누구나 알 수 있는 키)
이러한 PKI 체계에서 일반적으로 사인값을 생성하는 방법은 다음과 같다.
(1) 사인값 생성자는 먼저 메시지로부터 해시(Hash)값을 생성한다.
(2) 사인값 생성자는 생성된 해시값을 개인키로 암호화 한다. = 사인 값
검증은 이와 역순으로 진행된다.
(1) 사인값 검증자는 사인값(=생성자의 개인키로 암호화된 값)을 생성자의 공개키로 복호화 한다.
(2) 사인값 검증자는 복호화한 값이 수신한 메시지의 해시 값과 일치하는지 확인한다.
(3) 같을 경우 사인값 = O, 다를 경우 사인 값 = X
해시 알고리즘에는 MD5, SHA1, SHA-256 등이 있지만, 최근에는 안전성의 이유로 SHA1, SHA-256 사용을 권장하고 있으며 이 중에서도 SHA-256을 보다 더 권장하고 있다. 서명 알고리즘으로는 RSA, ECDSA이 존재하며 안전성의 이유로 보안강도 112 bits가 되도록 RSA의 경우 2048 bits, ECDSA의 경우 224-225 bits 이상을 권고하고 있다.
3. OpenSSL 사인 값 생성 및 검증 코드 예제
본 문서에서는 SHA-256을 이용해 해시값을 생성하고 RSA 기반의 서명값을 생성 및 검증하는 예제코드는 다음과 같다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>
#include <openssl/rsa.h>
void main( )
{
char *str = "hello message"; //원본 메시지
RSA *pri_key = NULL; //서명자 개인키
RSA *pub_key = NULL; //서명자 공개키
EVP_PKEY *e_pub_key = NULL; //서명자 인증서
FILE *key_f = NULL; //서명자 개인키 파일 포인터
FILE *crt_f = NULL; // 서명자 인증서 파일 포인터
int sign_len; // 사인값 길이(개인키 암호화된 결과값 길이)
unsigned char sign[256]; //사인값이 담길 버퍼(개인키로 암호화된 결과값이 담길 버퍼)
int d_sign_len; // 사인값 복호화 길이
unsigned char d_sign[256]; // 사인값 복호화된 값이 담길 버퍼
/* SHA-256 알고리즘 기반의 메시지 해시값 생성 시작 */
SHA256_CTX sha256; //SHA1의 경우 : SHA_CTX sha1;
char sha256_str[SHA256_DIGEST_LENGTH]; //SAH1의 경우 : char sha1_str[SHA_DIGEST_LENGTH];
SHA256_Init(&sha256); //SHA1의 경우 : SHA1_Init(&sha1);
SHA256_Update(&sha256, str256, strlen(str)); //SHA1의 경우 : SHA1_Update(&sha1, str, strlen(str));
SHA256_Final(sha256_str, &sha256); //SHA1의 경우 : SHA1_Final(sha1_str, &sha1)
/* SHA-256 알고리즘 기반의 메시지 해시값 생성 완료 */
/* 사인값 생성을 위한 사용자 개인키 로드 시작 */
key_f = fopen("user.key", "r"); // 개인키 파일 오픈
if( key_f == NULL ) {
printf("File Open user.key error\n");
return 0;
}
pri_key = PEM_read_RSAPrivateKey(key_f, NULL, NULL, NULL); // 파일로부터 RSA용 개인키를 로드한다.
if( pri_key == NULL) {
printf("Read Private Key for RSA Error\n");
goto shutdown;
}
memset(sign, 0x00, sizeof(sign) ); //버퍼 초기화
//로드한 RSA용 개인키를 이용하여 해시값을 서명한다.
sign_len = RSA_private_encrypt( sizeof(sha256_str), sha256_str, sign, pri_key, RSA_PKCS1_PADDING);
/*
리턴값 : 서명값 길이
입력값(순서대로) : 암호화할 입력값(=해시값)길이, 암호화할 입력값, 암호화한 결과가 담길 버퍼, 개인키, PADDING 유무
Padding과 관련된 이슈는 PKCS #1 표준 참고
*/
if( sign_len < 1 ) {
printf("rsa private encrypt error\n");
goto shutdown;
}
/* 사인값 검증을 위한 사용자 인증서 로드 시작 */
crt_f = fopen("user.crt", "r"); //사용자 인증서 파일 오픈
if( crt_f == NULL ) {
printf("fopen user.crt error\n");
goto shutdown;
}
X509* user_x509 = NULL;
user_x509 = PEM_read_X509(crt_f, NULL, NULL, NULL); //사용자 인증서 파일로부터 x509 타입 데이터 읽기
/*
설명 : x509는 인증서 포맷으로 현재는 ITU-T 표준기구에서 표준화한 것으로 현재 대부분의 인증서는 x509v3 기반의 인증서를 사용하고 있다.
*/
if( user_x509 == NULL ) {
printf("PEM read x509 form user.crt error\n");
goto shutdown;
}
e_pub_key = X509_get_pubkey(user_x509); // 사용자 x509v3 타입의 인증서로부터 공개키를 추출한다.
if( e_pub_key == NULL ) {
printf("get public key from input X509 error\n");
goto shutdown;
}
pub_key = EVP_PKEY_get1_RSA(e_pub_key);// 추출한 공개키로부터 RSA용 공개키를 가져온다.
if (pub_key == NULL) {
printf("get rsa from public key error\n");
goto shutdown;
}
memset(d_sign, 0x00, sizeof(d_sign) );
d_sign_len = RSA_public_decrypt(sign_len, sign, d_sign, pub_key, RSA_PKCS1_PADDING); // 사인값을 사용자의 공개키로 복호화 한다.
if(d_sign_len < 1) {
printf("RSA public decrypt error\n");
goto shutdown;
}
int result;
result = memcmp( d_sign, sha256_str, d_sign_len); //복호화한 값이 메시지 해시값과 같은지 확인한다.
if( !result ) printf("OK!\n");
else printf("Fail!\n");
}
|
댓글
댓글 쓰기