PyCrypto会生成错误的签名

时间:2012-05-11 03:53:48

标签: python pycrypto

我遇到了PyCrypto的主要问题,如下面的代码所示。一个问题是测试用例不会以可重复的方式失败,但在使用不同密钥的不同平台上有不同的原因。

请注意,测试用例为Alice和Bob提供了两组密钥,第一组由OpenSSL生成,第二组由PyCrypto生成(取消注释“备用密钥”部分)。

测试案例是一次简单的往返:

  1. Alice生成对称密钥并加密数据
  2. Alice使用Bob的公钥加密对称密钥,然后使用她的私钥对加密密钥进行签名(在这个简单的测试用例中不使用哈希)。
  3. Bob用Alice的公钥验证签名并解密 带有私钥的对称密钥。
  4. Bob使用对称密钥解密数据。
  5. 以下是一些示例运行的结果:

    在Linux上使用OpenSSL密钥

    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似乎是这样一个篮子?

1 个答案:

答案 0 :(得分:4)

首先,我会考虑使用OpenSSL密钥的情况。最重要的事实是Bob的RSA模数(bn)略小于Alice的RSA模数(an)。

错误Ciphertext too big显示在“发件人”处(即generation函数内)。您“签名”的密文是有保证的 小于an(因为加密是以模an为模计算的)但有时(由于明文是随机的)碰巧大于bn。在这种情况下,签名是不可能的。

据我所知,只有安装了GMP库时才会执行检查并引发异常,这在Linux系统中通常就是这种情况。 在Windows上,很难安装这样的库,而pycrypto依赖于纯python代码。在这种情况下不会引发异常(虽然它应该,两个版本应该以相同的方式运行),并且您将默默地将错误的值作为签名(Bad signature)。

随机AES时会显示错误AES key must be either 16, 24, or 32 bytes long 密钥以0x00开头。由于RSA原语将字节字符串转换为整数,因此前导零将在进程中丢失,并且您将在接收端返回该错误。

如果你动态生成RSA密钥,在50%的情况bn>an中你会看到更少的错误。

我无法理解为什么100%的测试在使用OpenSSL密钥的Linux上失败,但我想有类似的理由来解释这一点。

一般而言(这也适用于所有其他加密库),所有问题的根本原因是您正在使用原始 RSA机制。除了考虑因素 关于限制和使用API​​的正确方法,你有一个很大的安全漏洞。某种形式的安全填充必须始终,否则攻击者很容易破坏您的计划。

在PyCrypto中,通过PKCS#1模块可以为RSA signaturesRSA encryption提供适当的协议。但是,请注意必须在消息散列(例如SHA1)上进行签名,并对有效负载进行加密,该负载远小于RSA模数。