10

以下のコードが示すように、PyCrypto に大きな問題があります。1 つの問題は、テスト ケースが繰り返し失敗するわけではなく、異なるキーを使用する異なるプラットフォームで異なる原因があることです。

テスト ケースでは、Alice と Bob に 2 つのキー セットが提供されることに注意してください。1 つ目は OpenSSL によって生成され、2 つ目は PyCrypto によって生成されます (「代替キー」セクションのコメントを外してください)。

テスト ケースは単純なラウンド トリップです。

  1. アリスは対称鍵を生成し、データを暗号化します
  2. Alice は Bob の公開鍵で対称鍵を暗号化し、暗号化された鍵に彼女の秘密鍵で署名します (この単純なテスト ケースではハッシュは使用されません)。
  3. ボブはアリスの公開鍵で署名を検証し、秘密鍵で対称鍵を復号化します。
  4. Bob は、対称キーを使用してデータを復号化します。

以下は、いくつかのサンプル実行の結果です。

OpenSSL キーを使用する Linux の場合

attempts: 1000
 success: 0
mismatch: 0
    fail: 1000
  Bad signature = 993
  Ciphertext too large = 7

PyCrypto キーを使用する Linux の場合

attempts: 1000
 success: 673
mismatch: 0
    fail: 327
  AES key must be either 16, 24, or 32 bytes long = 3
  Ciphertext too large = 324

OpenSSL キーを使用する Windows の場合

attempts: 1000
 success: 993
mismatch: 0
    fail: 7
  AES key must be either 16, 24, or 32 bytes long = 3
  Bad signature = 4

PyCrypto キーを使用する Windows の場合

attempts: 1000
 success: 994
mismatch: 0
    fail: 6
  AES key must be either 16, 24, or 32 bytes long = 6

テストケースは次のとおりです。

from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes, bytes_to_long
from base64 import b64encode, b64decode

rng = Random.new().read

# openssl genrsa -out alice.rsa 1024
alice_private_key = RSA.importKey('''
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDcWasedZQPkg+//IrJbn/ndn0msT999kejgO0w3mzWSS66Rk3o
Nab/pjWFFp9t6hBlFuERCyyqjwFbqrk0fPeLJBsKQ3TOxDTXdLd50nIPZFgbBmtP
khKTd7tydB6GacMsLqrwI7IlJZcD7ts2quBTNgQAonkr2FJaWyJtTbb95QIDAQAB
AoGAbnIffD/w+7D5ZgCeTAKv54OTjV5QdcGI/OI1gUYrhWjfHAz7JcYms4NK1i+V
r9EfcJv8Kb/RHphZVOoItM9if5Rvaf890r4T+MUUZbl4E7LwEWBuASe6RPyI8Dao
uTOomFlKDjT5VbcBx+WOD+upmrjAwcolyLVulQ5g9Z59pW0CQQDybUKrz4EVzKMx
rpAx0gIzkvNpe/4gxXBueyWqUTASiSwojyZFY6g25KVMuW16fSsRStptm6NpumxB
XVojid7nAkEA6K/7VZd2eMq0O/MP2LT1n6dzx7130Y1g9HWbjsLTRWevGYytcD0O
ldebQxgCbLftuvkcpRtbmIjOsbji4dRfUwJBAJiQolC1+irZ6iouDZkM7U2/wWg1
HC1LlAIzhfS1u2cu5Jdx30fz+7zwEAdE+t0HQL9VODmapTC4ncBVG5EaBykCQB0L
4s8DckmP3EHjjKXbqRG+AIj9kNh60pCRodKHZYIzeDszQW9SX+C6omoUtDDIIQgH
EtlVefCnm026K7BPJ3sCQAdhylJJ/ePSiY9QriPG/KTZR2aprF8eM1UrRebH2S0S
4hZZmqYH/T/akHVxPsyuqyzoZGbVj6kauRhWbBLmpWk=
-----END RSA PRIVATE KEY-----
'''.strip())

