pythonで暗号演算 ~DH(Diffie-Hellman)による鍵生成~

python,暗号演算,TLS,暗号演算

今回はpythonのcryptographyというライブラリを使って、DHによる共通鍵の生成を行っていきます。

最近業務で認証処理を実装することになったのですが、ECDHとかECCとかよくわからない用語が頻出しました。

そのため、用語の整理と理解を兼ねた実装を行います。

用語

まずは簡単な用語整理から。

略称説明
公開鍵暗号方式公開鍵秘密鍵二つの鍵を使用し、データのやり取りを行う暗号方式。
共通鍵暗号方式共通鍵一つを使用し、データのやり取りを行う暗号方式。
ハイブリッド方式公開鍵秘密鍵を用いて共通鍵を生成し、生成した共通鍵でデータのやり取りを行う暗号方式。
DH公開鍵秘密鍵から、共通鍵を生成する仕組み。
Diffie-Hellmanは考案者二人の名前。
詳細についてはRFC 7919参照。
DHEEはEphemeral(短命)のE。
固定的な公開鍵秘密鍵を利用するのではなく、セッションごとに動的に公開鍵秘密鍵を生成し利用する方式。
生成される共通鍵もセッションごとに動的に使用される。
秘密鍵公開鍵共通鍵すべて使い捨てとなる
ECDHECはElliptic Curve(楕円曲線)のE。
DHによる公開鍵暗号方式の中でも、楕円曲線暗号を用いる方式。
詳細についてはRFC 8037参照。
ECDHEEはEphemeral(短命)のE。
ECDHによる公開鍵暗号の中でも、一時的な鍵値をサーバー側で動的に生成し使用する方式。
ECCElliptic Curve Cryptography(楕円曲線暗号)の略称で、楕円曲線と呼ばれる数学上の概念を利用した暗号技術の総称。
ECCの代表的な実装例として、ECDHECDSAがある。

各用語の関係について簡単に図にしてみました。

今回扱うDH鍵生成は、公開鍵暗号方式と共通鍵暗号方式、両方式のハイブリッド方式でありすべての理解が必要となります。

各方式の詳細については以下わかりやすかったので参考にしてみてください。

実装

今回はcryptographyというライブラリを使用して実装してみました。

そもそもDHの中でどのような処理をやっているのかについては、取り上げてるサイトも多数あるため割愛します。
(私が勉強さぼったというのもなくはない)

DHEによる鍵生成

以下参考に実装。

ソース

DHEの場合、本来は事前にパラメータp、g、yを共有し、共有した情報をもとに公開鍵、秘密鍵を生成します。
rfc7919参照)

以下の例ではp,g,yの共有を共通のパラメータ使用で表現しています。

具体的な設定値(p、g、y)を指定することも可能ですが今回は割愛。

from re import A
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dh
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import serialization

# パラメータ
parameters = dh.generate_parameters(generator=2, key_size=2048)

# Aさんの秘密鍵、公開鍵生成
private_key_A = parameters.generate_private_key()
public_key_A = private_key_A.public_key()

# Bさんの秘密鍵、公開鍵生成
private_key_B = parameters.generate_private_key()
public_key_B = private_key_B.public_key()


def main():
    # 秘密鍵
    chk_private_key(private_key_A)
    chk_private_key(private_key_B)
    # 公開鍵
    chk_public_key(public_key_A)
    chk_public_key(public_key_B)
    # Aさんの秘密鍵とBさんの公開鍵で共通鍵を生成
    derived_key = make_shared_key(private_key_A,public_key_B)
    print("生成した共通鍵 : {}".format(derived_key.hex()))
    # Bさんの秘密鍵とAさんの公開鍵で共通鍵を生成
    derived_key = make_shared_key(private_key_B,public_key_A)
    print("生成した共通鍵 : {}".format(derived_key.hex()))


# 秘密鍵を確認
def chk_private_key(private_key):
    print(private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
        ))


# 公開鍵を確認
def chk_public_key(public_key):
    print(public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo,
        ))


# 秘密鍵と公開鍵で共通鍵を作成
def make_shared_key( private_key , public_key):
    shared_key = private_key.exchange(public_key)
    # Perform key derivation.
    derived_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=b'handshake data',
    ).derive(shared_key)
    return derived_key


