アプリケーション開発ポータルサイト
ServerNote.NET
カテゴリー【C/C++
【OpenSSL1.1】Diffie-Hellman(ディフィ・ヘルマン)方式の鍵交換を行う
POSTED BY
2023-07-15

2つのシステムX・Y間で暗号化通信を行う典型的な仕組みと手順は以下の通りです。

1、システムXにて、ランダムな秘密鍵aを生成する。
2、システムYにて、ランダムな秘密鍵bを生成する。
3、システムXにて、秘密鍵aと素数pと生成元gから、公開鍵Aを生成する。
4、システムYにて、秘密鍵bと素数pと生成元gから、公開鍵Bを生成する。
5、システムXにて、公開鍵AをシステムYに送信する。
6、システムYにて、公開鍵BをシステムXに送信する。
7、システムXにて、秘密鍵aと受け取った公開鍵Bと素数pから、共有鍵Kを生成する。
8、システムYにて、秘密鍵bと受け取った公開鍵Aと素数pから、共有鍵Kを生成する。
9、共有鍵KはシステムX、Y両者で同じ値になる。以降の通信は、送信側はデータをこの共有鍵でエンコードし、受信側はデータをこの共有鍵でデコードする。

素数pと生成元gを使って公開鍵と共有鍵を生成する方式がDiffie-Hellman(ディフィ・ヘルマン)方式(DH法)です。

素数p(prime)と生成元g(generator)は固定値であり、いくつかのパターンがRFC-3526-MODPによって定められています。
今回はそのなかの1つ、1536-bit MDOP Group 5 を使用します。以下の通りです。

素数(prime): 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }

16進数での値:

FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF
ジェネレータ(generator): 2

この方式では秘密鍵・公開鍵・共有鍵すべて1536ビット=192バイトになります。

ここまで値が決まれば、あとはOpenSSLライブラリに上記1~8の手順をDH法を用いて行う関数が用意されていますので、それを使います。以下ソース
OpenSSL(libssl)1.1以降専用です。(DH関係の関数仕様が大幅に変更されています/DH構造体直接アクセス不可になった)

C/C++dh.cGitHub Source
/*
Diffie-Hellman法による秘密鍵、公開鍵、共有鍵の計算テスト
1536ビットMODPグループの固定素数p/自然数gを使用
https://www.ipa.go.jp/security/rfc/RFC3526JA.html
https://wiki.openssl.org/index.php/Diffie-Hellman_parameters
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* OpenSSL version 1.1以上専用 1.0以下はコンパイル不可
 1.1以上はdh構造体への直接アクセスは不可で、set,get関数を介す
 詳しくはopenssl/dh.hを参照
*/
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/rand.h>

