Dev/C, C++
OpenSSL 을 사용한 RSA
newtype
2017. 5. 30. 11:47
RSA
프로젝트를 진행하면서, github에 정리한 내용입니다.
https://github.com/lmk/HowToUseOpenSSL/blob/master/RAS.md
RSA 생성
PEM public 키로 RSA 생성하기
unsigned char *key = "PEM 형식의 public 키";
BIO *bio = BIO_new_mem_buf(key, -1);
RSA *rsa = PEM_read_bio_RSA_PUBKEY(bio, &rsa, NULL, NULL);
PEM private 키로 RSA 생성하기
unsigned char *key = "PEM 형식의 private 키";
BIO *bio = BIO_new_mem_buf(key, -1);
RSA *rsa = PEM_read_bio_RSAPrivateKey(bio, &rsa, NULL, NULL);
RSA 생성
int bits = 2048;
BIGNUM *bn = BN_new();
if ( BN_set_word(bn, RSA_F4) != 1 ) throw "BN_set_word fail";
RSA *rsa = RSA_new();
if ( RSA_generate_key_ex(rsa, bits, bn, NULL) != 1 ) throw "RSA_generate_key_ex fail";
인증서(.cer) 파일로 RSA 생성하기
int serial = 0;
int days = 365;
EVP_PKEY *pk = EVP_PKEY_new();
EVP_PKEY_assign_RSA(pk,rsa);
rsa = NULL;
X509 *x = X509_new();
X509_set_version(x, 2);
ASN1_INTEGER_set(X509_get_serialNumber(x),serial);
X509_gmtime_adj(X509_get_notBefore(x),0);
X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*days);
X509_set_pubkey(x,pk);
name=X509_get_subject_name(x);
X509_NAME_add_entry_by_txt(name,"C", MBSTRING_ASC, (const unsigned char*)"KR", -1, -1, 0);
X509_NAME_add_entry_by_txt(name,"ST", MBSTRING_ASC, (const unsigned char*)"Seoul", -1, -1, 0);
X509_NAME_add_entry_by_txt(name,"L", MBSTRING_ASC, (const unsigned char*)"Mapo", -1, -1, 0);
X509_NAME_add_entry_by_txt(name,"O", MBSTRING_ASC, (const unsigned char*)"company", -1, -1, 0);
X509_NAME_add_entry_by_txt(name,"OU", MBSTRING_ASC, (const unsigned char*)"test", -1, -1, 0);
X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, (const unsigned char*)"lmk", -1, -1, 0);
X509_set_issuer_name(x,name);
add_ext(x, NID_subject_key_identifier, "hash");
add_ext(x, NID_authority_key_identifier, "keyid:always");
add_ext(x, NID_basic_constraints, "CA:TRUE");
X509_sign(x,pk,EVP_sha1());
fp = fopen(".cer 경로", "wb");
d2i_X509_fp(fp, &x);
PEM_write_X509(fp, x);
fclose(fp);
EVP_PKEY_free(pkey);
X509_free(x);
// EVP_PKEY_assign_RSA() 하고 EVP_PKEY_free()했기 때문에 rsa는 free 할필요 없다.
PCKS12(.p12) 파일로 RSA 생성하기
char path[256] = ".p12 경로";
char passwd[128] = ".p12 파일 암호"
FILE* fp = fopen(path, "rb");
if(fp == NULL) throw "File open fail";
PKCS12 *pkcs12 = d2i_PKCS12_fp(fp, NULL);
if(pkcs12 == NULL) throw "PKCS12 load fail";
EVP_PKEY *pkey = NULL;
X509 *cert = NULL;
int result = PKCS12_parse(pkcs12, passwd, &pkey, &cert, NULL);
if ( result != 1 ) throw "PKCS12 parsing fail";
RSA *rsa = EVP_PKEY_get1_RSA(pkey);
if ( rsa == NULL ) throw "EVP_PKEY_get1_RSA fail";
DER 파일 읽기
int size = 0;
FILE* fp = NULL;
try {
fp = fopen(filename, "rb");
if ( fp == NULL ) throw L"File open error";
fseek(fp, 0, SEEK_END);
size = ftell(fp);
if ( size <= 0 ) throw L"File size is zero";
fseek(fp, 0, SEEK_SET);
if ( *out_len < size ) throw L"Out of memeory";
if ( fread(out, sizeof(unsigned char), size, fp) != size ) throw L"File read error";
*out_len = size;
}
catch(TCHAR *msg) {
MessageBox(msg);
isSuccess = false;
}
if ( fp ) fclose(fp);
DER 형식의 private key로 RSA 생성
DER 형식의 private key 생성 명령
openssl rsa -inform PEM -outform DER -in privatekey.pem -out privatekey.der
RSA 생성 코드
const unsigned char *key = { /*DER 형식의 private*/ };
const int key_len = 1192; // key의 길이
EVP_PKEY *pkey = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &key, key_len);
if ( pkey == NULL ) throw "RSA private Key read fail";
RSA *rsa = EVP_PKEY_get1_RSA(pkey);
if ( rsa == NULL ) throw "EVP_PKEY_get1_RSA fail";
DER 형식의 public key로 RSA 생성(294byte)
DER 형식의 public key 생성 명령
openssl rsa -pubin -in publickey.pem -inform PEM -pubout -out publickey.der -outform DER
RSA 생성 코드
RSA *rsa = d2i_RSA_PUBKEY(NULL, &key, key_len);
DER 형식의 public key로 RSA 생성(993byte)
DER 형식의 public key 생성 명령
openssl x509 -in ca.crt -pubkey -out ca_publickey.der -outform DER
RSA 생성 코드
X509 *cert = d2i_X509(NULL, &key, key_len);
if ( cert == NULL ) throw "RSA public key read fail";
EVP_PKEY *pkey = X509_get_pubkey(cert);
if (pkey == NULL) throw "public key getting fail";
int id = EVP_PKEY_id(pkey);
if ( id != EVP_PKEY_RSA ) throw "is not RAS Encryption file";
RSA *rsa = EVP_PKEY_get1_RSA(pkey);
순수한 public key(256byte) 로 RSA 생성하기
/**
* @brief ASN1 포로트콜용 길이 문자열을 HEX STRING으로 만든다.
* @param [in] size byte data 길이
* @param [out] out HEX STRING
* @return out
*/
inline char *ASN1_MAKE_HEX_LENGTH(int size, char *out)
{
if( size < 0x81 ) sprintf(out, "%02X", size);
else if ( size == 0x81 ) sprintf(out, "81%02X", size);
else if ( size > 0x81 ) sprintf(out, "82%04X", size);
return out;
}
const unsigned char *key = "HEX String의 256byte public 키";
char hex[540+1] = "";
unsigned char raw_buf[4096], *p;
int raw_size = 0;
char seq_length[6+1]="", int_length[6+1]="";
int seq_size_of_byte, int_size_of_byte = (strlen((const char*)key) +2) / 2; // include first "00"
ASN1_MAKE_HEX_LENGTH(int_size_of_byte, int_length);
seq_size_of_byte = 1 + (strlen(int_length)/2) + int_size_of_byte // public key block
+ 5; // exponent block
ASN1_MAKE_HEX_LENGTH(seq_size_of_byte, seq_length);
sprintf(hex, "30%s02%s00%s0203010001", seq_length, int_length, key);
// public key size를 256byte로 고정하는 경우
//sprintf(hex, "3082010A0282010100%s0203010001", key);
p = raw_buf;
hex2binary(raw_buf, &raw_size, hex);
RSA *rsa = d2i_RSAPublicKey(NULL, (const unsigned char**)&p, raw_size);
if ( rsa == NULL ) throw "EVP_PKEY_get1_RSA fail";
RSA 에서 추출
RSA에서 PEM public 키 추출
BIO *bio = BIO_new(BIO_s_mem());
// -----BEGIN RSA PUBLIC KEY----- 이런 해더로 생성됨
//if ( PEM_write_bio_RSAPublicKey(bio, rsa) != 1 ) throw "PEM_write_bio_RSAPublicKey fail";
// -----BEGIN PUBLIC KEY----- 이런 해더로 생성됨
if ( PEM_write_bio_RSA_PUBKEY(bio, rsa) != 1 ) throw "PEM_write_bio_RSAPublicKey fail";
int size = BIO_get_mem_data(bio, &p);
if ( size <= 0 ) throw "Public size is zero";
size++; // include null
char *buffer = new char[size];
int read_size = BIO_read(bio, buffer, size-1);
if ( read_size != size-1 ) throw "Public read fail";
RSA에서 PEM private 키 추출
BIO *bio = BIO_new(BIO_s_mem());
if ( PEM_write_bio_RSAPrivateKey(bio, _rsa, NULL, NULL, 0, NULL, NULL) != 1) throw "PEM_write_bio_RSAPrivateKey fail";
int size = BIO_get_mem_data(bio, &p);
if ( size <= 0 ) throw "Private size is zero";
size++; // include null
char *buffer = new char[size];
int read_size = BIO_read(bio, buffer, size-1);
if ( read_size != size-1 ) throw "Private read fail";
RSA에서 순수한 Public 키(256byte) 추출 하기(DER 파싱)
/**
@breif der를 한번 파싱한다.
@param [in] in 파싱 시작위치
@param [out] tag 태그 type
@param [out] length data length
@param [out] data data 시작위치
@return true 성공
*/
bool parsingDer(const unsigned char *in, unsigned char *tag, int *length, unsigned char **data)
{
int offset = 0;
*tag = in[0];
if ( in[++offset] == 0x82 ) {
*length = (int)(in[offset+1] << 8 | in[offset+2]);
offset += 2;
} else {
*length = (int)(in[++offset]);
}
*data = ( unsigned char *)&in[++offset];
return true;
}
unsigned char out[256];
unsigned char tag, *buf, *start_pos, *data_pos;
int data_len = i2d_RSAPublicKey( rsa, &buf );
if ( data_len < 0 ) throw "Fail get public key from rsa";
start_pos = buf;
parsingDer(start_pos, &tag, &data_len, &data_pos);
if ( tag != 0x30 ) throw "Fail parsing at SEQUENCE";
start_pos = data_pos;
parsingDer(start_pos, &tag, &data_len, &data_pos);
if ( tag != V_ASN1_INTEGER ) throw "Fail parsing at INTEGER";
memcpy(out, &(data_pos[1]), data_len-1);
RSA에서 시작일 추출
ASN1_TIME* atime_before = NULL;
atime_before = X509_get_notBefore(_cert);
if ( atime_before == NULL ) throw "X509_get_notBefore fail";
RSA에서 만료일 추출
ASN1_TIME* atime_after = NULL;
atime_after = X509_get_notAfter(_cert);
if ( atime_after == NULL ) throw "X509_get_notAfter fail";
ASN1_TIME을 문자열로 변환
char* ASN1_TIME_to_string(const ASN1_TIME* time, char out[DT_STRING_LENGTH])
{
struct tm t;
const char* str = (const char*) time->data;
size_t i = 0;
memset(&t, 0, sizeof(t));
if (time->type == V_ASN1_UTCTIME) {/* two digit year */
t.tm_year = (str[i++] - '0') * 10;
t.tm_year += (str[i++] - '0');
if (t.tm_year < 70)
t.tm_year += 100;
} else if (time->type == V_ASN1_GENERALIZEDTIME) {/* four digit year */
t.tm_year = (str[i++] - '0') * 1000;
t.tm_year+= (str[i++] - '0') * 100;
t.tm_year+= (str[i++] - '0') * 10;
t.tm_year+= (str[i++] - '0');
t.tm_year -= 1900;
}
t.tm_mon = (str[i++] - '0') * 10;
t.tm_mon += (str[i++] - '0') - 1; // -1 since January is 0 not 1.
t.tm_mday = (str[i++] - '0') * 10;
t.tm_mday+= (str[i++] - '0');
t.tm_hour = (str[i++] - '0') * 10;
t.tm_hour+= (str[i++] - '0');
t.tm_min = (str[i++] - '0') * 10;
t.tm_min += (str[i++] - '0');
t.tm_sec = (str[i++] - '0') * 10;
t.tm_sec += (str[i++] - '0');
/* Note: we did not adjust the time based on time zone information */
time_t tt = mktime(&t);
//strftime(out, DT_STRING_LENGTH, "%Y-%m-%d %H:%M:%S", localtime(&tt));
strftime(out, DT_STRING_LENGTH, "%Y%m%d", localtime(&tt));
return out;
}
암호화
public키로 생성한 RSA로 암호화
RSA_public_encrypt(flen, from, to, rsa, padding);
private키로 생성한 RSA로 암호화
RSA_private_encrypt(flen, from, to, rsa, padding);
복호화
public키로 생성한 RSA로 복호화
RSA_public_decrypt(flen, from, to, rsa, padding);
private키로 생성한 RSA로 복호화
RSA_private_decrypt(flen, from, to, rsa, padding);
base64
인코딩
int raw_size = 256;
unsigned char raw[256] ={/* RAWDATA */};
int bas64_size = 1.5 * raw_size;
char *out = new char[bas64_size];
bas64_size = EVP_EncodeBlock((unsigned char*)out, (const unsigned char*)raw, raw_size);
out[bas64_size++] = 0;
기타 함수
hex string을 binary(RAWDATA)로 만들기
void hex2binary(unsigned char *dst, int *dst_len, const char *src)
{
int src_len = strlen(src);
char *end =0;
char buf[3] = {0,};
int i=0;
for(i=0; i<src_len; i++) {
strncpy(buf, &src[i*2], 2);
dst[i] = (char)strtol(buf, &end, 16);
}
*dst_len = i/2;
return;
}
반응형