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


# 記事の概要
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)は公開鍵暗号方式の１つで、IND-CCA2安全性を持ちます。

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

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

[安全性を証明するために知っておくべき4つのこと](https://www.slideshare.net/shibataka000/ss-29217236)
[Pythonで暗号：IND-CCA2とRSA-OAEP](https://qiita.com/tibigame/items/8c49fee0fff620f69888)

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


```c
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_C`と`MBEDTLS_PKCS1_V21`は必須で、それに伴い諸々の定義も有効化を求められます。

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

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


## 鍵
鍵の生成は以下のサイトでRSA鍵ペアを作成しました。
[RSA鍵ペアの生成](https://kjur.github.io/www/wcrypt/genrsa.html)

![RSA鍵生成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/2bd3418f-20c8-18d1-f48c-fb20019ac52f.png)

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

https://forums.mbed.com/t/error-while-connectiong-to-aws/6231/2

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

![heap_size.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/ee19f718-2005-bb34-1d43-667a46e61bc3.png)


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

```c
    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引数に代入する乱数を生成します。

```c
    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に渡します。

```c
    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に渡します。

```c
    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`を追加します。

## 暗号化

```c
    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引数に文字列とその長さを代入します。

```c
    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に暗号化データが保存されます。

## 復号

```c
    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()`関数により内部で鍵を生成することもできます。

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

```

詳しくは「[mbed TLSでRSAを利用する「RSAの公開鍵と秘密鍵の作成」](https://qiita.com/Kosuke_Matsui/items/786e88eace7646c8a7e6)」をご覧ください。

# 復号時間の注意
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のサイズを代入します。

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

```C
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を利用する](https://qiita.com/Kosuke_Matsui/items/340c6b906fed2c6ea40b)
[mbed TLSでRSAを利用する「RSA-OAEPの暗号化と復号」](https://qiita.com/Kosuke_Matsui/items/4c64c993e5979a65620d)
[mbed TLSでRSAを利用する「RSAの公開鍵と秘密鍵の作成」](https://qiita.com/Kosuke_Matsui/items/786e88eace7646c8a7e6)
[mbed TLSおよびOberonでECDHを利用する](https://qiita.com/Kosuke_Matsui/items/4390f5e58339e95dcdae)
[mbed TLSでHKDFを利用する](https://qiita.com/Kosuke_Matsui/items/93f25f47a6171fe94d30)
[mbed TLSでJWTを利用する](https://qiita.com/Kosuke_Matsui/private/915e6607b2b81de7f55d)