기본 콘텐츠로 건너뛰기

데이터에 서명 넣기

출처 : 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");
}

댓글

이 블로그의 인기 게시물