簡體   English   中英

AES - 解密

[英]AES - Decryption

我有一些 C# 代碼,它在使用 AES 將電子郵件發送到另一個電子郵件帳戶之前對它的正文進行加密。 我相信 C# 中 AES 的默認模式是CBC並且我也相信 C# 中的默認填充方法是PKCS#7

C# 代碼應用默認編碼對密文進行編碼 - 可能使用機器的活動代碼頁。 服務器和本地機器的活動代碼頁是cp437 解密是在生產環境中使用 C++ 完成的,它可以工作,我需要一個等效的 Python 3.+ 版本來處理解密。

我決定編寫一個簡單的示例程序來幫助您理解為什么簡單地復制和粘貼字符串(簡單地放入一個字節數組)會使您丟失數據,因此無法從給定的字符串中正確解密。 順便說一句,您在評論中發布的答案完美地解釋了為什么將加密數據存儲在字符串中是一件壞事。

將加密數據存儲在字符串中不是一個好主意,因為它們用於人類可讀的文本,而不是任意二進制數據。 對於二進制數據,最好使用 byte[]

正如評論部分中的許多其他人所提到的,給出任何編碼都可能有一些不可打印的字符,即沒有可在屏幕上打印的表示。 因此,如果您的加密文本包含一些不可打印的字符,屏幕上的字符串解釋將丟棄信息。 我還將嘗試解釋為什么在您的生產服務器中,代碼“似乎”有效。

提示:你提到了

...在接收方以相同的方式工作,首先將加密的電子郵件正文作為字符串(不是字節數組形式)

沒有通過電子郵件發送string這樣的東西,它只是字節,您只能在接收端看到它的解釋。 無論如何,讓我們回到手頭的問題。

首先,讓我抽象出您的加密實現。 一位偉大的哲學家曾經說過

我們在軟件開發中喜歡抽象。

這是您使用 CBC 模式的 AES 加密,我試過它有效:

private byte[] EncryptString(string inputText) {
    // great encryption stuff
    return encryptedBytes;
}

在代碼中的某個地方,您可以像這樣使用它:

// you mention in comments that this is your code page
var encoder = Encoding.GetEncoding(437);    
var encrypted = EncryptString(body);
var email = new MailMessage {
       ...
       Body = encoder.GetString(encryptedBytes)
       ...
};

現在讓我們看看它到目前為止的樣子! 一些屏幕截圖正在制作中。 在此處輸入圖片說明 對於給定的密鑰和 iv,我得到了以下 26 元素加密字節數組!

var encyrpted = new byte[] {
                    8, 9, 10, 11, 12, 13, 14, 15, 16,
                    65, 66, 67, 68, 69, 70, 71, 72,
                    73, 74, 75, 0, 1, 2, 3, 4, 5
                 }

Aaaaand body 在調試器中的外觀如何? 看起來有些字符已經不可打印,控制字符如\\b backspace 或\\t tab 或\\f彈出紙張/清潔視頻終端。

在此處輸入圖片說明

無論如何,字符串表示如何呢? 請注意,我已使用 CTRL + A 選擇所有可用的字符串信息並將其 CTRL + C 放入剪貼板。

在此處輸入圖片說明

現在讓我們使用相同的編碼還原復制粘貼的字符串,看看是否得到相同的字節數組? 劇透:哈哈當然不是

在此處輸入圖片說明

在使用復制粘貼字符串之前我有 26 個字節,現在我只有 17 個字節,那 9 個字節發生了什么? 因為它們不可打印,所以當我在文本編輯器之間移動它們時,它們根本就沒有被復制。

由於您沒有加密前后的全部信息(因此如注釋中提到的丟棄信息),您不能期望在 Python 中正確解密它。

為什么它在生產服務器中有效 - 待編輯

在發送郵件之前,使用默認編碼在 C# 代碼中執行顯式解碼,根據問題是 Cp437。 但是,使用 Cp437 編碼失敗,而使用Cp1252則編碼成功。

