今回の卒研ではPICとPCの間で暗号化したデータのやり取りを行いたい.暗号化にはAESを使いたいが,毎回同じ鍵を使用するのは鍵が漏洩したときのことを考えると好ましくない.そのため鍵共有を行う.なお,鍵共有にはECDH鍵共有を使用する.
PICでECDH鍵共有を使うためにはMPLAB Harmonyの中のCrypto Libraryに入っているCRYPT_ECC_DHE_SharedSecretMake関数を使用する.一方,PC側では今回はWindows上のコンソールアプリケーションとして作成するので,.NET Framework 4.7以降に追加されたSystem.Security.Cryptography.ECDiffieHellmanCngクラスを使用する.
しかし,上記の2つの間では公開鍵の共有に互換性がない.
Harmonyの方ではImport/Exportが用意されており,これはX9.63(RFC5480とも同じ)である 04||Hex(X)||Hex(Y) という形式を使う.なお,04は非圧縮,Hex(X)は点のX座標,Hex(Y)は点のY座標を表す.また,.NETの方ではSHA256でハッシュをかけるようにしているので,こちらは手動でSHA256をかける.
一方で.NETの方ではECDiffieHellmanCngクラスにExportParametersメソッドが実装されている.出力されるECParameters構造体にはQという公開鍵を表すECPoint構造体が入っており,Qの中にはX,Yという座標を表すbyte[]型の変数が格納されている.このX,Yを先程のX9.63形式に整形してPIC側に渡せば,PIC側はそれをImportするだけで使うことができる.なお,Importについては以下の手順で行う.
1.ECDiffieHellmanCngクラスのインスタンス(Aとする)を生成し,GenerateKeyメソッドで適当な鍵を生成する.
2.ECParameters構造体の変数(Bとする)を生成し,Qの中にあるX,YをPIC側から受け取ったものに変更する.
3. AでImportParametersメソッドを用いてBをImportする.
実際のソースコードは以下のようになる.なお,抜き出して書くため動作確認はしていない.
PIC
case APP_STATE_KEY_EXCHANGE:
switch(appData.keState){
case KE_STATE_INIT:
CRYPT_RNG_Initialize(&appData.kePicRngCtx);
CRYPT_RNG_Initialize(&appData.kePcRngCtx);
CRYPT_ECC_Initialize(&appData.kePicEccCtx);
CRYPT_ECC_Initialize(&appData.kePcEccCtx);
CRYPT_ECC_DHE_KeyMakeEx(&appData.kePicEccCtx,&appData.kePicRngCtx,32,ECC_SECP256R1);
CRYPT_ECC_DHE_KeyMakeEx(&appData.kePcEccCtx,&appData.kePcRngCtx,32,ECC_SECP256R1);
CRYPT_ECC_PublicExport(&appData.kePicEccCtx,appData.kePicPubkey,sizeof(appData.kePicPubkey),&appData.kePubkeySize);
appData.keState=KE_STATE_PUBKEY_SEND;
break;
case KE_STATE_PUBKEY_SEND:
appData.isWriteComplete = false;
USB_DEVICE_CDC_Write(USB_DEVICE_CDC_INDEX_0,
&appData.writeTransferHandle, appData.kePicPubkey,appData.kePubkeySize,
USB_DEVICE_CDC_TRANSFER_FLAGS_DATA_COMPLETE);
appData.keState=KE_STATE_WAIT_SEND_COMPLETE;
break;
case KE_STATE_WAIT_SEND_COMPLETE:
if(appData.isWriteComplete == true)
{
appData.keState = KE_STATE_PUBKEY_RECEIVE;
}
break;
case KE_STATE_PUBKEY_RECEIVE:
if(appData.isReadComplete == true)
{
appData.isReadComplete = false;
appData.readTransferHandle = USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID;
USB_DEVICE_CDC_Read (USB_DEVICE_CDC_INDEX_0,
&appData.readTransferHandle, appData.kePcPubkey+appData.keReceivedPcPubkeySize,
128);
appData.keReceivedPcPubkeySize+=appData.numBytesRead;
if(appData.readTransferHandle == USB_DEVICE_CDC_TRANSFER_HANDLE_INVALID)
{
appData.state = APP_STATE_ERROR;
break;
}
if(appData.keReceivedPcPubkeySize>=65){
CRYPT_ECC_PublicImport(&appData.kePcEccCtx,appData.kePcPubkey,appData.kePubkeySize);
CRYPT_ECC_DHE_SharedSecretMake(&appData.kePicEccCtx,&appData.kePcEccCtx,appData.keSharedSecret,sizeof(appData.keSharedSecret),&appData.keSharedSecretSize);
appData.keSharedSecretSize;
sizeof(appData.keSharedSecret);
appData.keState=KE_STATE_CALC_SHA256;
}
}
break;
case KE_STATE_CALC_SHA256:
CRYPT_SHA256_Initialize(&appData.sha256Ctx);
CRYPT_SHA256_DataAdd(&appData.sha256Ctx,appData.keSharedSecret,appData.keSharedSecretSize);
CRYPT_SHA256_Finalize(&appData.sha256Ctx,appData.shaSharedSecret);
appData.keState=KE_STATE_COMPLETE;
break;
case KE_STATE_COMPLETE:
memcpy(appData.aesKey,appData.shaSharedSecret,AES_BLOCK_SIZE);
memcpy(appData.aesIv ,"1234567890abcdef",AES_BLOCK_SIZE);
CRYPT_AES_KeySet(&appData.aesCtx,appData.aesKey,AES_BLOCK_SIZE,appData.aesIv,CRYPT_AES_ENCRYPTION);
appData.state = APP_STATE_SCHEDULE_READ;
break;
default:
appData.state = APP_STATE_SCHEDULE_READ;
break;
}
}
PC
ECCurve curve = ECCurve.CreateFromFriendlyName("secp256r1");
ECDiffieHellmanCng pc = new ECDiffieHellmanCng();
ECDiffieHellmanCng pic = new ECDiffieHellmanCng();
pc.GenerateKey(curve);
pic.GenerateKey(curve);
ECParameters pcParam = pc.ExportParameters(true);
ECParameters picParam = pc.ExportParameters(false);
//ここからPCの公開鍵を送る処理
byte[] X = pcParam.Q.X;
byte[] Y = pcParam.Q.Y;
byte[] X963 = { 0x04 };
X963 = Enumerable.Concat(X963, X).ToArray();
X963 = Enumerable.Concat(X963, Y).ToArray();
Console.WriteLine("\npcPubkey : " + ToHexString(X963));
Console.WriteLine("pcX : " + ToHexString(pcParam.Q.X));
Console.WriteLine("pcY : " + ToHexString(pcParam.Q.Y));
//↓SerialPortで送る
spp.WriteData(X963);
//ここまでPCの公開鍵を送る処理
//ここからPICの公開鍵を受け取る処理
picParam.Q.X = picX;
picParam.Q.Y = picY;
pic.ImportParameters(picParam);
//ここまでPICの公開鍵を受け取る処理
pc.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
pc.HashAlgorithm = CngAlgorithm.Sha256;
byte[] sharedSecret = pc.DeriveKeyMaterial(pic.PublicKey);
Console.WriteLine("\npcSharedSecret : " + ToHexString(sharedSecret));