0

こんにちは、みんな。

PyCryptoで二重のRSA/PKCS#1暗号化が可能かどうか疑問に思っていました。

独自の RSA キー (opensslサーバーのインストール時にコマンドで生成される) を持つサーバーと、サーバーのキーの公開部分を要求できるクライアントがあります。また、そのクライアントはサーバーに別の RSA キー (またはキーペア) を生成するように依頼できます。その場合、サーバーは秘密 (または「全体」の RSA キー) も保持し、そのキーの公開部分をクライアントに送信します。

私は RSA/PKCS と AES 暗号化をいじっています。1 つの RSA キーのみで正常に暗号化できるテスト Python ファイルを作成しました。それが行うことは、対称AESシステム(「オンザフライ」で生成されたランダムキーを使用する)でデータを暗号化し、RSA / PKCS#1システムを使用してAESに使用されるパスワードを暗号化し、それを結果に入れることです送信済:

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Interesting links: 
# 1> http://stackoverflow.com/a/9039039/289011
# 2> http://eli.thegreenplace.net/2010/06/25/aes-encryption-of-files-in-python-with-pycrypto/

from Crypto.PublicKey import RSA
import base64
import os
from Crypto.Cipher import AES
import Crypto.Util.number
import random
import struct
import cStringIO
from Crypto.Cipher import PKCS1_OAEP

def encrypt(string):
    #Begin RSA Part to get a cypher that uses the server's public key
    externKeyFilename="/home/borrajax/rsaKeys/server-key.pub"
    externKeyFile = open(externKeyFilename, "r")
    rsaKey= RSA.importKey(externKeyFile, passphrase="F00bAr")
    pkcs1Encryptor=PKCS1_OAEP.new(rsaKey)
    #End RSA Part

    #Begin AES Part
    iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
    thisMessagePassword = os.urandom(16)
    aesEncryptor = AES.new(thisMessagePassword, AES.MODE_CBC, iv)
    chunksize=64*1024
    #End AES Part

    #Begin RSA Encription of the AES Key
    rsaEncryptedPassword = pkcs1Encryptor.encrypt(thisMessagePassword)

    retvalTmp = cStringIO.StringIO()
    retvalTmp.write(struct.pack('<Q', len(string)))
    retvalTmp.write(struct.pack('<Q', len(rsaEncryptedPassword)))
    retvalTmp.write(rsaEncryptedPassword)
    retvalTmp.write(iv)
    while len(string) > 0:
        chunk = string[0:chunksize]
        string = string[chunksize:]
        if len(chunk) % 16 != 0:
            chunk += ' ' * (16 - len(chunk) % 16)
        retvalTmp.write(aesEncryptor.encrypt(chunk))
    return retvalTmp.getvalue()

def decrypt(string):
    stringAsBuffer = cStringIO.StringIO(string)
    retval = str()
    chunksize=64*1024

    externKeyFilename="/home/borrajax/rsaKeys/server-key.pem"
    externKey = open(externKeyFilename, "r")
    rsaKey = RSA.importKey(externKey, passphrase="F00bAr")
    pkcs1Decryptor=PKCS1_OAEP.new(rsaKey)


    origsize = struct.unpack('<Q', stringAsBuffer.read(struct.calcsize('Q')))[0]
    rsaEncryptedPasswordLength = long(struct.unpack('<Q', stringAsBuffer.read(struct.calcsize('Q')))[0])
    rsaEncryptedPassword = stringAsBuffer.read(rsaEncryptedPasswordLength)
    thisMessagePassword = pkcs1Decryptor.decrypt(rsaEncryptedPassword)
    iv = stringAsBuffer.read(16)
    decryptor = AES.new(thisMessagePassword, AES.MODE_CBC, iv)
    while True:
        chunk = stringAsBuffer.read(chunksize)
        if len(chunk) == 0:
            break
        retval += decryptor.decrypt(chunk)
    return retval



if __name__ == "__main__":
    encryptedThingy=encrypt(base64.b64encode("Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉"))
    print "Decrypted thingy: %s" % base64.b64decode(decrypt(encryptedThingy))

ご覧のとおり、AES パスワードはサーバーの RSA キーで暗号化されています。ここで、私はさらに偏執的になり、すでに暗号化されたパスワードをクライアントの公開鍵で暗号化したいので、「暗号化」メソッドは次のようになります。

def encrypt(string):
    #Begin RSA Part to get a cypher that uses the server's public key
    externServerKeyFilename="/home/borrajax/rsaKeys/server-key.pub"
    externServerKeyFile = open(externServerKeyFilename, "r")
    rsaServerKey= RSA.importKey(externServerKeyFile, passphrase="F00bAr")
    pkcs1ServerEncryptor=PKCS1_OAEP.new(rsaServerKey)
    #End RSA Part

    #Begin RSA Part to get a cypher that uses the client's public key
    externClientKeyFilename="/home/borrajax/rsaKeys/client-key.pub"
    externClientKeyFile = open(externClientKeyFilename, "r")
    rsaClientKey= RSA.importKey(externClientKeyFile, passphrase="F00bAr")
    pkcs1ClientEncryptor=PKCS1_OAEP.new(rsaClientKey)
    #End RSA Part


    #Begin AES Part
    iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
    thisMessagePassword = os.urandom(16)
    aesEncryptor = AES.new(thisMessagePassword, AES.MODE_CBC, iv)
    chunksize=64*1024
    #End AES Part

    #Begin RSA Encription of the AES Key
    rsaEncryptedPasswordWithServer = pkcs1ServerEncryptor.encrypt(thisMessagePassword)
    rsaEncryptedPasswordWithServerAndClient = pkcs1ClientEncryptor.encrypt(rsaEncryptedPasswordWithServer) #Katacrasssshh here!! 

    retvalTmp = cStringIO.StringIO()
    retvalTmp.write(struct.pack('<Q', len(string)))
    retvalTmp.write(struct.pack('<Q', len(rsaEncryptedPasswordWithServerAndClient)))
    #...Probably some yadda yadda here with key lengths and stuff so it would help re-build the keys in the server's side...
    retvalTmp.write(rsaEncryptedPasswordWithServerAndClient)
    retvalTmp.write(iv)
    while len(string) > 0:
        chunk = string[0:chunksize]
        string = string[chunksize:]
        if len(chunk) % 16 != 0:
            chunk += ' ' * (16 - len(chunk) % 16)
        retvalTmp.write(aesEncryptor.encrypt(chunk))
    return retvalTmp.getvalue()

