3
2

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およびOberonでECDHを利用する

Last updated at Posted at 2021-07-17

記事の概要

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_sourcembedtls_ctr_drbg_seedにより乱数の実体ctr_drbgを生成します。
引数のentropy_sourceには以下の記事の関数を代入しています。

nRF52でよく使用している乱数生成

デバッグ用に固定した値の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_binarymbedtls_mpi_read_binaryを使用します。
リトルエンディアンの場合はmbedtls_mpi_write_binary_lembedtls_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を利用する

3
2
0

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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?