記事の概要
mbet TLSを用いてRSAを利用します。
今回はRSA-OAEPの暗号化と復号を試します。
サンプルプログラムは以下に置いてあります。
https://github.com/matsuikosuke/mbedTLSTest
RSAとは何か
RSAは公開鍵暗号であり、以下の手順でデータを送受信します。
- 鍵のペア、公開鍵(Public Key)と秘密鍵(Private Key)を作成
- 公開鍵を公開
- 公開鍵を用いて平文から暗号文を作成する
- 秘密鍵を用いて暗号文から平文を複合する
誰でも見れる公開鍵を用いて作成された暗号文は、秘密鍵を持っていないと復号が極めて難しいので、セキュリティが保たれます。
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_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鍵ペアの生成
256bytesにしてメモリ不足で-0x4310 (-17168)エラーになる場合はHEAPサイズを増やしてください。
例えば、NordicのnRF52の開発環境であるSEGGER Embedded StudioではHEAPサイズを設定できます。
他のマイコンの開発環境にも、このような設定項目があるはずです。
内部変数
内部変数は以下を定義します。
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を利用する