繁体   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