3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

mbed TLSでRSAを利用する「RSA-OAEPの暗号化と復号」

Last updated at Posted at 2021-02-07

記事の概要

mbet TLSを用いてRSAを利用します。
今回はRSA-OAEPの暗号化と復号を試します。

サンプルプログラムは以下に置いてあります。
https://github.com/matsuikosuke/mbedTLSTest

RSAとは何か

RSAは公開鍵暗号であり、以下の手順でデータを送受信します。

  1. 鍵のペア、公開鍵(Public Key)と秘密鍵(Private Key)を作成
  2. 公開鍵を公開
  3. 公開鍵を用いて平文から暗号文を作成する
  4. 秘密鍵を用いて暗号文から平文を複合する

誰でも見れる公開鍵を用いて作成された暗号文は、秘密鍵を持っていないと復号が極めて難しいので、セキュリティが保たれます。

RSA-OAEPとは何か

RSA-OAEP(Optimal Asymmetric Encryption Padding)は公開鍵暗号方式の1つで、IND-CCA2安全性を持ちます。

INDは、安全性モデルの1種で、組み合わせ識別不可能性を意味します。
CCA2は、攻撃モデルの1種で、適応型選択暗号文攻撃を意味します。

INDとCCA2の詳細は以下をご参照ください

安全性を証明するために知っておくべき4つのこと
Pythonで暗号:IND-CCA2とRSA-OAEP

サンプルコード

GitHubにあったサンプルコードを参照して以下を作成しました。

static char g_plaintext[32] = "===Hello! This is plaintext!===";
static unsigned char rsa_oeap_decrypted_buf[256];
static unsigned char rsa_oeap_encrypted_buf[256];

static int entropy_dummy_source( void *data, unsigned char *output,
                                 size_t len, size_t *olen )
{
    ((void) data);

    memset( output, 0x2a, len );
    *olen = len;

    return( 0 );
}

int mbedtls_rsa_rsaes_oaep_encrypt_test(void)
{  
    int ret = 1;
    mbedtls_pk_context p_pk;
    mbedtls_rsa_context *p_rsa;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    const char* pers = "my_app_specific_string";

    mbedtls_entropy_init(&entropy);    
    mbedtls_ctr_drbg_init(&ctr_drbg); 
    mbedtls_entropy_add_source( &entropy, entropy_dummy_source, NULL, 134, MBEDTLS_ENTROPY_SOURCE_STRONG );
    ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
                    (const unsigned char*)pers,
                    strlen(pers));
   if (ret != 0)
        return ret ;

    mbedtls_pk_init(&p_pk);
    ret = mbedtls_pk_parse_public_key(&p_pk, TEST_PUBLIC_KEY, strlen(TEST_PUBLIC_KEY) + 1);
    if (ret != 0)
        return ret;
 
    mbedtls_rsa_init(p_rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
    p_rsa = mbedtls_pk_rsa( p_pk );
    mbedtls_rsa_set_padding(p_rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);

    ret = mbedtls_rsa_rsaes_oaep_encrypt(p_rsa, 
                        mbedtls_ctr_drbg_random, &ctr_drbg,
                        MBEDTLS_RSA_PUBLIC, NULL, 0,
                        32, &g_plaintext[0], &rsa_oeap_encrypted_buf[0]);
   if (ret != 0)
        return ret ;

    return ret;
}