/* Diffie-Hellman秘密鍵と公開鍵を自動生成 */
extern DH *com_dh1536generate(const char *rnd_seed) {
  /* 素数p (prime) */
  static unsigned char dh1536_p[] = {
      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
      0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
      0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
      0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
      0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
      0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
      0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
      0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
      0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11,
      0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D,
      0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36,
      0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
      0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56,
      0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D,
      0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08,
      0xCA, 0x23, 0x73, 0x27, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
  /* 生成元g (generator) */
  static unsigned char dh1536_g[] = {0x02};
  DH *dh;
  const BIGNUM *p, *g;

  int ck, flags = 0;
  long residue;

  if ((dh = DH_new()) == NULL)
    return (NULL); /* 初期化 */

  /* prime, generatorセット */
  DH_set0_pqg(dh, BN_bin2bn(dh1536_p, sizeof(dh1536_p), NULL), NULL,
              BN_bin2bn(dh1536_g, sizeof(dh1536_g), NULL));

  DH_get0_pqg(dh, &p, NULL, &g);
  if (!p || !g) {
    DH_free(dh);
    return NULL;
  }

  RAND_seed(rnd_seed, strlen(rnd_seed));

  if (!DH_check(dh, &ck)) {
    DH_free(dh);
    return NULL;
  }

  if (BN_is_word(g, DH_GENERATOR_2)) {
    residue = BN_mod_word(p, 24);
    if (residue == 11 || residue == 23) {
      ck &= ~DH_NOT_SUITABLE_GENERATOR;
    }
  }

  if (ck & DH_CHECK_P_NOT_PRIME) {
    fprintf(stderr, "DH_CHECK_P_NOT_PRIME\n");
  }
  if (ck & DH_CHECK_P_NOT_SAFE_PRIME) {
    fprintf(stderr, "DH_CHECK_P_NOT_SAFE_PRIME\n");
  }
  if (ck & DH_UNABLE_TO_CHECK_GENERATOR) {
    fprintf(stderr, "DH_UNABLE_TO_CHECK_GENERATOR\n");
  }
  if (ck & DH_NOT_SUITABLE_GENERATOR) {
    fprintf(stderr, "DH_NOT_SUITABLE_GENERATOR\n");
  }

  flags &= ~DH_FLAG_NO_EXP_CONSTTIME;
  DH_set_flags(dh, flags);

  if (!DH_generate_key(dh)) {
    DH_free(dh);
    return NULL;
  }

  /* DHparams_print_fp(stderr, dh); */

  return (dh);
}

/* Diffie-Hellman秘密鍵と公開鍵から共有鍵を生成 */
extern unsigned char *com_dh1536compute(DH *dh, unsigned char *inpub,
                                        unsigned char *outcom) {
  BIGNUM *bn;
  int len;

  if (!dh || !inpub || !outcom)
    return NULL;

  if ((bn = BN_bin2bn(inpub, 192, NULL)) == NULL)
    return NULL;

  len = DH_compute_key(outcom, bn, dh);

  BN_free(bn);

  if (len == 192)
    return outcom; /* 192バイトなら成功 */

  fprintf(stderr, "DH_compute_key_NOT_192_BYTES\n");
  return NULL;
}

/* TEST MAIN
gcc -D_COM_DH_MAIN dh.c -lssl -lcrypto
*/
#ifdef _COM_DH_MAIN
extern int main(int argc, char **argv) {
  char *ptr;
  int i, len;
  DH *dh_X, *dh_Y;
  const BIGNUM *priv_key_X, *priv_key_Y;
  const BIGNUM *pub_key_X, *pub_key_Y;
  unsigned char pub_key_bin_X[192], pub_key_bin_Y[192];
  unsigned char share_key_bin_X[192], share_key_bin_Y[192];
  char share_key_str_X[512], share_key_str_Y[512];

  /* システムX、Yそれぞれ秘密鍵・公開鍵生成 */

  dh_X = com_dh1536generate("I have a pen, I have a apple.");
  if (!dh_X) {
    return (-1);
  }
  dh_Y = com_dh1536generate("Ah, pen pine apple apple pen.");
  if (!dh_Y) {
    DH_free(dh_X);
    return (-1);
  }

  DH_get0_key(dh_X, &pub_key_X, &priv_key_X);
  DH_get0_key(dh_Y, &pub_key_Y, &priv_key_Y);

  /* 公開鍵の長さチェック */

  if (BN_num_bytes(pub_key_X) != 192 || BN_num_bytes(pub_key_Y) != 192) {
    fprintf(stderr, "公開鍵が192バイトではありません\n");
    DH_free(dh_X);
    DH_free(dh_Y);
    return (-1);
  }

  /* 秘密鍵・公開鍵情報ダンプ */

  if ((ptr = BN_bn2hex(priv_key_X)) != NULL) {
    fprintf(stdout, "システムXの秘密鍵a:\n%s\n", ptr);
    OPENSSL_free(ptr);
  }
  if ((ptr = BN_bn2hex(pub_key_X)) != NULL) {
    fprintf(stdout, "システムXの公開鍵A:\n%s\n", ptr);
    OPENSSL_free(ptr);
  }

  if ((ptr = BN_bn2hex(priv_key_Y)) != NULL) {
    fprintf(stdout, "システムYの秘密鍵b:\n%s\n", ptr);
    OPENSSL_free(ptr);
  }
  if ((ptr = BN_bn2hex(pub_key_Y)) != NULL) {
    fprintf(stdout, "システムYの公開鍵B:\n%s\n", ptr);
    OPENSSL_free(ptr);
  }

  /* 公開鍵をバイナリデータに変換 */

  BN_bn2bin(pub_key_X, pub_key_bin_X);
  BN_bn2bin(pub_key_Y, pub_key_bin_Y);

  /* 鍵交換 = 自分の秘密鍵と相手の公開鍵から共有鍵Kを生成 */

  if (!com_dh1536compute(dh_X, pub_key_bin_Y, share_key_bin_X) ||
      !com_dh1536compute(dh_Y, pub_key_bin_X, share_key_bin_Y)) {
    DH_free(dh_X);
    DH_free(dh_Y);
    return (-1);
  }

  /* ダンプ用に文字列化 */

  for (i = 0, len = 0; i < 192; i++) {
    len += sprintf(share_key_str_X + len, "%02X", share_key_bin_X[i]);
  }

  for (i = 0, len = 0; i < 192; i++) {
    len += sprintf(share_key_str_Y + len, "%02X", share_key_bin_Y[i]);
  }

  /* 2つの共有鍵は同じになるはずである */

  fprintf(stdout, "システムXの共有鍵K:\n%s\n", share_key_str_X);
  fprintf(stdout, "システムYの共有鍵K:\n%s\n", share_key_str_Y);

  DH_free(dh_X);
  DH_free(dh_Y);

  return 0;
}
#endif /* _COM_DH_MAIN */

秘密鍵・公開鍵生成関数、共有鍵生成関数、それぞれあとで自作ライブラリ化できるようにcom_をつけています。
以下、上記1~8の手順を順番に行うソース末尾のテストmain関数を実行するためのコンパイルです。

gcc -D_COM_DH_MAIN dh.c -lssl -lcrypto

実行、実行結果

./a.out

システムXの秘密鍵a:
70CB0BEAD4AFBB3B41A5E09C4BC43F868CA085CC52F8AA1B40C862963BD3BC81126E0F24DB93C926DD5DCE80BA42A2DFF756CDEF7111C65DCA7FFE32152E57F716575C54C64E620469163414A5D046CA7BA90A0AF474DD5D2F4BF19450E95E6E4FA76897E9A1EAAEC66300D14F7FBC31B8045237CE7D08C859250C841B3957C960E009066737F27EAC3473CCFDEEEBB3BE0AED7C2D1756EE33EA7CF7574DFA220166B880394DA9DBF0A7656E3AF82B1965F56F2D819B937B15A8983A1024B9F5
システムXの公開鍵A:
D197F972CC6C0F201E40845EEC1DB64E429E71AF8AA04F885BB9753664F8B61ED51C179F47B78E8CEFF6791C1A5DBE469BEF41D860BD1A1C706BA2AFDD265D0B1FE67E149A63230B0A6949BACBDA7DC5B6FEFD692125905EE9B4F942317644E788F1CA9B206F9B917AE9B9115BACBACD039440D7AED0E7D62B2D96233E8BC6659728AC322E46D7D771DFF7D64CC4E84ACB14F078F7E02F7E71A3A12312E3005852C2F6D2C18668EB01D9BB8B558ECB2F0A8E181EFCBB615E0241C39BD3331F86
システムYの秘密鍵b:
559068A2FCCB6AFDFC63F600F91968A56D3462D5EB7960FAF7FEE9032239D26E62B3ECA5C8764F02C37CD51218BA9DB6AC07BFD560FF2696C4D6D071716953EF22CEBD6BC424ABA86FE4182907E241CA46C1CD0AE0BF6520027561E4AF698DA635EC1715AEE621813525837DC091F210C62050042128ED16E60377FC300849908E572B289978CAC5A20CBD53797D6C4E375A43171FD5745EB4C61BA5AE378B4813C07612F6EB4BD308127F765EC8436101D69DD2F88390AF85EED113FF03FA61
システムYの公開鍵B:
67F7AEA9F28E4961543FFABC19171D4141D286CED139F07AE7AE0E87A3F035FA305DA8F25FB583B214320639000D0FF9CC0FACFDAAFD5F261628E84D04AA7742CF6029CFE58186052E7ED7D16BA0F2C5B4F1AAD19F021D0D26AAD8775D955ED486CFCB1C36DADCDE6598BDFAF4510AE71EF64F369C267C816725E9B6CEC5F20FCAA87D829002EEEE53B59B3820EB7E00C0B83AEB6DB7512F880512733A70C5E6B142EF5B872B99F1C6B3FA25F34CFA2FA430761E0DEDB096AB955C9A26EB737F
システムXの共有鍵K:
F538EAF42B189A2EDA578897D35B217F25336CAE68658792B4385CB8FC9FB072C3895C7F1778AFC3F43BA40C2A9C26B577344742C8E47C76A79500E6EBDC1AE67346DD29DCE11DD968802DB0429B1CF8DD74F2A65C332BD8DEF0F0E3071F8AFC8D76C8E4FA6C01DD9C8493AFAAAA8E5CC6B50052CD23F07DF672B3AB446CA4C87440F29D2780DF272F23203E31FB782B8FE149628D9BA3FD95E1489CEAA6AA29176E8DAD551425661AA04D539317B8D6968AD5ECDA8AB01D701F063FE7B4AD90
システムYの共有鍵K:
F538EAF42B189A2EDA578897D35B217F25336CAE68658792B4385CB8FC9FB072C3895C7F1778AFC3F43BA40C2A9C26B577344742C8E47C76A79500E6EBDC1AE67346DD29DCE11DD968802DB0429B1CF8DD74F2A65C332BD8DEF0F0E3071F8AFC8D76C8E4FA6C01DD9C8493AFAAAA8E5CC6B50052CD23F07DF672B3AB446CA4C87440F29D2780DF272F23203E31FB782B8FE149628D9BA3FD95E1489CEAA6AA29176E8DAD551425661AA04D539317B8D6968AD5ECDA8AB01D701F063FE7B4AD90

両システムにおいて秘密鍵・公開鍵ともに全く異なるのに、共有鍵Kは全く同じになるのがポイントで成功です。
以降はこの共有鍵でパケットを暗号化して送受信し合えば良いです。次回は実際にこれを使って暗号化してみます。

※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

【キーワード検索】