if __name__ == '__main__':
	main()

出力結果

出力結果は以下となり、ABそれぞれ公開鍵と秘密鍵の値は前半同じに見えるが中盤以降差分がある。

DHEとしては成り立っているのかな。

b'-----BEGIN PRIVATE KEY-----\nMIICJgIBADCCARcGCSqGSIb3DQEDATCCAQgCggEBAPvVCxm801jQG9MaQE3gxraS\nRdPAWNbwi88OKM69ZogJv9maiHbHGBR5Ay7AZYV7jEtaG9Ko2IGa3PE/BRoXkPQy\nloFOySCWCLBv5B53PJzw1XWEcNs3l5I9JgeYD2fZ7WiaK5dtd7gfUU0ZqpMwMVTz\n/LgBrxwO4NBPiXkV1zu7kuCK9nQJ9rSe5NVK/h8Yu1/m+y9EHad/HJlvRPGWWxS9\n3DqpxFrMXgd6RD+CVUak46GHdUv+ltiJLkXvF1k7M20dRFAcERLGl4YgBcP5ZSrv\nFqmaYBeE14AAOUZvWAoMIVLG4jYQ9xeZcPU20gcFH99JqLXdj03vXd6IRLJCCRMC\nAQIEggEEAoIBAEfujleBo0LX0VOKLsrC2YlQcUrFrpiQGa7Mii2Ek1cnYVmDuow0\nkUHSdYuUq8vbUzyB7tF9myAVtM9x95sgmuAf1IJVgkcgoOrrOiYVvd5uYi12pTg7\necqQowwwGWhYvYdhdl4Jw/wt1ZtOqHBkhdzWVbN5P51l/axQg+BKM6XKH0Yqu1dU\nHC91HGNZ7Ml4TwoYgGP2XYZJV3M4mBn7pGwZ2BIlIor4EEwbAtFs/Wk3JM5m88ed\nNruZsTtMI8qeaYCQskX0VxUi/nVE0TYNvEqNJ/iFq9KxoXzWX+tbcwJaksAbZyAY\n8mtGX32orBf+9CA+sgkaj76jzXZqOC9Y17o=\n-----END PRIVATE KEY-----\n'
b'-----BEGIN PRIVATE KEY-----\nMIICJgIBADCCARcGCSqGSIb3DQEDATCCAQgCggEBAPvVCxm801jQG9MaQE3gxraS\nRdPAWNbwi88OKM69ZogJv9maiHbHGBR5Ay7AZYV7jEtaG9Ko2IGa3PE/BRoXkPQy\nloFOySCWCLBv5B53PJzw1XWEcNs3l5I9JgeYD2fZ7WiaK5dtd7gfUU0ZqpMwMVTz\n/LgBrxwO4NBPiXkV1zu7kuCK9nQJ9rSe5NVK/h8Yu1/m+y9EHad/HJlvRPGWWxS9\n3DqpxFrMXgd6RD+CVUak46GHdUv+ltiJLkXvF1k7M20dRFAcERLGl4YgBcP5ZSrv\nFqmaYBeE14AAOUZvWAoMIVLG4jYQ9xeZcPU20gcFH99JqLXdj03vXd6IRLJCCRMC\nAQIEggEEAoIBAHSDGDp9yzM814T8cjhaG08bfvN0PSNPNQ0sGaDnUaYJEMbga11j\nHIfL+Io/ALeT86jX8MOf+M5dYO8HAGe6gO0+u3MOE8smMZBjsLJ/GQeNAC6heDTB\nwX1J3S8CVOm9jJ4aOD8YAKK4jiDF4lgVmhUx9hMfCP5VwfHAaWt2uDX6DVmPorwz\n/uNFM3KFeRwqMtE8JoyNtBu+9FcIwdSjobChsWMc7jMHuAK3O1qB5qsWIw6SMh14\n1K1e5xPVrumnuiYOMnh6WjC8Nb94OuD/mcsj6GRVyRzjOrIVRdXCtZNqVk2tIQz9\nqqbleYaUW7NCqnATc05bULPsydRvHcBMqLI=\n-----END PRIVATE KEY-----\n'
b'-----BEGIN PUBLIC KEY-----\nMIICJDCCARcGCSqGSIb3DQEDATCCAQgCggEBAPvVCxm801jQG9MaQE3gxraSRdPA\nWNbwi88OKM69ZogJv9maiHbHGBR5Ay7AZYV7jEtaG9Ko2IGa3PE/BRoXkPQyloFO\nySCWCLBv5B53PJzw1XWEcNs3l5I9JgeYD2fZ7WiaK5dtd7gfUU0ZqpMwMVTz/LgB\nrxwO4NBPiXkV1zu7kuCK9nQJ9rSe5NVK/h8Yu1/m+y9EHad/HJlvRPGWWxS93Dqp\nxFrMXgd6RD+CVUak46GHdUv+ltiJLkXvF1k7M20dRFAcERLGl4YgBcP5ZSrvFqma\nYBeE14AAOUZvWAoMIVLG4jYQ9xeZcPU20gcFH99JqLXdj03vXd6IRLJCCRMCAQID\nggEFAAKCAQBPh6l1dqPvCzTtWHHj8toGMM6/WvsgRZFtLhNwB0d03cXQrrh1rCcG\nrUYsCF4iLb4FDFsq6eiFOVtMRdoVi1eWeV3vNZWqsGDgQgM65Anb2O8X5bAc5Qm/\nQvMz5jWcDlNhYm4/ssvwOmQP5ewoSMYKgRaeDI58lsqWeY+S9FAbJQrgej8h80gX\n1MW/tCyFbB7SsIInL9+4lq4EcKZ/7+nL1BzjBq6RHfA0y95zQovyLj3IsJKFpUAs\nRF+wu3HY5r6i6SkNZYciYfUoG/jGj9+hm0PqWFTAVZ8aVnc1k/NueZXE5wkqBAQR\n3T+jT/QoTVFZU1xlcnFJLSuKiwznXNPs\n-----END PUBLIC KEY-----\n'
b'-----BEGIN PUBLIC KEY-----\nMIICJTCCARcGCSqGSIb3DQEDATCCAQgCggEBAPvVCxm801jQG9MaQE3gxraSRdPA\nWNbwi88OKM69ZogJv9maiHbHGBR5Ay7AZYV7jEtaG9Ko2IGa3PE/BRoXkPQyloFO\nySCWCLBv5B53PJzw1XWEcNs3l5I9JgeYD2fZ7WiaK5dtd7gfUU0ZqpMwMVTz/LgB\nrxwO4NBPiXkV1zu7kuCK9nQJ9rSe5NVK/h8Yu1/m+y9EHad/HJlvRPGWWxS93Dqp\nxFrMXgd6RD+CVUak46GHdUv+ltiJLkXvF1k7M20dRFAcERLGl4YgBcP5ZSrvFqma\nYBeE14AAOUZvWAoMIVLG4jYQ9xeZcPU20gcFH99JqLXdj03vXd6IRLJCCRMCAQID\nggEGAAKCAQEAnXlCE9Iw/hIGi5TXwSNTrS4iKVsnhl7Kw+em1WprJijyc37/cSU5\n3MI2+91QOC3LXH/eH8A+ck6jscYGV1gPz/nBoR9bnSq/iTDlInf77h5Sjo/Aa8IV\nSgB9ZxmJSA99GoduRQCEzkfNl+xaHAYhsVlnwoSa/7/b7i6Rxhzl2+Hk3e1CbNKW\nVJ1J3/r0WM9BUGu/I8iOQjKrFSivnd6cbzOlj95slc+Yi4zjgQXXV46i1WZqzZiZ\nDcZX64jw6QouSZsCgGH65UWa4lqyP9zWUA9OJnTaaJo914NTMWeM/3mGZwvsPrfw\n6q4dtQE8wG/8NYTo4509cUjInGGRMUSVAw==\n-----END PUBLIC KEY-----\n'
生成した共通鍵 : 70365843045dd6b49609208ece8b6fb1c52717ff71e5e603e41d0bf7339bb5b2
生成した共通鍵 : 70365843045dd6b49609208ece8b6fb1c52717ff71e5e603e41d0bf7339bb5b2