# openssl rsa -in alice.rsa -out alice.pub -pubout
alice_public_key = RSA.importKey('''
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcWasedZQPkg+//IrJbn/ndn0m
sT999kejgO0w3mzWSS66Rk3oNab/pjWFFp9t6hBlFuERCyyqjwFbqrk0fPeLJBsK
Q3TOxDTXdLd50nIPZFgbBmtPkhKTd7tydB6GacMsLqrwI7IlJZcD7ts2quBTNgQA
onkr2FJaWyJtTbb95QIDAQAB
-----END PUBLIC KEY-----
'''.strip())

# openssl genrsa -out bob.rsa 1024
bob_private_key = RSA.importKey('''
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDddMPxMRIe34mNYbldimaZ1j4Zw/kqPHkOfbzBhp3XR254eSQO
Ne9DgaLQhw16n4o3FFP8aijlotw/LUfKosEldmiCFuZdTiMP/49a5CbQ/End+Z38
tHIzmGv7qjtkU7K8Eu/J5/y3wqBNAkfejC4j8MNxg8eBBGTq8okra8in8wIDAQAB
AoGBAKmueSAKME81iiipMyWoEPtYe9a0IOsq0Lq4vvMtmS1FTzDB6U12J/D6mGzc
vggxy+5uBfgGw3VINye1IyfxUrlbD0iycMY0dZUgm0QetOOnv8ip/cSKpAilvK+B
H4q9ES0L2M/XOZoFgSmg58HS9UJfcXz95un8WRxSvn26lH3BAkEA/VZoZmTJ5W5f
NwqxbWmOokRn+hBOl1hOvCDbRjuMKWNdQSFSmsQtjbGorNYfT4qrL4SxPbE3ogAe
Pw9zxHbWkwJBAN/IlQtCfncEZ/3wYCS2DxEbO5NPEBTUQgOGzauQ4/lzU5k73gXL
ZiHZYdwNUPY359k+E26AAEBG5A+riI1VZSECQQCYR7Jlqjv6H4g4a8MPQ54rR/dA
R0EWlExvpUhpRS4RStspZUBkK3w+agY8LlGP3Ijd/WMU9Eu+o1eLDFzIQa7lAkEA
kViwJV4M0bSU7oRfjbiJ1KyBZ04kvcKXFb9KejJjP7O+Cnqt28meDkIoo0oq2aC5
/4moCU8t2pGwstTQnitmwQJBAPSIOKujoLp23e4KCbB8ax9meY+2jaWTtf5FPpSV
tHs1WhlITxCowbjF+aWGsypitdT596cHFKAV0Om89vf6R0U=
-----END RSA PRIVATE KEY-----
'''.strip())

# openssl rsa -in bob.rsa -out bob.pub -pubout
bob_public_key = RSA.importKey('''
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDddMPxMRIe34mNYbldimaZ1j4Z
w/kqPHkOfbzBhp3XR254eSQONe9DgaLQhw16n4o3FFP8aijlotw/LUfKosEldmiC
FuZdTiMP/49a5CbQ/End+Z38tHIzmGv7qjtkU7K8Eu/J5/y3wqBNAkfejC4j8MNx
g8eBBGTq8okra8in8wIDAQAB
-----END PUBLIC KEY-----
'''.strip())

# Alternate keys (uncomment for PyCrypto keys)
#alice_private_key = RSA.generate(1024, rng)
#alice_public_key = alice_private_key.publickey()
#bob_private_key = RSA.generate(1024, rng)
#bob_public_key = bob_private_key.publickey()

