Table of Contents
C のセキュアな通信プロトコルの構築のための前提条件
実装に潜入する前に、開発環境にCコンパイラ(GCCまたはClang)、ソケットプログラミングの基本的な知識、およびOpenSSLライブラリがインストールされていることを確認してください。 OpenSSLは、暗号化アルゴリズムの強力な実装を提供し、Cで安全な通信のための標準的な選択を提供します。 Linuxでは、パッケージマネージャ(例えば、)を介してOpenSSLをインストールします。 Windowsでは、事前にコンパイルされたバイナリを使用して、ソースからビルドします。 TCP / IPサーバーとFamiliarityは、クライアントも想定しています。
暗号資産のブロックを理解する
安全な通信プロトコルは、機密性、完全性、認証の3つの柱に残ります。機密性は暗号化によって達成され、意図した受信者だけがメッセージを読むことができることを保証します。 完全性は、データがトランジットで変更されていないことを保証します。 認証は、通信相手の識別性を検証します。 カスタムプロトコルでは、通常、対称暗号化、メッセージ認証コード(HMAC)、Diffie-Hellmanなどの重要な交換メカニズムを組み合わせます。
AES による対称暗号化
高度な暗号化標準(AES)は、最も広く使用されている対称暗号です。128ビットブロックで動作し、128ビット、192ビット、または256ビットのキーサイズをサポートしています。安全な通信のために、Galois /カウンダモード(GCM)のAESを好むので、単一の操作で機密性と慎重な完全性の両方を提供します。OpenSSLのEVPインターフェイスは、AES-GCMでデータを暗号化および復号化するために簡単です。ECBBやCBCの認証などの古いモードを避け、またはCBCの認証を結合しない限り、暗号化します。
ディーフィーとキー交換–ヘルマン
機密保持チャネルを安全に管理するために、Diffie-Hellman (DH) の鍵交換を使用します。両当事者は秘密鍵を生成し、公開パラメータを交換し、共通の秘密を計算します。 [Diffie-Hellman]は、認証されていない場合は、マンインテージミドル攻撃に対して脆弱です。そのため、このデジタル署名やプレシェアキーを拡張することができます。 製造のために、Diffie-Hellmanは、正規化を転送する) は、正規のキーを使用することができます。
HMACによるメッセージの完全性および認証
メッセージが改ざんされていないことを確認するには、各暗号化された暗号テキストにハッシュベースのメッセージ認証コード(HMAC)を追加します。 HMACは共有秘密鍵と暗号ハッシュ関数(例えば、SHA‐256)を使用します。 受信したデータにHMACを出力し、送信された値と比較します。 このステップは、再再生と改ざん攻撃を防止します。 同様に、AESG-認証には、同じタグをシンプルにすることができます。
CプロジェクトでOpenSSLを設定する
OpenSSL は、プログラムの開始時に、必要なヘッダーと [ と を呼び出します。 エラー処理のために、 と ] を使用します。 リンクするときは、コンパイラフラグに を追加します。 最小限の設定は、次のようになります。
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/err.h>
// Initialize OpenSSL
void init_openssl() {
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}
TCPソケットレイヤーの構築
プロトコルのアンダーリーティング輸送は、信頼できる、注文されたデリバリーを提供するTCPになります。 接続とハンシェイクを開始したクライアントをリスニングするサーバーを作成します。 、 、 []]、 []]、および [、 []、クライアント側で、標準POSIXソケットを使用します。 コピーし、ファイルの所有者のエラーを閉じて、削除した後に、削除してください。
サーバー例 スケルトン
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, 3);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
クライアント例 スケルトン
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
ディーフィー―ヘルマン・キー・エクスチェンジの実施
TCP 接続を確立した後、クライアントとサーバーは DH キー交換を実行します。各サイドは、OpenSSL の API を使用して DH キーペアを生成します。パブリックキーはソケット上に送信され、両側は ]] を使用して共有シークレットを導きます。単純性のために、固定プライムグループ (例えば、) を ]]] からパラメーターで使用します。 実際のプロトコルでは、またはグループを事前に定義します。
// Generate DH parameters
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL);
EVP_PKEY_paramgen_init(pctx);
EVP_PKEY_CTX_set_dh_paramgen_prime_len(pctx, 2048);
EVP_PKEY *params = NULL;
EVP_PKEY_paramgen(pctx, ¶ms);
// Generate key pair
EVP_PKEY_CTX *kctx = EVP_PKEY_CTX_new(params, NULL);
EVP_PKEY_keygen_init(kctx);
EVP_PKEY *my_key = NULL;
EVP_PKEY_keygen(kctx, &my_key);
// Export public key to send
unsigned char *pub_key_der = NULL;
int pub_len = i2d_PUBKEY(my_key, &pub_key_der);
send(sock, pub_key_der, pub_len, 0);
受信終了時、ピアはを使用して公開鍵をインポートし、共有秘密を導きます。 派生した秘密は、AESとHMACの均一キーを生成するために(例えば、SHA-256)ハッシュ化することができます。
AES-GCM でメッセージの暗号化と復号化
AES-GCMは、暗号化と認証タグを1つの操作で提供しているため、優先モードです。 ] で OpenSSL の を使用します。 12 バイトの nonce (IV) と DH 共有シークレットから派生する 256 ビット キーが必要です。 暗号テキストはチャンクで生成され、最終更新後、 16 バイトのタグが取得されます。 nonce、 暗号、タグを、およびタグを一緒に渡します。 [FLT] とタグが、 [F] と 暗号化されたタグがチェックされます。 [F] と、 暗号化されたタグが、 [F] エラーが、 [F] エラーが、 [F] エラーがエラーが [F] エラーが、 [F] されます。 [FATF] と [F] エラーが、 [FCM が、 [FCM エラーが、 [FCM エラーが、 エラーが、 エラーが、 エラーが、 [FCM エラーが、 されます。 [FCM されます。 [FCM エラーが、 エラーが、
// Encryption
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, nonce);
unsigned char ciphertext[1024];
int outlen;
EVP_EncryptUpdate(ctx, ciphertext, &outlen, plaintext, len);
int tmplen;
EVP_EncryptFinal_ex(ctx, ciphertext + outlen, &tmplen);
unsigned char tag[16];
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
HMAC(またはGCMタグをレバレッジする)で整合性を追加
AES-GCM を使わないと、AES-CBC で暗号化し、暗号文を上回る HMAC を計算することができます。OpenSSL から を SHA-256 で使用してください。 暗号文後に HMAC を付加します。 受信機は再計算し比較します。 この方法は 2 つのキーが必要です。 HMAC の 1 つ。 共有秘密から をキーの派手な機能 (KKF) を 変更し、 HFAC を 変更します。 複雑な HFAC は、 を し、 HMAC します。
一緒にそれを置きます: 完全なワークフロー
- クライアントとサーバー間でTCP接続を確立します。
- 両サイドはエピヘムアルディフィー=ヘルマンキーペアを生成します。
- 公開鍵を交換し、共有秘密を計算します。
- 256ビット AES キーと 256ビット HMAC キーを 有効化 (または GCM の同じキーを使用します)。
- クライアントは、非出力(12バイトランダム)を送信します。その後、AES-GCM暗号化されたメッセージとタグを送信します。サーバーは、暗号化し、検証します。
- サーバは、新しい nonce を使って応答を送信します(同じキーで nonces を再利用する)。
- 両サイドは、メッセージの交換を継続できます。長いセッションでは、同じDHハンドシェイクまたはラチェット機構を使用して定期的にリキーを交換します。
セキュリティベストプラクティス
- []強力なランダムな数値ジェネレータを使用します。は、OpenSSLからキー、非個数、およびDHのプライベートキーを生成します。 またはを暗号化目的のために使用しないでください。
- []すべての受信されたデータ。[チェック長、公開鍵パラメータ(例えば、pがプライムであることを確認してください、gはジェネレータです)、および処理前にHMACタグ。
- []ハードコードキーまたはデフォルトは無効です。[]常にキーをセッションごとに新しい交渉して、完璧なフォワードの秘密を提供します。
- [] エラーを優雅に処理します。[]] 復号が失敗した場合、またはHMAC の検証が失敗した場合は、接続を閉じてイベントをログに記録します。失敗が発生した理由は、明らかにしないでください。
- []Keep の依存関係が更新されました。[[ 定期的に OpenSSL を更新して既知の脆弱性をパッチ化します。 []]OpenSSL のドキュメンテーションは、非推奨とベストプラクティスに関するガイダンスを提供します。
- [カスタムプロトコルではなくTLSを使用してコンサイダー。[]]]は、生産システムのために、TLS 1.3のようなよくテストされたプロトコルに依存します。 深い暗号技術を持っている場合を除き、スクラッチからカスタムプロトコルを構築することは、エラーが発生し、推奨されていません。
プロトコルのテスト
クライアントとサーバーを同じマシン(localhost)で実行し、そのメッセージが正しく復号化することを検証することで、実装をテストします。 プロトコルがそれらを拒否することを確認するために改ざんされた暗号文や無効な非部分などのエラーを導入します。 生ネットワークトラフィックを調べるWiresharkのようなツールを使用して、その文脈が見えないことを確認してください。 ユニットテストでは、ソケットレイヤーをモックし、暗号原始性を個別にテストします。 OpenSSLのデバッグは、中間値の値を期待する値の値を検証するのに役立ちます。
コンテンツ
C の安全な通信プロトコルを構築することは優れた学習演習ですが、細部に細心の注意を払って必要です。OpenSSL の実証済みの Diffie-Hellman、AES-GCM、および HMAC の実装を活用することで、機密性、完全性、認証を提供するシステムを作成できます。常に暗号化されたベストプラクティスに従います。KDF で強力なランダム性、派生セッションキーを使用し、ノンセを再利用し、すべてのデータを徹底的に検証しません。プロジェクトを超えて何かのために、このプロトコルは、以下の手順を踏襲します。[LTF] および [LTF] は、 および [LTF] の継続的な改善を継続して、 します。