int mbedtls_rsa_rsaes_oaep_decrypt_test(void)
{
    int ret = 1;
    mbedtls_pk_context p_pk;
    mbedtls_rsa_context *p_rsa;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    const char* pers = "my_app_specific_string";

    mbedtls_entropy_init(&entropy);    
    mbedtls_ctr_drbg_init(&ctr_drbg); 
    mbedtls_entropy_add_source( &entropy, entropy_dummy_source, NULL, 134, MBEDTLS_ENTROPY_SOURCE_STRONG );
    ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
                    (const unsigned char*)pers,
                    strlen(pers));
   if (ret != 0)
        return ret ;

    mbedtls_pk_init(&p_pk);
    mbedtls_pk_parse_key(&p_pk, TEST_PRIVATE_KEY, strlen(TEST_PRIVATE_KEY) + 1, NULL, NULL);
    if (ret != 0)
        return ret ;

    mbedtls_rsa_init(p_rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
    p_rsa = mbedtls_pk_rsa( p_pk );
    mbedtls_rsa_set_padding(p_rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
    ret = mbedtls_rsa_rsaes_oaep_decrypt(p_rsa, 
                        mbedtls_ctr_drbg_random, &ctr_drbg,
                        MBEDTLS_RSA_PRIVATE, NULL, 0, // label = NULL and label_len = 0
                        &olen, rsa_oeap_encrypted_buf, rsa_oeap_decrypted_buf, 32);
    if (ret != 0)
        return ret ;

    return ret;
}

mbed TLSの設定

nrf_crypto_mbedtls_config.hファイルの幾つかの設定を有効にしないといけません。
RSA-OAEPに無関係なものも含めて、今回は以下の定義を有効にしています。

MBEDTLS_AES_ROM_TABLES
MBEDTLS_CIPHER_MODE_CBC
MBEDTLS_CIPHER_MODE_CFB
MBEDTLS_CIPHER_MODE_CTR
MBEDTLS_CIPHER_MODE_OFB
MBEDTLS_CIPHER_MODE_XTS
MBEDTLS_CIPHER_PADDING_PKCS7
MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS
MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN
MBEDTLS_CIPHER_PADDING_ZEROS
MBEDTLS_CTR_DRBG_USE_128_BIT_KEY
MBEDTLS_REMOVE_ARC4_CIPHERSUITES
MBEDTLS_REMOVE_3DES_CIPHERSUITES
MBEDTLS_ECP_DP_SECP192R1_ENABLED
MBEDTLS_ECP_DP_SECP224R1_ENABLED
MBEDTLS_ECP_DP_SECP256R1_ENABLED
MBEDTLS_ECP_DP_SECP384R1_ENABLED
MBEDTLS_ECP_DP_SECP521R1_ENABLED
MBEDTLS_ECP_DP_SECP192K1_ENABLED
MBEDTLS_ECP_DP_SECP224K1_ENABLED
MBEDTLS_ECP_DP_SECP256K1_ENABLED
MBEDTLS_ECP_DP_BP256R1_ENABLED
MBEDTLS_ECP_DP_BP384R1_ENABLED
MBEDTLS_ECP_DP_BP512R1_ENABLED
MBEDTLS_ECP_DP_CURVE25519_ENABLED
MBEDTLS_ECP_DP_CURVE448_ENABLED
MBEDTLS_ECP_NIST_OPTIM
MBEDTLS_KEY_EXCHANGE_PSK_ENABLED
MBEDTLS_ERROR_STRERROR_DUMMY
MBEDTLS_GENPRIME
MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES
MBEDTLS_NO_PLATFORM_ENTROPY
MBEDTLS_ENTROPY_FORCE_SHA256
MBEDTLS_PKCS1_V15
MBEDTLS_PKCS1_V21
MBEDTLS_SSL_ALL_ALERT_MESSAGES
MBEDTLS_SSL_FALLBACK_SCSV
MBEDTLS_SSL_RENEGOTIATION
MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
MBEDTLS_SSL_SESSION_TICKETS
MBEDTLS_SSL_TRUNCATED_HMAC
MBEDTLS_VERSION_FEATURES
MBEDTLS_X509_CHECK_KEY_USAGE
MBEDTLS_AES_C
MBEDTLS_ASN1_PARSE_C
MBEDTLS_ASN1_WRITE_C
MBEDTLS_BASE64_C
MBEDTLS_BIGNUM_C
MBEDTLS_CAMELLIA_C
MBEDTLS_CCM_C
MBEDTLS_CERTS_C
MBEDTLS_CHACHAPOLY_C
MBEDTLS_CIPHER_C
MBEDTLS_CMAC_C
MBEDTLS_CTR_DRBG_C
MBEDTLS_ECDH_C
MBEDTLS_ECDSA_C
MBEDTLS_ECP_C
MBEDTLS_ENTROPY_C
MBEDTLS_GCM_C
MBEDTLS_HKDF_C
MBEDTLS_HMAC_DRBG_C
MBEDTLS_MD_C
MBEDTLS_MD5_C
MBEDTLS_OID_C
MBEDTLS_PEM_PARSE_C
MBEDTLS_PK_C
MBEDTLS_PK_PARSE_C
MBEDTLS_PLATFORM_C
MBEDTLS_POLY1305_C
MBEDTLS_RSA_C
MBEDTLS_SHA1_C
MBEDTLS_SHA256_C
MBEDTLS_SHA512_C
MBEDTLS_VERSION_C
MBEDTLS_CTR_DRBG_USE_128_BIT_KEY
MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE

RSA-OAEPを使用するならMBEDTLS_RSA_CMBEDTLS_PKCS1_V21は必須で、それに伴い諸々の定義も有効化を求められます。

ハッシュにSHA-1を使用するならMBEDTLS_SHA1_C、SHA-256を使用するならMBEDTLS_SHA256_Cを有効化しないといけません。

mbedtls_pk_parse_public_key()関数でkeyをcontextに取り込むなら、MBEDTLS_PEM_PARSE_CMBEDTLS_BASE64_Cも有効化しないといけません。

鍵の生成は以下のサイトでRSA鍵ペアを作成しました。
RSA鍵ペアの生成

RSA鍵生成.png

256bytesにしてメモリ不足で-0x4310 (-17168)エラーになる場合はHEAPサイズを増やしてください。

例えば、NordicのnRF52の開発環境であるSEGGER Embedded StudioではHEAPサイズを設定できます。
他のマイコンの開発環境にも、このような設定項目があるはずです。

heap_size.png

内部変数

内部変数は以下を定義します。

    int ret = 1;
    mbedtls_pk_context p_pk;
    mbedtls_rsa_context *p_rsa;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    const char* pers = "my_app_specific_string";

乱数の生成

mbedtls_rsa_rsaes_oaep_encrypt()関数およびmbedtls_rsa_rsaes_oaep_decrypt()関数の第3引数に代入する乱数を生成します。

    mbedtls_entropy_init(&entropy);    
    mbedtls_ctr_drbg_init(&ctr_drbg); 
    mbedtls_entropy_add_source( &entropy, entropy_dummy_source, NULL, 134, MBEDTLS_ENTROPY_SOURCE_STRONG );
    ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
                    (const unsigned char*)pers,
                    strlen(pers));
   if (ret != 0)
        return ret ;

