【问题标题】:AES - DecryptionAES - 解密
【发布时间】:2021-02-11 19:42:21
【问题描述】:

我有一些 C# 代码使用 AES 加密电子邮件正文,然后将其发送到另一个电子邮件帐户。我相信 C# 中 AES 的默认模式是 CBC,我也相信 C# 中的默认填充方法是 PKCS#7

C# 代码应用默认编码对密文进行编码 - 可能使用机器的活动代码页。服务器和本地机器的活动代码页是cp437。解密是在生产环境中使用 C++ 完成的,它可以工作,我需要 Python 3.+ 版本来处理解密。

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • 你把问题写得太短了,后面的读者可能无法理解这个问题。根据目前的信息,我的答案(可能也不是另一个)是不可能的。因此,如果您可以撤消此操作会很好(或至少提供答案所需的最少信息,以供后续读者考虑)。谢谢。

标签: python c# encryption cryptography aes


【解决方案1】:

我决定编写一个简单的示例程序来帮助您理解为什么简单地复制和粘贴一个字符串(简单地放置一个字节数组)会使您丢失数据,因此无法从给定的字符串中正确解密。顺便说一句,您在 cmets 中发帖的the answer 完美地解释了为什么将加密数据存储在字符串中是一件坏事。

将加密数据存储在字符串中并不是一个好主意因为它们用于人类可读的文本,而不是用于任意二进制数据。对于二进制数据,最好使用 byte[]

正如评论部分中的许多其他人所提到的那样,在进行任何编码时,很可能存在一些不可打印的字符,即没有可在屏幕上打印的表示。因此,如果您的加密文本包含一些不可打印的字符,屏幕上的字符串解释将丢弃信息。我还将尝试解释为什么在您的生产服务器中,代码“似乎”可以工作。

提示:你提到了

...在接收方以相同的方式工作,首先将加密的电子邮件正文作为字符串(不是字节数组形式)接收

没有通过电子邮件发送string 这样的东西,它只是字节,你只是在接收端看到它的解释。无论如何,让我们回到手头的问题。

首先,让我抽象一下您的加密实现。一位伟大的哲学家曾经said

我们在软件开发中喜欢抽象。

这是您使用 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 主体在调试器中的外观如何?看起来有些字符已经不可打印,控制字符如\b 退格或\t 制表符或\f 弹出纸张/清洁视频终端。

无论如何,字符串表示形式如何呢?请注意,我已使用 CTRL + A 选择所有可用的字符串信息,并 CTRL + C 将其放入我的剪贴板。

现在让我们使用相同的编码还原复制粘贴的字符串,看看是否得到相同的字节数组?剧透:哈哈当然不是

在使用复制粘贴字符串之前我有 26 个字节,现在我只有 17 个字节,这 9 个字节发生了什么?因为它们不可打印,所以当我在文本编辑器之间移动它们时,它们根本没有被复制。

由于您没有加密前后的全部信息(因此如 cmets 丢弃的信息中所述),您不能期望在 Python 中正确解密它。

为什么它在生产服务器中工作 - 待编辑

【讨论】:

  • @John,对不起,John 我没有时间更新帖子。如果您只是进入 SmtpClient.Send 方法的源代码,那么您会看到 MailMessage 的正文被编码回字节数组然后发送到客户端。因此基于复制粘贴字符串不会丢失数据
  • 问题出在复制粘贴上,只能复制粘贴屏幕上可打印的内容。您认为 \0 空终止字符之后的任何字节会发生什么?你能在屏幕上看到它们吗?
【解决方案2】:

在发送邮件之前,使用默认编码在 C# 代码中执行显式解码,根据问题是 Cp437。但是,使用 Cp437 编码失败,而使用 Cp1252 编码成功。

使用 Cp1252 会导致 mail.BodyEncoding 被隐式设置为从 ASCIIEncoding(默认)到 UTF8Encodingmail.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,参见例如here。 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>

编码过于复杂。此外,不能排除特定于平台的依赖关系(例如处理未定义的代码点),因此实现可能依赖于平台,因此不可靠。

因此,最合理的解决方法是在 C# 代码中使用类似 Base64 的 binary-to-text encoding,而不是字符集编码(结合默认值 ASCIIEncoding 用于 mail.BodyEncodingTransferEncoding.SevenBit 用于 mail.BodyTransferEncoding)。否则,这个问题可能会在未来继续造成困难。


更新:关于您的问题:为什么 Cp437 的编码似乎失败,而 Cp1252 的编码似乎成功?这很奇怪,尤其是因为正如您所提到的,在 C# 代码中使用默认编码(我们发现它是 Cp437)执行显式解码。
从 C# 代码(同时只能通过历史记录访​​问),很明显使用了默认编码。无法推断它是 Cp437,这是您之后提供的信息(最初您说它是 ISO-8859-1 或 UTF-8 或 UTF-16,请参阅历史记录)。由于发布的消息可以用 Cp1252 编码,但不能用 Cp437 编码,因此很可能使用 Cp1252 而不是 Cp437。
默认编码取决于平台(另一个不使用它来编码密文的原因),有时不是很透明。例如。在 Windows 系统上,有两个代码页 (ANSI and OEM),它们可能完全不同(例如,ANSI:在西欧通常是 Cp1252,OEM:在西欧通常是 Cp850,在美国是 Cp437)。根据文档,Encoding.Default 属性返回 ANSI 代码页。可能这里只是混淆了。
我并不是说这是正确的解释,但这是一种可能的解释。我也不想完全排除使用 Cp1252 以外的字符集对发布的密文进行编码是可行的可能性。但是,Cp1252有令人信服的理由(除了成功的编码和解密):

  • Base64 解码后的二进制数据无一例外地对应于允许且非常有特点的 UTF-8 序列,因此实际上可以肯定地假定为 UTF-8 编码。
  • UTF-8 解码后的字符依次对应于 Cp1252 字符集的字符,但不对应于 Cp437 字符集(或其他字符集)的字符,因此只能使用 Cp1252 进行编码,但可能没有其他字符集.

由于使用 Cp1252 的假设包括对使用 UTF-8 解码的字符的分析,因此其正确性的概率取决于所分析密文的长度/数量。发布的密文已经具有与统计相关的长度,但仍建议使用进一步的密文进行验证以检查该假设。

【讨论】:

  • @John - 关于您在聊天中提出的问题,请参阅我回答的更新部分。
猜你喜欢
  • 2016-09-22
  • 2012-02-24
  • 2017-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多