使用 Cp1252 會導致mail.BodyEncodingASCIIEncoding (默認值)隱式設置為UTF8Encoding ,將mail.BodyTransferEncodingTransferEncoding.Base64

Cp1252 具有(例如與 Cp437 相比)未定義的代碼點,即 0x81、0x8d、0x8f、0x90 和 0x9d。 雖然 Cp1252 定義的代碼點可以毫無問題地轉換為 UTF8(每個 Cp1252 字符也對應一個有效的 Unicode 字符,例如代碼點 U+20AC (€): 0x80 (Cp1252), 0xE282AC (UTF-8)),它是不清楚如何轉換未定義的代碼點。 事實證明,代碼點只是簡單的 UTF8 編碼,即 0x81、0x8d、0x8f、0x90 和 0x9d 被轉換為 0xc281、0xc28d、0xc28f、0xc290 和 0xc29d,例如參見此處 UTF8編碼后,再進行Base64編碼。

對於 Python 代碼中的解密,您只需按照相反的方向進行:首先是 Base64 解碼,然后是 UTF8 解碼,最后是 Cp1252 編碼(記住未定義的代碼點)。 結果是實際的密文。

編碼的一種可能實現是:

def customEncode(ciphertextB64):
    cipherbytes = base64.b64decode(ciphertextB64)
    ciphertext = cipherbytes.decode('utf8')
    undefCodepoints = [0x81, 0x8d, 0x8f, 0x90, 0x9d]
    result = []
    for char in ciphertext:
        if ord(char) in undefCodepoints:
            data = bytes([ord(char)])           
        else:
            data = char.encode('Cp1252')  
        result.append(data)
    return b''.join(result) 

有了這個,發布的密文可以被編碼和解密:

ciphertextB64 = """amDDjAsQJjEzw7nFvSpcIgHFksO/xb3CoF7Cv+KAsMKN4oCZNeKAulxaHwghwo1ExaEQKcOrGMO9
                   Iw4Rw4sncsOLxb3Di8OKwqs0AgdFwo1CB8Oy4oCTPkrDlSbDkTDDtB3Cj2PDocW4AcKxM0bigJnD
                   gsOsw6scw47DlQHCuEEnwqxZwqnDp8KdDBNzw7JKw70aw5/DtcK2FHzigJNJwq1kBsKyw57CpMOi
                   CkPigJQnw5nCgVUcw5bCtl9j4oCcG8OGw5Yiw4zCv1bDrzhBMREIwr1yKMOTT8OMw68OVsOKeGxx
                   wq3Dv0Nkw4vDgcO4wqYCw7DDi8OEFsKjEcOjwrdzw5RUdU/CqwBZw6rDvcKsw67DvE5lwqvDhMKv
                   w5HDiwBy4oC6NsO+w5vigLDDjcOGMHElHA7CjULDnsKtUuKAoH0LUxclPV3FuMO6aWtVAuKAnlcF
                   wr/CsDbCqQEAwr1JMcW+w692w6XCrgbDt8ObZDnDgcOqF8KmwrrCucK9a8Kt4oCdZMOpHiPDigfD
                   hcWSNMOmw7zDhcOPAMOlSzXDs2XDlMKoBcOLdcOMw5PDjeKAusKxw7U94oCgY8Oww6XDrnLigJQj
                   csO7wq8vy5wJMcK4cuKAoMO/MsO/4oCwJcOdXMK0fWxND8uGbiRnBMW4Kw=="""
