2012-04-18 124 views
0

大家好。PyCrypto:使用RSA和PKCS#1加密兩次字符串#1

我想知道是否可以用PyCrypto做雙重加密RSA/PKCS#1加密。

我有一個服務器,它有自己的RSA密鑰(在安裝上述服務器時使用openssl命令生成)以及可以請求服務器密鑰公共部分的客戶端。另外,該客戶端可以要求服務器爲其生成另一個RSA密鑰(或密鑰對)。在這種情況下,服務器還會保留私鑰(或「整個」RSA密鑰)並向客戶端發送其公鑰部分。

我一直在玩RSA/PKCS和AES加密技術。我創建了一個測試Python文件,只用一個RSA密鑰就可以很好地進行加密。它所做的是使用對稱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() 

但是,當我嘗試重新加密鍵,我得到一個異常。這是有道理的(至少對於幾乎不知道加密的人來說是有意義的),因爲PKCS添加了一個填充,所以當我用服務器的公鑰加密「thisMessagePassword」時,我得到一個256字節的字符串,這對於第二個字符來說太長了PKCS加密器(我一直在做一些「手動測試」,限制似乎是214個字節......我的意思是...這是最後一個不拋出異常的值)。

我知道這可能是一個奇怪的構造,它可能會更有意義使用服務器的公鑰加密和用客戶端密鑰簽名,但我只是想玩一點加密的東西,並嘗試瞭解他們如何工作以及爲什麼。這就是爲什麼任何提示將不勝感激。

預先感謝您!

回答

3

PKCS1OAEP.encrypt的文檔說以下有關它的輸入:

消息(字符串) - 來加密該消息,也被稱爲明文。它可以是可變長度的,但不能超過RSA模數(以字節爲單位)減2,減去散列輸出大小的兩倍。

SHA-1(默認散列函數)具有160位摘要,即20個字節。您看到右聲道的限制:256 = 214 + 2 + 2 * 20。

除此之外,您計劃添加的額外步驟不會增加太多價值。如果你想讓客戶向服務器證明它確實是他,而不是其他人,你應該爲客戶提供私鑰,並讓服務器讓公衆減少一半。在加密步驟之後,客戶端可以使用PKCS#1 PSS將整個包(包裝的AES密鑰+加密數據)簽名爲併發送簽名。服務器將使用客戶端的公鑰驗證源,然後使用其私鑰解密密鑰,最後使用AES解密數據。

1

所以,你在做什麼似乎沒有多大意義。 您想安全地將消息從服​​務器發送到客戶端?

您試圖在服務器公共密鑰下加密消息的代碼,然後在客戶端的公共密鑰下。客戶端將無法讀取它,因爲他永遠不會擁有服務器的私鑰(需要讀取服務器公鑰下加密的郵件)。換句話說,如果服務器和客戶端都有相同的私鑰,那麼你應該只使用AES。你爲什麼做這個 ?

真的,您可能應該使用ssl/tls/https向客戶端發送消息,因爲編寫加密代碼是有問題的,並且您至少在代碼中出現了兩個不良錯誤以及需要修復的錯誤。

  1. 您的IV需要安全隨機。 python隨機調用不是,這就是爲什麼你使用os.random(16)作爲鍵。你應該這樣做的第四代

  2. 您需要使用hmac驗證加密的數據和密鑰的hmac與單獨的隨機密鑰。然後在另一端使用相同的密鑰,通過相同的輸入重新生成hmac,並比較兩者。如果你不這樣做,有人可能會篡改你的數據並使用加密庫中的錯誤來讀取它。

  3. 您發佈的問題:請注意,如上所述,您不應該這樣做,因爲這無關緊要。您需要在新密鑰下使用AES加密rsaEncryptedPasswordWithServer(並使用上述2中的HMAC),然後使用客戶端公鑰對新密鑰進行加密。

1

我不建議你這樣做,或者暗示它有意義,但如果你只是想玩它,這裏是你可以做的。

  1. 確保第一RSA密鑰的應用模量小於的應用第二RSA密鑰的模量。
  2. 使用較小的模數和適當的PKCS#1填充執行第一次RSA加密
  3. 使用較大模數和無填充執行第二次RSA加密。

在解密時,您必須顛倒這些操作的順序。