しかし、キーを再暗号化しようとすると、ValueError("Plaintext too large")例外が発生します。これは理にかなっています (少なくとも、暗号化についてほとんど何も知らない人にとっては理にかなっています) PKCS はパディングを追加するためthisMessagePassword、サーバーの公開鍵で " " を暗号化すると、256 バイトの文字列が得られますが、これは 2 番目の PKCS には長すぎます。暗号化 (私はいくつかの「手動テスト」を行ってきましたが、制限は 214 バイトのようです... つまり、例外をスローしない最後の値です)。

おそらく奇妙な構造であり、サーバーの公開鍵を暗号化に使用してクライアントの鍵で署名する方が理にかなっていることは承知していますが、暗号化を少し試して、それらがどのように機能するかを理解しようとしています。仕事とその理由。そのため、ヒントをいただければ幸いです。

前もって感謝します!

4

3 に答える 3

3

のドキュメントにPKCS1OAEP.encryptは、その入力について次のように記載されています。

message (string) - 暗号化するメッセージ。プレーンテキストとも呼ばれます。可変長にすることができますが、RSA モジュラス (バイト単位) から 2 を引いた値からハッシュ出力サイズの 2 倍を引いた値までの長さにすることはできません。

SHA-1 (デフォルトのハッシュ関数) には 160 ビットのダイジェスト (20 バイト) があります。256 = 214 + 2 + 2*20 という制限は、ほぼ正しいように聞こえます。

それに加えて、追加する予定の余分なステップはあまり価値をもたらしません。クライアントが本当に彼であり、他の誰かではないことをサーバーに証明したい場合は、クライアントに秘密鍵を提供し、サーバーに公開の半分を保持させる必要があります。暗号化ステップの後、クライアントはパッケージ全体 (ラップされた AES キー + 暗号化されたデータ) に PKCS#1 PSS で署名し、署名を一緒に送信できますサーバーは、クライアントの公開鍵でオリジンを検証し、独自の秘密鍵を使用して鍵を復号化し、最後に AES でデータを復号化します。

于 2012-04-18T22:47:26.597 に答える
1

私はあなたがこれをすることを勧めたり、それが理にかなっていることを示唆したりしませんが、あなたがそれで遊びたいだけなら、ここであなたができることです。

  1. 適用する最初のRSAキーのモジュラスが、適用する2番目のRSAキーのモジュラスよりも小さいことを確認してください。
  2. より小さなモジュラスと適切なPKCS#1パディングを使用して最初のRSA暗号化を実行します
  3. より大きなモジュラスを使用し、パディングなしで2番目のRSA暗号化を実行します。

もちろん、復号化では、これらの操作の順序を逆にする必要があります。

于 2012-04-19T03:30:39.973 に答える
1

だから、あなたがしていることはほとんど意味がないようです。サーバーからクライアントにメッセージを安全に送信したいですか?

あなたが持っているコードは、サーバーの公開鍵の下でメッセージを暗号化し、次にクライアントの公開鍵の下で暗号化しようとします。クライアントはサーバーの秘密鍵 (サーバーの公開鍵で暗号化されたメッセージを読み取るために必要) を決して持つべきではないため、これを読み取ることはできません。別の言い方をすれば、サーバーとクライアントの両方が同じ秘密鍵を持っている場合は、AES を使用する必要があります。なぜあなたはこれをやっている ?

実際には、おそらく ssl/tls/https を使用してクライアントにメッセージを送信するだけでよいでしょう。なぜなら、暗号化コードの記述には問題があり、修正したいエラーと一緒にコードに少なくとも 2 つの悪いエラーがあるからです。

  1. IV は安全にランダムにする必要があります。Python のランダム呼び出しはそうではないため、キーに os.random(16) を使用します。IVでもそうする必要があります

  2. hmac を使用して暗号化されたデータを認証し、別のランダム キーで hmac をキー設定する必要があります。次に、反対側で同じキーを使用して、同じ入力で hmac を再生成し、2 つを比較します。これを行わないと、誰かがデータを改ざんし、暗号ライブラリのエラーを使用してデータを読み取る可能性があります。

  3. あなたが投稿した問題:上で述べたように、これは無意味であるため、これを行うべきではないことに注意してください。新しいキーの下で AES を使用して rsaEncryptedPasswordWithServer を暗号化し (上記の 2 に従って HMAC を使用)、クライアントの公開キーで新しいキーを暗号化する必要があります。

于 2012-04-18T22:12:00.087 に答える