def generate(data, signature_key, encryption_key):
    # Generate encrypted data
    symmetric_key = rng(16)
    symmetric_cipher = AES.new(symmetric_key)
    padded_data = data + (' ' * (16 - divmod(len(data), 16)[1]))
    encrypted_data = bytes(symmetric_cipher.encrypt(padded_data))

    # Encrypt the symmetric key
    encrypted_key = bytes(encryption_key.encrypt(symmetric_key, None)[0])

    # Sign the encrypted key
    signature = long_to_bytes(signature_key.sign(encrypted_key, None)[0])

    return encrypted_key, signature, encrypted_data

def validate(encrypted_key, signature, encrypted_data, verification_key, decryption_key):
    # Verify the signature
    if not verification_key.verify(encrypted_key, (bytes_to_long(signature),)):
        raise Exception("Bad signature")

    # Decrypt the key
    symmetric_key = decryption_key.decrypt((encrypted_key,))

    # Decrypt the data
    symmetric_cipher = AES.new(symmetric_key)
    return symmetric_cipher.decrypt(encrypted_data).strip()


def test():
    attempts = 1000
    success = 0
    mismatch = 0
    fail = 0
    causes = {}
    for _ in range(attempts):
        data = b64encode(Random.new().read(16))
        try:
            encrypted_key, signature, encrypted_data = \
                generate(data, alice_private_key, bob_public_key)
            result = validate(encrypted_key, signature, 
                encrypted_data, alice_public_key, bob_private_key)
            if result == data:
                success += 1
            else:
                mismatch += 1
        except Exception as e:
            fail += 1
            reason = str(e)
            if reason in causes:
                causes[reason] += 1
            else:
                causes[reason] = 1

    print("attempts: %d" % attempts)
    print(" success: %d" % success)
    print("mismatch: %d" % mismatch)
    print("    fail: %d" % fail)
    for cause, count in causes.items():
        print("  %s = %d" % (cause, count))


test()

PyCrypto がそのようなバスケットケースのように見える理由はありますか?

4

1 に答える 1

4

まず、OpenSSL キーが使用されるケースを考えてみます。最も重要な事実は、ボブの RSA モジュラス ( bn) がアリスの RSA モジュラス ( ) よりわずかに小さいことanです。

エラーCiphertext too bigは「送信側」(つまり、generation関数内) に表示されます。an「署名」する暗号文は、 (暗号化は を法として計算されるため)よりも小さいことが保証されていますが、 (an平文がランダムであるため) よりも大きい場合がありbnます。その場合、サインはできません。

私が知る限り、Linux システムではよくあることですが、GMP ライブラリがインストールされている場合にのみ、チェックが実行され、例外が発生します。Windows では、このようなライブラリをインストールするのは難しく、pycrypto は純粋な python コードに依存しています。その場合、例外は発生しません (発生するはずですが、2 つのバージョンは同じように動作するはずですBad signature)。

AES key must be either 16, 24, or 32 bytes longランダムな AES キーが で始まると、エラーが表示されます0x00。RSA プリミティブはバイト文字列を整数に変換するため、先頭のゼロは処理中に失われ、受信側でそのエラーが返されます。

その場で RSA キーを生成すると、50% のケースbn>anでエラーが少なくなります。

OpenSSL キーを使用した Linux で 100% のテストが失敗する理由を理解することはできませんが、それを説明する同様の理由があると思います。

一般に (これは他のすべての暗号化ライブラリにも当てはまります)、すべての問題の根本的な原因は、未加工のRSA メカニズムを使用していることです。API の制限と正しい使用方法に関する考慮事項に加えて、大きなセキュリティ ホールがあります。何らかの形式の安全なパディングを常に使用する必要があります。そうしないと、攻撃者がスキームを簡単に破ることができます。

PyCrypto では、RSA 署名RSA 暗号化の両方について、PKCS#1 モジュールを介して適切なプロトコルを利用できます。ただし、署名はメッセージ ハッシュ (SHA1 など) に対して行う必要があり、暗号化は RSA モジュラスよりもかなり小さいペイロードに対して行う必要があることに注意してください。

于 2012-05-11T12:29:27.803 に答える