persには適当な文字列を設定しておきます。

鍵のcontextへの引き渡し

暗号化の場合は公開鍵をcontextに渡します。

    mbedtls_pk_init(&p_pk);
    ret = mbedtls_pk_parse_public_key(&p_pk, TEST_PUBLIC_KEY, strlen(TEST_PUBLIC_KEY) + 1);
    if (ret != 0)
        return ret;

復号の場合は秘密鍵をcontextに渡します。

    mbedtls_pk_parse_key(&p_pk, TEST_PRIVATE_KEY, strlen(TEST_PRIVATE_KEY) + 1, NULL, NULL);
    if (ret != 0)
        return ret ;

長さの指定には、null byteも含めないといけないため、strlen+1を追加します。

暗号化

    mbedtls_rsa_init(p_rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
    p_rsa = mbedtls_pk_rsa( p_pk );
    mbedtls_rsa_set_padding(p_rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);

    ret = mbedtls_rsa_rsaes_oaep_encrypt(p_rsa, 
                        mbedtls_ctr_drbg_random, &ctr_drbg,
                        MBEDTLS_RSA_PUBLIC, NULL, 0,
                        32, &g_plaintext[0], &rsa_oeap_encrypted_buf[0]);
   if (ret != 0)
        return ret ;

mbedtls_rsa_rsaes_oaep_encrypt()関数の第7引数には入力データのサイズを代入します。

また、もしlabelを設定するならば、mbedtls_rsa_rsaes_oaep_encrypt()関数の第5引数と第6引数に文字列とその長さを代入します。

    ret = mbedtls_rsa_rsaes_oaep_encrypt(p_rsa, 
                        mbedtls_ctr_drbg_random, &ctr_drbg,
                        MBEDTLS_RSA_PUBLIC, 
                        (unsigned char *)"Hello", 5,
                        32, &g_plaintext[0], &rsa_oeap_encrypted_buf[0]);

mbedtls_rsa_rsaes_oaep_encrypt()の第7引数に入力データのサイズ、第8引数に入力データを代入します。
第9引数のrsa_oeap_encrypted_bufに暗号化データが保存されます。

復号

    mbedtls_rsa_init(p_rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
    p_rsa = mbedtls_pk_rsa( p_pk );
    mbedtls_rsa_set_padding(p_rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
    ret = mbedtls_rsa_rsaes_oaep_decrypt(p_rsa, 
                        mbedtls_ctr_drbg_random, &ctr_drbg,
                        MBEDTLS_RSA_PRIVATE, NULL, 0, // label = NULL and label_len = 0
                        &olen, rsa_oeap_encrypted_buf, rsa_oeap_decrypted_buf, 32);
    if (ret != 0)
        return ret ;

labelについては暗号化と同様になります。暗号化で設定したのと同じlabelを使用してください。

mbedtls_rsa_rsaes_oaep_encrypt()の第8引数に暗号化データを代入します。
また、mbedtls_rsa_rsaes_oaep_decrypt()関数の第10引数には復号データのサイズを代入します。
第9引数のrsa_oeap_encrypted_bufに復号データが保存されます。

関数による鍵の生成

今回は外部から与えられた鍵をmbedtls_pk_parse_key()mbedtls_pk_rsa()関数でcontextに渡しましたが、mbedtls_rsa_gen_key()関数により内部で鍵を生成することもできます。

mbedtls_rsa_gen_key(p_rsa, mbedtls_ctr_drbg_random, &ctr_drbg, 2048, 0x010001);

詳しくは「mbed TLSでRSAを利用する「RSAの公開鍵と秘密鍵の作成」」をご覧ください。

復号時間の注意

nRF52840にはARM Cryptocellのハードウェア最適化があるので、暗号化処理が短時間で済みます。

一方、nRF52832はハードウェア最適化機能はなく、ソフトウェアで処理しています。
この場合、RSA-OAEPの復号には時間がかかるのでご注意ください。
例えば、nRF52832において、256byteのRSA Keyを使用した場合は、復号処理に2650 msecを要します。

128byteのRSA Keyを使用した場合は、復号処理に600 msecにまで時間が短縮します。

復号するデータのサイズが処理時間に与える影響はほとんどありません。50バイトでも300バイトでも復号には同じくらいの時間を要します。

なお計算時間の計測には以下のようにRSA Keyを自前で生成する関数mbedtls_rsa_gen_keyを用いました。

mbedtls_rsa_rsaes_oaep_self_testの第1引数に暗号化・復号化するデータのサイズ、第2引数にRSA Keyのサイズを代入します。

マイコンのタイマー機能を利用して処理時間を測定しています。

int mbedtls_rsa_rsaes_oaep_self_test(uint8_t key_length, uint32_t key_bits)
{
  
    int ret = 1;

    size_t olen;
    mbedtls_pk_context p_pk;
    mbedtls_rsa_context *p_rsa;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    const char* pers = "my_app_specific_string";

    sys_timer_count[TEST_TIMER] = 0;
    mbedtls_entropy_init(&entropy);    
    mbedtls_ctr_drbg_init(&ctr_drbg); 
    mbedtls_entropy_add_source(&entropy, entropy_source, NULL,
                               GENERATE_KEY_ENTROPY_THRESHOLD, MBEDTLS_ENTROPY_SOURCE_STRONG);

    // Initialize entropy.
    ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
                    (const unsigned char*)pers,
                    strlen(pers));
    if (ret != 0)
        return ret;
    printf("Execution time of mbedtls_ctr_drbg_seed %d ms\n",sys_timer_count[TEST_TIMER]*50);
    sys_timer_count[TEST_TIMER] = 0;

    mbedtls_rsa_init(p_rsa, PADDING_TYPE, HASH_TYPE);
    mbedtls_pk_init(&p_pk);
    ret = mbedtls_pk_setup( &p_pk, mbedtls_pk_info_from_type( MBEDTLS_PK_RSA ) );    
    if (ret != 0)
        return ret;
    printf("Execution time of mbedtls_pk_setup %d ms\n",sys_timer_count[TEST_TIMER]*50);
    sys_timer_count[TEST_TIMER] = 0;
                
    p_rsa = mbedtls_pk_rsa( p_pk );
    ret = mbedtls_rsa_gen_key(p_rsa, mbedtls_ctr_drbg_random, &ctr_drbg, key_bits, 0x010001);
    if (ret != 0)
        return ret;
    printf("Execution time of mbedtls_rsa_gen_key %d ms\n",sys_timer_count[TEST_TIMER]*50);
    sys_timer_count[TEST_TIMER] = 0;

    //mbedtls_rsa_context *rsa = mbedtls_pk_rsa( p_pk );
    mbedtls_rsa_set_padding(p_rsa, PADDING_TYPE, HASH_TYPE);

    if (LONG_KEY == key_length)
    {
        ret = mbedtls_rsa_rsaes_oaep_encrypt(p_rsa, 
                            mbedtls_ctr_drbg_random, &ctr_drbg,
                            MBEDTLS_RSA_PUBLIC, NULL, 0,
                            key_length, &g_plaintext_120[0], &rsa_oeap_encrypted_buf[0]);
    } else {
        ret = mbedtls_rsa_rsaes_oaep_encrypt(p_rsa, 
                            mbedtls_ctr_drbg_random, &ctr_drbg,
                            MBEDTLS_RSA_PUBLIC, NULL, 0,
                            key_length, &g_plaintext_56[0], &rsa_oeap_encrypted_buf[0]);
    }
    if (ret != 0)
    return ret;
    printf("Execution time of mbedtls_rsa_rsaes_oaep_encrypt %d ms\n",sys_timer_count[TEST_TIMER]*50);
    sys_timer_count[TEST_TIMER] = 0;


    ret = mbedtls_rsa_rsaes_oaep_decrypt(p_rsa, 
                        mbedtls_ctr_drbg_random, &ctr_drbg,
                        MBEDTLS_RSA_PRIVATE, NULL, 0, // label = NULL and label_len = 0
                        &olen, rsa_oeap_encrypted_buf, rsa_oeap_decrypted_buf, key_length);
    if (ret != 0)
        return ret;
    printf("Execution time of mbedtls_rsa_rsaes_oaep_decrypt %d ms\n",sys_timer_count[TEST_TIMER]*50);
    sys_timer_count[TEST_TIMER] = 0;

    //Verification: are the computed hkdf and the expected hkdf equal?
    if (LONG_KEY == key_length)
      ret = memcmp(&g_plaintext_120, &rsa_oeap_decrypted_buf, key_length);
    else
        ret = memcmp(&g_plaintext_56, &rsa_oeap_decrypted_buf, key_length);
    if( ret != 0 )
        printf("RSA-OAEP error\r\n");
    printf("RSA-OAEP success\r\n");

    // free context
    mbedtls_entropy_free(&entropy); 
    mbedtls_ctr_drbg_free(&ctr_drbg); 
    mbedtls_rsa_free(p_rsa);
    mbedtls_pk_free(&p_pk);
    return ret;
}

参照

mbed TLSでAES-GCMを利用する
mbed TLSでRSAを利用する「RSA-OAEPの暗号化と復号」
mbed TLSでRSAを利用する「RSAの公開鍵と秘密鍵の作成」
mbed TLSおよびOberonでECDHを利用する
mbed TLSでHKDFを利用する
mbed TLSでJWTを利用する

3
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?