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.c | GitHub 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は全く同じになるのがポイントで成功です。
以降はこの共有鍵でパケットを暗号化して送受信し合えば良いです。次回は実際にこれを使って暗号化してみます。
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
Windowsのデスクトップ画面をそのまま配信するための下準備
WindowsでGPUの状態を確認するには(ASUS系監視ソフトの自動起動を停止する)
CORESERVER v1プランからさくらインターネットスタンダートプランへ引っ越しメモ
さくらインターネットでPython MecabをCGIから使う
さくらインターネットのPHPでAnalytics-G4 APIを使う
インクルードパスの調べ方
【Git】特定ファイルを除外する.gitignore
【Ubuntu/Debian】NVIDIA関係のドライバを自動アップデートさせない
【Python】Spacyを使用して文章から出発地と目的地を抜き出す
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
【Windows10】リモートデスクトップ間のコピー&ペーストができなくなった場合の対処法
【Apache】サーバーに同時接続可能なクライアント数を調整する
Windows版Google Driveが使用中と言われアンインストールできない場合
【C/C++】小数点以下の切り捨て・切り上げ・四捨五入
【Linux】iconv/libiconvをソースコードからインストール
VirtualBoxの仮想マシンをWindows起動時に自動起動し終了時に自動サスペンドする
Ubuntu Server 21.10でイーサリアムブロックチェーン【その5】
Googleファミリーリンクで子供の端末の現在地がエラーで取得できない場合