我有一些C#代码,在电子邮件发送到另一个电子邮件帐户之前加密它的正文,使用AES。我相信C#中AES的默认模式是CBC,我也认为C#中默认的填充方法是PKCS#7。
C#代码应用默认编码对密文进行编码--可能使用机器的活动代码页。服务器和本地机器的活动代码页是cp437。解密是在生产环境中使用C++完成的,它可以工作,我需要一个相当于Python的3.+来处理解密。
发布于 2020-11-10 00:14:27
在发送邮件之前,使用默认编码在C#代码中执行显式解码,根据问题,默认编码是Cp437。然而,对于Cp437,编码失败,而Cp1252则成功。
使用Cp1252会导致隐式地将mail.BodyEncoding从ASCIIEncoding (默认)设置为UTF8Encoding,将mail.BodyTransferEncoding设置为TransferEncoding.Base64。
Cp1252具有(例如,相对于Cp437)未定义的编码点,即0x81、0x8d、0x8f、0x90和0x9d。虽然定义的Cp1252码点可以无任何问题地转换为UTF8 (每个Cp1252字符也对应于有效的Cp1252字符,例如码点U+20AC (欧元):0x80 (Cp1252),0xE282AC (UTF-8)),但尚不清楚如何转换未定义的码点。结果,代码点只是UTF8编码,即0x81、0x8d、0x8f、0x90和0x9d被转换为0xc 281、0xc28d、0xc28f、0xc 290和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#,而不是字符集编码(与默认值ASCIIEncoding ( mail.BodyEncoding )和TransferEncoding.SevenBit ( mail.BodyTransferEncoding)相结合)。否则,这个问题将来可能会继续造成困难。
更新:关于您的问题:为什么Cp437的编码似乎失败了,而Cp1252却成功了?这很奇怪,特别是因为,正如您提到的,在C#代码中使用默认编码(我们发现是Cp437)执行显式解码。
从C#代码(同时,只有通过历史记录才能访问),很明显使用了默认编码。这是无法推断的Cp437,这是你后来提供的信息(最初你说是ISO-8859-1或UTF-8或UTF-16,见历史)。由于发布的消息可以用Cp1252编码,但不能用Cp437编码,所以更有可能的是,Cp1252是使用的默认值,而不是Cp437。
默认编码依赖于平台(另一个原因是不使用它来编码密文),有时也不太透明。例如,在Windows系统中有两个代码页(ANSI与OEM),它们可能有很大的不同(例如,Cp1252在西欧,Cp850在西欧,Cp437在美国)。根据文档,Encoding.Default属性返回ANSI代码页。这里可能只是搞混了。
我不是说这是正确的解释,但这是一个可能的解释。我也不想完全排除使用Cp1252以外的字符集对已发布的密文进行编码是可行的。但是,除了成功的编码和解密之外,Cp1252还有令人信服的理由:
由于使用Cp1252的假设是对被UTF-8解码的字符进行分析,其正确性的概率取决于所分析的密文的长度/数量。张贴的密文已经有一个统计相关的长度,但与进一步的密文核实,以核实这一假设仍然是可取的。
发布于 2020-10-29 15:38:15
我决定编写一个简单的示例程序,以帮助您理解为什么简单地复制和粘贴字符串(简单地放置一个字节数组)会使您丢失数据,因此无法正确地从给定字符串中解密。顺便说一下,您在评论中发布的答案完美地解释了为什么将加密的数据存储在字符串中是一件坏事。
将加密数据存储在字符串中并不是一个好主意,因为它们是用于人类可读的文本,而不是用于任意二进制数据。对于二进制数据,最好使用byte[]
正如注释部分中的许多其他人所提到的,给出任何编码,很可能有一些字符是不可打印的,也就是说,屏幕上没有可打印的表示形式。因此,如果加密的文本包含一些不可打印的字符,屏幕上的字符串解释将丢弃信息。我还将尝试解释为什么在您的生产服务器中,代码“似乎”工作。
提示:你说过
...on接收端以相同的方式工作,首先将加密的电子邮件主体作为字符串(而不是字节数组形式)。
没有像通过电子邮件发送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
}在调试器里身体看上去怎么样?看起来有些字符已经是不可打印的,控制字符如\b backspace或\t选项卡或\f弹出文件/干净视频终端。

无论如何,字符串表示形式是怎样的呢?请注意,我已经使用CTRL +A选择所有可用的字符串信息和CTRL +C它到我的剪贴板。

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

在使用复制粘贴字符串之前,我有26个字节,现在我只有17个字节,这9个字节怎么了?因为它们是不可打印的,所以当我在文本编辑器之间移动它们时,它们根本没有被复制。
由于您在加密之前和之后没有全部信息(因此,正如注释中提到的,丢弃的信息),所以您不能期望在Python中正确地对其进行解密。
为什么IT在生产服务器上工作??要编辑。
https://stackoverflow.com/questions/64589830
复制相似问题