記事の概要
ECDH鍵ペアとECDH共通鍵の生成をmbedTLSとOberonを用いて行います。
サンプルプログラムは以下に置いてあります。
https://github.com/matsuikosuke/mbedTLSTest
ECDHとは何か
ECDHは楕円曲線ディフィー・ヘルマン(Elliptic curve Diffie–Hellman)を意味します。
これは2人の通信者AliceとBobが、それぞれ秘密鍵と公開鍵を使って共通の鍵を生成します。
つまり、Aliceは秘密鍵$d_A$をもとにして公開鍵を$P_A=d_A G$を計算してBobに送信します。
Bobは秘密鍵$d_B$をもとにして公開鍵を$P_B=d_B G$を計算してAliceに送信します。
Aliceは自分の秘密鍵とBobの公開鍵から$d_A P_B = d_A d_B G$を求め、Bobは自分の秘密鍵とAliceの公開鍵から$d_B P_A = d_B d_A G$を求めます。
こうして両者は同じ共有鍵を持つようになります。
Curve25519
本記事では乱数$d_i$の生成にCurve25519を使用しています。
Curve25519は$y^2=x^3 + 486662x^2 + x$の式で表現され、$2^{255}-19$通りの素数の剰余数で動作するのが名前の由来です。
NIST曲線が持つ不審な係数がないので、より信頼性が高いと言われています。
mbed TLSによるECDH鍵ペア生成とECDH共通鍵生成
以下はクライアント側とサーバ側のECDH鍵ペア生成とECDH共通鍵生成の流れをを1つの関数内で再現しています。
まず、関数内でサーバ側のECDH鍵ペアとクライアント側のECDH鍵ペアを作成します。
次いで、サーバ側のPrivate Keyとクライアント側のPublic Keyにより共通鍵を生成します。
更にクライアント側のPrivate Keyとサーバ側のPublic Keyにより共通鍵を生成します。
最後にこの2つの共通鍵が一致していることを確認します。
#define ECDH_KEY_SIZE (32)
int mbedtls_ecdh_self_test(void)
{
int ret = 1;
mbedtls_ecdh_context ctx_cli;
mbedtls_ecdh_context ctx_srv;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
unsigned char cli_to_srv[ECDH_KEY_SIZE];
unsigned char srv_to_cli[ECDH_KEY_SIZE];
// ECDH Shared Key
uint32_t ecdhSharedKey1[8] = {};
uint32_t ecdhSharedKey2[8] = {};
const char* pers = "ecdh";
// initialize entropy
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);
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char*)pers,
strlen(pers));
if (ret != 0)
goto exit;
//initialize context and generate keypair
mbedtls_ecdh_init( &ctx_cli );
ret = mbedtls_ecp_group_load( &ctx_cli.grp, MBEDTLS_ECP_DP_CURVE25519 );
if( ret != 0 )
goto exit;
ret = mbedtls_ecdh_gen_public( &ctx_cli.grp, &ctx_cli.d, &ctx_cli.Q,
mbedtls_ctr_drbg_random, &ctr_drbg );
if( ret != 0 )
goto exit;
//save Public Key
ret = mbedtls_mpi_write_binary( &ctx_cli.Q.X, cli_to_srv, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
//initialize context and generate keypair
mbedtls_ecdh_init( &ctx_srv );
ret = mbedtls_ecp_group_load( &ctx_srv.grp, MBEDTLS_ECP_DP_CURVE25519 );
if( ret != 0 )
goto exit;
ret = mbedtls_ecdh_gen_public( &ctx_srv.grp, &ctx_srv.d, &ctx_srv.Q,
mbedtls_ctr_drbg_random, &ctr_drbg );
if( ret != 0 )
goto exit;
//save Public Key
ret = mbedtls_mpi_write_binary( &ctx_srv.Q.X, srv_to_cli, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
//read peer's key and generate shared secret
ret = mbedtls_mpi_lset( &ctx_srv.Qp.Z, 1 );
if( ret != 0 )
goto exit;
//read Public Key
ret = mbedtls_mpi_read_binary( &ctx_srv.Qp.X, cli_to_srv, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
// generate shared key
ret = mbedtls_ecdh_compute_shared( &ctx_srv.grp, &ctx_srv.z,
&ctx_srv.Qp, &ctx_srv.d,
mbedtls_ctr_drbg_random, &ctr_drbg );
if( ret != 0 )
goto exit;
//save shared key
for (uint16_t i=0; i<ctx_srv.z.n; i++) {
ecdhSharedKey1[i] = ctx_srv.z.p[i];
}
//read peer's key and generate shared secret
ret = mbedtls_mpi_lset( &ctx_cli.Qp.Z, 1 );
if( ret != 0 )
goto exit;
//read Public Key
ret = mbedtls_mpi_read_binary( &ctx_cli.Qp.X, srv_to_cli, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
// generate shared key
ret = mbedtls_ecdh_compute_shared( &ctx_cli.grp, &ctx_cli.z,
&ctx_cli.Qp, &ctx_cli.d,
mbedtls_ctr_drbg_random, &ctr_drbg );
if( ret != 0 )
goto exit;
//save shared key
for (uint16_t i=0; i<ctx_cli.z.n; i++) {
ecdhSharedKey2[i] = ctx_cli.z.p[i];
}
printf("read mbedtls ecdhSharedKey1=");
for (uint16_t i=0; i<8; i++) {
printf("%02lX, ", ecdhSharedKey1[i]);
}
printf("\r\n");
printf("read mbedtls ecdhSharedKey2=");
for (uint16_t i=0; i<8; i++) {
printf("%02lX, ", ecdhSharedKey2[i]);
}
printf("\r\n");
//Verification: are the computed secrets equal?
ret = mbedtls_mpi_cmp_mpi( &ctx_cli.z, &ctx_srv.z );
if( ret != 0 )
goto exit;
exit:
mbedtls_ecdh_free( &ctx_srv );
mbedtls_ecdh_free( &ctx_cli );
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
return ret;
}
乱数生成
mbedtls_entropy_add_source
とmbedtls_ctr_drbg_seed
により乱数の実体ctr_drbg
を生成します。
引数のentropy_source
には以下の記事の関数を代入しています。
デバッグ用に固定した値のECDH鍵ペアを生成したい場合は、entropy_dummy_source
を使用してください。
ECDH鍵ペア生成
mbedtls_ecp_group_load
により実体mbedtls_ecdh_context
を作成します。
mbedtls_ecdh_gen_public
によりmbedtls_ecdh_context.Q.X
にPublic Key、mbedtls_ecdh_context.d
にPrivate Keyが生成されます。
ret = mbedtls_mpi_write_binary( &ctx_cli.Q.X, cli_to_srv, ECDH_KEY_SIZE );
により第1引数のPublick Keyを第2引数の変数に書き出します。第3引数は鍵のサイズの32を指定しています。
ECDH共通鍵の生成
ret = mbedtls_mpi_read_binary( &ctx_cli.Qp.X, srv_to_cli, ECDH_KEY_SIZE );
により第1引数のPublick Keyに対して第2引数の変数を読み込みます。第3引数は鍵のサイズの32を指定しています。
PrivateKeyはctx_srv
が既に持っているので、わざわざ呼び出しません。
そしてmbedtls_ecdh_compute_shared
により、mbedtls_ecdh_context.z
にECDH共通鍵が生成されます。
ret = mbedtls_mpi_cmp_mpi( &ctx_cli.z, &ctx_srv.z );
によりサーバ側とクライアント側のECDH共通鍵が一致していることが分かります。
ECDH鍵ペア生成関数
ECDH鍵ペアを単独で生成する関数があると便利なので、以下のように作成しました。
#define ECDH_KEY_SIZE (32)
static int generate_ecdh_key(uint8_t * ecdh_public_key, uint8_t *ecdh_private_key)
{
int ret = 1;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ecdh_context ctx_srv;
const char* pers = "ecdh";
// initialize context and generate keypair
mbedtls_ecdh_init( &ctx_srv );
// initialize entropy
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_add_source(&entropy,entropy_dummy_source, NULL,
GENERATE_KEY_ENTROPY_THRESHOLD, MBEDTLS_ENTROPY_SOURCE_STRONG);
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char*)pers,
strlen(pers));
if (ret != 0)
goto exit;
// Server: initialize context and generate keypair
ret = mbedtls_ecp_group_load( &ctx_srv.grp, MBEDTLS_ECP_DP_CURVE25519 );
if( ret != 0 )
goto exit;
ret = mbedtls_ecdh_gen_public( &ctx_srv.grp, &ctx_srv.d, &ctx_srv.Q,
mbedtls_ctr_drbg_random, &ctr_drbg );
if( ret != 0 )
goto exit;
// save Public Key
ret = mbedtls_mpi_write_binary_le( &ctx_srv.Q.X, ecdh_public_key, sizeof(ecdh_public_key) );
if( ret != 0 )
goto exit;
// save Public Key
ret = mbedtls_mpi_write_binary_le(&ctx_srv.d, ecdh_private_key, sizeof(ecdh_private_key));
if( ret != 0 )
goto exit;
printf("write ecdh_public_key=");
for (uint16_t i=0; i<ECDH_KEY_SIZE; i++) {
printf("%02lX, ", ecdh_public_key[i]);
}
printf("\r\n");
printf("write ecdh_private_key=");
for (uint16_t i=0; i<ECDH_KEY_SIZE; i++) {
printf("%02lX, ", ecdh_private_key[i]);
}
printf("\r\n");
exit:
// free context
mbedtls_ecdh_free( &ctx_srv );
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
return ret;
}
今回はPublic keyだけではなく、Private Keyも変数に書き出しています。
エンディアンの注意
ECDH鍵ペア生成したり、読み込んだりする際には、エンディアンに注意して使い分けないといけません。
通信相手によってデータがビッグエンディアンであったり、リトルエンディアンであったりします。
通信相手がリトルエンディアンならば、ビッグエンディアンの鍵を送信しても同じ共通鍵は生成されません。
逆に通信相手がリトルエンディアンなのに、ビッグエンディアンの鍵として読み込んでも同じ共通鍵は生成されません。
ビッグエンディアンの場合はmbedtls_mpi_write_binary
やmbedtls_mpi_read_binary
を使用します。
リトルエンディアンの場合はmbedtls_mpi_write_binary_le
やmbedtls_mpi_read_binary_le
を使用します。
mbed TLSによるECDH共通鍵生成
ECDH鍵ペア生成関数を使うと先ほどの関数は以下のように修正できます。
int mbedtls_ecdh_self_test2(void)
{
int ret = 1;
mbedtls_ecdh_context ctx_cli;
mbedtls_ecdh_context ctx_srv;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
// ECDH Shared Key
uint32_t ecdhSharedKey1[8] = {};
uint32_t ecdhSharedKey2[8] = {};
const char* pers = "ecdh";
uint8_t my_ecdh_public_key[ECDH_KEY_SIZE] = {0};
uint8_t my_ecdh_private_key[ECDH_KEY_SIZE] = {0};
uint8_t your_ecdh_public_key[ECDH_KEY_SIZE] = {0};
uint8_t your_ecdh_private_key[ECDH_KEY_SIZE] = {0};
generate_ecdh_key(my_ecdh_public_key, my_ecdh_private_key);
generate_ecdh_key(your_ecdh_public_key, your_ecdh_private_key);
// initialize entropy
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);
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char*)pers,
strlen(pers));
if (ret != 0)
goto exit;
//initialize context
mbedtls_ecdh_init( &ctx_cli );
ret = mbedtls_ecp_group_load( &ctx_cli.grp, MBEDTLS_ECP_DP_CURVE25519 );
if( ret != 0 )
goto exit;
//initialize context
mbedtls_ecdh_init( &ctx_srv );
ret = mbedtls_ecp_group_load( &ctx_srv.grp, MBEDTLS_ECP_DP_CURVE25519 );
if( ret != 0 )
goto exit;
//read peer's key and generate shared secret
ret = mbedtls_mpi_lset( &ctx_srv.Qp.Z, 1 );
if( ret != 0 )
goto exit;
//read Public Key
ret = mbedtls_mpi_read_binary_le( &ctx_srv.Qp.X, my_ecdh_public_key, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
//read Private Key
ret = mbedtls_mpi_read_binary_le( &ctx_srv.d, your_ecdh_private_key, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
// generate shared key
ret = mbedtls_ecdh_compute_shared( &ctx_srv.grp, &ctx_srv.z,
&ctx_srv.Qp, &ctx_srv.d,
mbedtls_ctr_drbg_random, &ctr_drbg );
if( ret != 0 )
goto exit;
//save shared key
for (uint16_t i=0; i<ctx_srv.z.n; i++) {
ecdhSharedKey1[i] = ctx_srv.z.p[i];
}
//read peer's key and generate shared secret
ret = mbedtls_mpi_lset( &ctx_cli.Qp.Z, 1 );
if( ret != 0 )
goto exit;
//read Public Key
ret = mbedtls_mpi_read_binary_le( &ctx_cli.Qp.X, your_ecdh_public_key, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
//read Private Key
ret = mbedtls_mpi_read_binary_le( &ctx_cli.d, my_ecdh_private_key, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
// generate shared key
ret = mbedtls_ecdh_compute_shared( &ctx_cli.grp, &ctx_cli.z,
&ctx_cli.Qp, &ctx_cli.d,
mbedtls_ctr_drbg_random, &ctr_drbg );
if( ret != 0 )
goto exit;
//save shared key
for (uint16_t i=0; i<ctx_cli.z.n; i++) {
ecdhSharedKey2[i] = ctx_cli.z.p[i];
}
printf("read mbedtls ecdhSharedKey1=");
for (uint16_t i=0; i<8; i++) {
printf("%02lX, ", ecdhSharedKey1[i]);
}
printf("\r\n");
printf("read mbedtls ecdhSharedKey2=");
for (uint16_t i=0; i<8; i++) {
printf("%02lX, ", ecdhSharedKey2[i]);
}
printf("\r\n");
//step7: Verification: are the computed secrets equal?
ret = mbedtls_mpi_cmp_mpi( &ctx_cli.z, &ctx_srv.z );
if( ret != 0 )
goto exit;
exit:
mbedtls_ecdh_free( &ctx_srv );
mbedtls_ecdh_free( &ctx_cli );
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
return ret;
}
乱数の生成や実体の作成などをしないといけないので、処理としてはほとんど省略できていません。
OberonによるECDH共通鍵生成
ECDH共通鍵の生成にはOberonの使用をお勧めします。
処理時間がmbed TLSの場合より短くなります。
ocrypto_curve25519_scalarmult
の第1引数にPublic Keyを、第2引数にPrivate Keyを代入すると、第3引数に共通鍵が生成されます。
以下の関数は、generate_ecdh_key
によりサーバ側とクライアント側のECDH鍵ペアを生成し、ocrypto_curve25519_scalarmult
によりサーバ側とクライアント側の共通鍵を生成して、それらが一致していることを確認しています。
void oberon_ecdhTest(void)
{
// ECDH Shared Key
uint8_t ecdhSharedKey1[32] = {};
uint8_t ecdhSharedKey2[32] = {};
uint8_t my_ecdh_public_key[ECDH_KEY_SIZE] = {0};
uint8_t my_ecdh_private_key[ECDH_KEY_SIZE] = {0};
uint8_t your_ecdh_public_key[ECDH_KEY_SIZE] = {0};
uint8_t your_ecdh_private_key[ECDH_KEY_SIZE] = {0};
generate_ecdh_key(my_ecdh_public_key, my_ecdh_private_key);
generate_ecdh_key(your_ecdh_public_key, your_ecdh_private_key);
// Compute curve25519
ocrypto_curve25519_scalarmult(ecdhSharedKey1, my_ecdh_private_key, your_ecdh_public_key);
ocrypto_curve25519_scalarmult(ecdhSharedKey2, your_ecdh_private_key, my_ecdh_public_key);
printf("read ecdhSharedKey1=");
for (uint16_t i=0; i<ECDH_KEY_SIZE; i++) {
printf("%02lX, ", ecdhSharedKey1[i]);
}
printf("\r\n");
printf("read ecdhSharedKey2=");
for (uint16_t i=0; i<ECDH_KEY_SIZE; i++) {
printf("%02lX, ", ecdhSharedKey2[i]);
}
printf("\r\n");
}
実際に使用する場合
実際の暗号化では、自分で両方の鍵ペアを生成したりはしません。
自分で作成したPrivate Keyと相手から送られたPublic Keyを読み込んで、ECDH共通鍵を生成します。
read_ecdh_private_key
関数とread_ecdh_public_key
関数は、GitHubのコードには名前だけで、中身の処理は存在していませんが、実際に使用しているコードではFLASHメモリに保存したkeyデータをreadする処理を行ったりしています。
例えば、以下のような関数になるはずです。
mbedTLSの場合
int mbedtls_ecdh_test(void)
{
int ret = 1;
mbedtls_ecdh_context ctx_srv;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
// ECDH Shared Key
uint32_t ecdhSharedKey1[8] = {};
const char* pers = "ecdh";
// 自分の秘密鍵と相手の公開鍵を呼び出す
read_ecdh_private_key(my_ecdh_private_key);
read_ecdh_public_key(your_ecdh_public_key);
// initialize entropy
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);
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char*)pers,
strlen(pers));
if (ret != 0)
goto exit;
//initialize context
mbedtls_ecdh_init( &ctx_srv );
ret = mbedtls_ecp_group_load( &ctx_srv.grp, MBEDTLS_ECP_DP_CURVE25519 );
if( ret != 0 )
goto exit;
//read peer's key and generate shared secret
ret = mbedtls_mpi_lset( &ctx_srv.Qp.Z, 1 );
if( ret != 0 )
goto exit;
//read Public Key
ret = mbedtls_mpi_read_binary_le( &ctx_srv.Qp.X, my_ecdh_public_key, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
//read Private Key
ret = mbedtls_mpi_read_binary_le( &ctx_srv.d, your_ecdh_private_key, ECDH_KEY_SIZE );
if( ret != 0 )
goto exit;
// generate shared key
ret = mbedtls_ecdh_compute_shared( &ctx_srv.grp, &ctx_srv.z,
&ctx_srv.Qp, &ctx_srv.d,
mbedtls_ctr_drbg_random, &ctr_drbg );
if( ret != 0 )
goto exit;
//save shared key
for (uint16_t i=0; i<ctx_srv.z.n; i++) {
ecdhSharedKey1[i] = ctx_srv.z.p[i];
}
exit:
mbedtls_ecdh_free( &ctx_srv );
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
return ret;
}
Oberonの場合
void oberon_ecdh_test(void)
{
// ECDH Shared Key
uint8_t ecdhSharedKey1[32] = {};
uint8_t my_ecdh_private_key[ECDH_KEY_SIZE] = {0};
uint8_t your_ecdh_public_key[ECDH_KEY_SIZE] = {0};
// 自分の秘密鍵と相手の公開鍵を呼び出す
read_ecdh_private_key(my_ecdh_private_key);
read_ecdh_public_key(your_ecdh_public_key);
// Compute curve25519
ocrypto_curve25519_scalarmult(ecdhSharedKey1, my_ecdh_private_key, your_ecdh_public_key);
}
参照
mbed TLSでAES-GCMを利用する
mbed TLSでRSAを利用する「RSA-OAEPの暗号化と復号」
mbed TLSでRSAを利用する「RSAの公開鍵と秘密鍵の作成」
mbed TLSおよびOberonでECDHを利用する
mbed TLSでHKDFを利用する
mbed TLSでJWTを利用する