panda's tech note

Advent Calendar 2021:自作メッセージングプロトコル

Day 3: IDの実装

今日からは昨日設計した送受信者IDの発行と管理機能を実装します。まずは、IDの生成とCAによるユニーク性の保証・署名を実装します。

IDの生成

IDを生成するための関数を以下のように実装しました。

import secrets
ID_BITS = 256
def generate_id(tostr=False):
    ## Generate a cryptographically-secure pseudo-random number as an identifier
    ident = secrets.randbits(ID_BITS)
    if tostr:
        return '{:0>64x}'.format(ident)
    else:
        return ident

IDのユニーク性の保証はCAで行うため、暗号論的にセキュアな疑似乱数生成器 (CSPRNG: Cryptographically-Secure Pseudo-Random Number Generator) を使う必要はないのですが、衝突しないに超したことはないので、今回はCSPRNGにより256ビットの乱数を生成しています。generate_id() 関数の引数で真の値を入れた場合、生成した乱数(ID)を16進数文字列にして返します。

IDに対応する鍵ペアの生成

生成したIDが自分のものであることを証明するために、公開鍵暗号方式(RSAやECDSA)の公開鍵・秘密鍵のペアを作成し、ID、公開鍵、署名方式をCAに送って署名した証明書を発行してもらいます。秘密鍵は、この証明書が自分のものであることを証明したり、証明書に含まれる公開鍵で暗号化されたメッセージの復号に必要なため、必ず保存しておきます。

以下のコードでは、256ビットのECDSAで鍵ペア(key) を作成して、これをPEMフォーマットとして秘密鍵(key_pem)、公開鍵(pub_pem)に変換しています。このPEMファイルはファイルとして保存しておきます。

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec

# Generate a key pair using 256-bit ECDSA
key = ec.generate_private_key(ec.SECP256R1(), default_backend())
# Private key PEM
key_pem = key.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption())
# Public key
pub_pem = key.public_key().public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)

Pythonバインディングの OpenSSL モジュールが楕円曲線暗号の鍵制性に対応していないため、上記のコードでは cryptography モジュールを使用しましたが、これ以降は OpenSSL モジュールで処理をするので、以下のように OpenSSL モジュール形式の鍵ペアを生成します。

okey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_pem)

証明書署名要求の発行

CAにIDのユニーク性の検証とIDおよび署名を行ってもらうために、証明書署名要求 (CSR: Certificate Signing Request) を発行します。IDはCommon Nameフィールドに入れます。

上記で作成したCSRを

import OpenSSL
def generate_csr(cn, key):
    # Generate a new CSR
    req = OpenSSL.crypto.X509Req()
    # Set the common name
    req.get_subject().CN = cn
    # Set the public key to the request (with SHA256 fingerprint)
    req.set_pubkey(key)
    req.sign(key, 'sha256')
    return req

なお、本当はリカバリー用にバックアップ鍵ペアの公開鍵を含めた署名要求を発行したいのですが、既存のCSRフォーマットでは対応できないので、一旦この形式で実装します。

実装

上記で説明したID生成、鍵ペアの生成、証明書署名要求の発行を [github:drpnd/advmsg:request_id.py] のように実装しました。このプログラムを実行すると以下のようにIDを生成し、秘密鍵を key.pem、公開鍵を pub.pem、CSRを csr.pem に保存します。なお、CSRは表示もしています。

$ python3 request_id.py
Generated an ID: 62c479d0594052d6fc44ac95dd69899384d6c028db0e0a86a0c9bb2abfbdbb4a
        Private key: key.pem
        Public key: pub.pem
        CSR: csr.pem

-----BEGIN CERTIFICATE REQUEST-----
MIIBBTCBrQIBADBLMUkwRwYDVQQDDEA2MmM0NzlkMDU5NDA1MmQ2ZmM0NGFjOTVk
ZDY5ODk5Mzg0ZDZjMDI4ZGIwZTBhODZhMGM5YmIyYWJmYmRiYjRhMFkwEwYHKoZI
zj0CAQYIKoZIzj0DAQcDQgAE4bGnFbGhey22EYL7Zd6J95GulS5C1JHWacQhvwJx
ObIjGa3WONbCuWpfccI/kjBK1W+6z6CYDmN5L41eaGgbK6AAMAoGCCqGSM49BAMC
A0cAMEQCIH3eB6ns/KwDTd1dAUykKYsFpyEhNESgf53UsOzcj/kYAiB7ocWAPqu2
+A9NDXb3ME8VyxOt8VDsJ7QLy/ggluCOKQ==
-----END CERTIFICATE REQUEST-----

この生成したCSRの中身は以下のように openssl コマンドで確認できます。

$ openssl req -in csr.pem -text
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: CN=62c479d0594052d6fc44ac95dd69899384d6c028db0e0a86a0c9bb2abfbdbb4a
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:e1:b1:a7:15:b1:a1:7b:2d:b6:11:82:fb:65:de:
                    89:f7:91:ae:95:2e:42:d4:91:d6:69:c4:21:bf:02:
                    71:39:b2:23:19:ad:d6:38:d6:c2:b9:6a:5f:71:c2:
                    3f:92:30:4a:d5:6f:ba:cf:a0:98:0e:63:79:2f:8d:
                    5e:68:68:1b:2b
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        Attributes:
            a0:00
    Signature Algorithm: ecdsa-with-SHA256
         30:44:02:20:7d:de:07:a9:ec:fc:ac:03:4d:dd:5d:01:4c:a4:
         29:8b:05:a7:21:21:34:44:a0:7f:9d:d4:b0:ec:dc:8f:f9:18:
         02:20:7b:a1:c5:80:3e:ab:b6:f8:0f:4d:0d:76:f7:30:4f:15:
         cb:13:ad:f1:50:ec:27:b4:0b:cb:f8:20:96:e0:8e:29
-----BEGIN CERTIFICATE REQUEST-----
MIIBBTCBrQIBADBLMUkwRwYDVQQDDEA2MmM0NzlkMDU5NDA1MmQ2ZmM0NGFjOTVk
ZDY5ODk5Mzg0ZDZjMDI4ZGIwZTBhODZhMGM5YmIyYWJmYmRiYjRhMFkwEwYHKoZI
zj0CAQYIKoZIzj0DAQcDQgAE4bGnFbGhey22EYL7Zd6J95GulS5C1JHWacQhvwJx
ObIjGa3WONbCuWpfccI/kjBK1W+6z6CYDmN5L41eaGgbK6AAMAoGCCqGSM49BAMC
A0cAMEQCIH3eB6ns/KwDTd1dAUykKYsFpyEhNESgf53UsOzcj/kYAiB7ocWAPqu2
+A9NDXb3ME8VyxOt8VDsJ7QLy/ggluCOKQ==
-----END CERTIFICATE REQUEST-----

CNにIDがセットされたCSRであることが確認できました。

まとめと明日の予定

今日はID生成、鍵ペアの作成と証明書署名要求の発行を実装しました。明日はCAによるIDのユニーク性の検証と署名を実装しようと思います。