ECDHEによる鍵生成

以下参考に実装。コードはDHEと大きく変わりありません。

ソース

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import serialization


# Aさんの秘密鍵、公開鍵生成
private_key_A = ec.generate_private_key(ec.SECP384R1())
public_key_A = private_key_A.public_key()

# Bさんの秘密鍵、公開鍵生成
private_key_B = ec.generate_private_key(ec.SECP384R1())
public_key_B = private_key_B.public_key()


def main():
    # 秘密鍵
    chk_private_key(private_key_A)
    chk_private_key(private_key_B)
    # 公開鍵
    chk_public_key(public_key_A)
    chk_public_key(public_key_B)
    # Aさんの秘密鍵とBさんの公開鍵で共通鍵を生成
    derived_key = make_shared_key(private_key_A,public_key_B)
    print("生成した共通鍵 : {}".format(derived_key.hex()))
    # Bさんの秘密鍵とAさんの公開鍵で共通鍵を生成
    derived_key = make_shared_key(private_key_B,public_key_A)
    print("生成した共通鍵 : {}".format(derived_key.hex()))


# 秘密鍵を確認
def chk_private_key(private_key):
    print(private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
        ))


# 公開鍵を確認
def chk_public_key(public_key):
    print(public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo,
        ))


# 秘密鍵と公開鍵で共通鍵を作成
def make_shared_key( private_key , public_key):
    shared_key = private_key.exchange(ec.ECDH(),public_key)
    # Perform key derivation.
    derived_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=b'handshake data',
    ).derive(shared_key)
    return derived_key


if __name__ == '__main__':
	main()

出力結果

b'-----BEGIN PRIVATE KEY-----\nMIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDJFsWoqS7nu5gCbX7p\nG6F7Z+KxnVfTGSPUE04fpiCZ7KOXYS3OsLiz2NxGcId22EChZANiAAQwyOhzEt1y\nLCCme6SVJDbzRfk7z8R5yJylzOoK4gh5BQ3+sp2ORnr/oNuNa7SgbNuL05ug8Ctw\nshsqt/ASd2chW722KyALKw/WM3vkh+wVMIy+XRoQjGAxHm3jS+u0vLU=\n-----END PRIVATE KEY-----\n'
b'-----BEGIN PRIVATE KEY-----\nMIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDYlkw48FFaDs6MDqPs\nA0oPkDcJolyWZWlcI0wu3ktUFSTQ5YvGfUZfp6ijgWxjf0mhZANiAAQsN0T4kUrC\nmMneiSTGBcZUlmHNzglIf71btUW3gNa2X8aHfn506J7ghG9mAJHJqcTOaV0EXmbW\nqQS0IYksgJ24ASGoNi0nBMn+Ixk1wXF9Tho0d1/gEZG8asBndRT+AuU=\n-----END PRIVATE KEY-----\n'
b'-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMMjocxLdciwgpnuklSQ280X5O8/Eecic\npczqCuIIeQUN/rKdjkZ6/6DbjWu0oGzbi9OboPArcLIbKrfwEndnIVu9tisgCysP\n1jN75IfsFTCMvl0aEIxgMR5t40vrtLy1\n-----END PUBLIC KEY-----\n'
b'-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAELDdE+JFKwpjJ3okkxgXGVJZhzc4JSH+9\nW7VFt4DWtl/Gh35+dOie4IRvZgCRyanEzmldBF5m1qkEtCGJLICduAEhqDYtJwTJ\n/iMZNcFxfU4aNHdf4BGRvGrAZ3UU/gLl\n-----END PUBLIC KEY-----\n'
生成した共通鍵 : 2637fbcf7059d39665859d3357c3e9f87a1f6441cbccb697af4a08140a84f5cb
生成した共通鍵 : 2637fbcf7059d39665859d3357c3e9f87a1f6441cbccb697af4a08140a84f5cb

最後に

簡単ですが実際にDH法を用いた共通鍵の生成を実施してみました。

正直opensslで実施した方が使い勝手が良いですね。

一つだけ補足すると、近年のTLSではDHEではなくECDHEが推奨されているらしいです。

理由はセキュリティ面と性能面であるようですが、実際今回のコードを実行してみるとDHEの共通鍵生成処理時間がECDHEの共通鍵生成処理時間よりはるかに大きいことが分かります。