mbed TLSおよびOberonでECDHを利用する

# 記事の概要
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つの共通鍵が一致していることを確認します。

```C
#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`には以下の記事の関数を代入しています。

[nRF52でよく使用している乱数生成](https://qiita.com/Kosuke_Matsui/items/0c25c5176665634a4247)

デバッグ用に固定した値の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が生成されます。

```C
    ret = mbedtls_mpi_write_binary( &ctx_cli.Q.X, cli_to_srv, ECDH_KEY_SIZE );
```
により第1引数のPublick Keyを第2引数の変数に書き出します。第3引数は鍵のサイズの32を指定しています。

## ECDH共通鍵の生成

```C
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共通鍵が生成されます。

```C
ret = mbedtls_mpi_cmp_mpi( &ctx_cli.z, &ctx_srv.z );
```

によりサーバ側とクライアント側のECDH共通鍵が一致していることが分かります。

# ECDH鍵ペア生成関数

ECDH鍵ペアを単独で生成する関数があると便利なので、以下のように作成しました。

```C
#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鍵ペア生成関数を使うと先ほどの関数は以下のように修正できます。

```C
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`によりサーバ側とクライアント側の共通鍵を生成して、それらが一致していることを確認しています。

```C
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の場合

```C
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の場合

```C
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を利用する](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)