cipherbytes = customEncode(ciphertextB64)
key = b'\x12\x34\x56\x78\x9A\xBC\xDE\xF0\x12\x34\x56\x78\x9A\xBC\xDE\xF0\x12\x34\x56\x78\x9A\xBC\xDE\xF0\x12\x34\x56\x78\x9A\xBC\xDE\xF0'
iv1 = b'\x02\x13\x24\x35\x46\x57\x68\x79\x8A\x9B\xAC\xBD\xCE\xDF\xE0\xF1'
cipher = AES.new(key, AES.MODE_CBC, iv1)
decrypted = cipher.decrypt(cipherbytes)
decryptedUnpad = unpad(decrypted, AES.block_size)
print(decryptedUnpad) # b'<!DOCTYPE html><html><head><title>Register new RikRhino camera</title></head><body><p>IMEI:324<br/>ServerUrl:https://cmorelm.chpc.ac.za/za<br/>Token:1m7e9LaDp42v6l8hm71l5tZe9z4vO4EFDmiZHiH06e4=<br/>destinationGroup:7<br/>Altitude:4.7<br/>Latitude:-33.7498685982923<br/>Longitude:19.3239212036133</p></body></html>'

解密后的密文為:

<!DOCTYPE html><html><head><title>Register new RikRhino camera</title></head><body><p>IMEI:324<br/>ServerUrl:https://cmorelm.chpc.ac.za/za<br/>Token:1m7e9LaDp42v6l8hm71l5tZe9z4vO4EFDmiZHiH06e4=<br/>destinationGroup:7<br/>Altitude:4.7<br/>Latitude:-33.7498685982923<br/>Longitude:19.3239212036133</p></body></html>

編碼不必要地復雜。 此外,不能排除特定於平台的依賴性(例如處理未定義的代碼點),因此實現可能是平台相關的,因此不可靠。

因此,最合理的解決辦法是使用一個二進制到文本編碼等的Base64在C#代碼,而不是字符集編碼(結合的默認值ASCIIEncodingmail.BodyEncodingTransferEncoding.SevenBitmail.BodyTransferEncoding )。 否則,這個問題將來很可能會繼續造成困難。


更新:關於您的問題:為什么編碼似乎在 Cp437 上失敗,而在 Cp1252 上卻成功了? 這很奇怪,尤其是因為,正如您提到的,使用默認編碼(我們發現是 Cp437)在 C# 代碼中執行顯式解碼。
從 C# 代碼(同時只能通過歷史記錄訪​​問),很明顯使用了默認編碼。 不能推斷是Cp437,這是您后來提供的信息(最初您說它是ISO-8859-1或UTF-8或UTF-16,請參閱歷史)。 由於發布的消息可以使用 Cp1252 編碼,但不能使用 Cp437 編碼,因此更有可能使用的是 Cp1252 而不是 Cp437。
默認編碼是平台相關的(另一個不使用它來編碼密文的原因),有時不是很透明。 例如,在 Windows 系統上有兩個代碼頁( ANSI 和 OEM ),它們可能完全不同(例如,ANSI:西歐通常為 Cp1252,OEM:西歐通常為 Cp850,美國為 Cp437)。 根據文檔, Encoding.Default屬性返回 ANSI 代碼頁。 可能這里只是混淆了。
我並不是說這是正確的解釋,但這是一種可能的解釋。 我也不想完全排除使用 Cp1252 以外的字符集對發布的密文進行編碼是可行的可能性。 但是,Cp1252有令人信服的理由(除了成功的編碼和解密):

  • Base64 解碼后的二進制數據無一例外地對應於允許且非常具有特征的 UTF-8 序列,因此實際上可以肯定地假設 UTF-8 編碼。
  • UTF-8 解碼后的字符依次對應於 Cp1252 字符集的字符,而不對應於 Cp437 字符集(或其他字符集)的字符,因此只能實現使用 Cp1252 的編碼,但可能沒有其他字符集。

由於使用 Cp1252 的假設包括對使用 UTF-8 解碼的字符的分析,因此其正確性的概率取決於所分析密文的長度/數量。 發布的密文已經具有統計上相關的長度,但仍然建議使用進一步的密文進行驗證以檢查該假設。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM