充气城堡在尝试解密 AES 消息时抛出"mac check in OCB failed"



我需要解密AES消息。我能够在python上运行(使用pycryptodome库(,但在kotlin/java上没有成功。由于解密模式是OCB,Java默认情况下不支持它,所以我不得不使用BouncyCastle库。

以下是工作的python代码:

def decrypt_aes_message(shared_key, encrypted_message):
encrypted_msg = b64decode(encrypted_message["encryptedMessage"].encode())
tag = b64decode(encrypted_message["tag"].encode())
nonce = b64decode(encrypted_message["nonce"].encode())
cipher = AES.new(shared_key.encode(), AES.MODE_OCB, nonce=nonce)
return cipher.decrypt_and_verify(encrypted_msg, tag).decode()

这是java代码:

fun decryptAesMessage2(sharedKey: String, encryptedMessageData: Map<String, String>): ByteArray {
var encryptedMessage = encryptedMessageData["encryptedMessage"]!!.utf8Base64Decode()
var tag = encryptedMessageData["tag"]!!.utf8Base64Decode()
var nonce = encryptedMessageData["nonce"]!!.utf8Base64Decode()
var key = KeyParameter(sharedKey.toByteArray(Charsets.UTF_8))
var params = AEADParameters(key, tag.size*8, nonce)
var cipher = OCBBlockCipher(AESEngine(), AESEngine())
cipher.init(false, params)
val out = ByteArray(cipher.getOutputSize(encryptedMessage.size))
var offset = cipher.processBytes(encryptedMessage, 0, encryptedMessage.size, out, 0)
offset += cipher.doFinal(out, offset) // Throwing exception here
return out
}

java代码正在抛出异常org.bouncycastle.crypto.InvalidCipherTextException:OCB中的mac检查在cipher.doFinal 上失败

文件debug.zip具有完整的问题复制程序。在zip文件中,您会发现:

  • py_working_code.py-正在运行的python脚本(需要pycryptodome才能运行。您可以使用pip-install-pycyptodome安装pycrypto dome(
  • bc调试-gradle项目再现问题

有两个问题,一个是Kotlin代码中的错误,另一个是库错误:

Kotlin代码中的错误当PyCryptodome分别处理密文和标签时,BC/Kotlin预计两者的级联顺序为:ciphertext|tag
因此,必须在Kotlin代码中添加encryptedMessage += tag行:

fun decryptAesMessage2(sharedKey: String, encryptedMessageData: Map<String, String>): ByteArray {
var encryptedMessage = encryptedMessageData["encryptedMessage"]!!.utf8Base64Decode()
var tag = encryptedMessageData["tag"]!!.utf8Base64Decode()
encryptedMessage += tag // Fix
var nonce = encryptedMessageData["nonce"]!!.utf8Base64Decode()
var key = KeyParameter(sharedKey.toByteArray(Charsets.UTF_8))
var params = AEADParameters(key, tag.size*8, nonce)
var cipher = OCBBlockCipher(AESEngine(), AESEngine())
cipher.init(false, params)
val out = ByteArray(cipher.getOutputSize(encryptedMessage.size))
var offset = cipher.processBytes(encryptedMessage, 0, encryptedMessage.size, out, 0)
offset += cipher.doFinal(out, offset) // Throwing exception here
return out
}

测试:下面,使用Python代码和固定的Kotlin代码成功解密了相同的测试数据:

Python:

encrypted_message = {
'encryptedMessage': 'LzoelJ9Nv4cruj0JUlxFrNR+mqyO2rvwqDHYwnj0OkvJ+BBvug+ORYVkxA==', 
'tag': 'hl56drXePWiLkVavVwF3/w==', 
'nonce': b64encode(b'012345678901').decode()
}
dt = decrypt_aes_message('01234567890123456789012345678901', encrypted_message)
print(dt) # The quick brown fox jumps over the lazy dog

Kotlin:

val encrypted_message = mutableMapOf<String, String>()
encrypted_message["encryptedMessage"] = "LzoelJ9Nv4cruj0JUlxFrNR+mqyO2rvwqDHYwnj0OkvJ+BBvug+ORYVkxA=="
encrypted_message["tag"] = "hl56drXePWiLkVavVwF3/w=="
encrypted_message["nonce"] = Base64.getEncoder().encodeToString("012345678901".toByteArray(Charsets.UTF_8))
val dt = decryptAesMessage2("01234567890123456789012345678901", encrypted_message)
println(String(dt, Charsets.UTF_8)) // The quick brown fox jumps over the lazy dog

库错误

另一个问题是,两种实现对于120比特(15字节(的随机数长度(OCB的最大允许随机数长度(产生不同的结果(参见RFC 7253,4.2。加密:OCB-ENCRYPT(:

以下测试使用固定密钥和明文,并比较Python和BC/Kotlin代码的nonce长度13、14和15字节的结果:

Key: b'01234567890123456789012345678901'
Plaintext: b'testmessage'
Nonce Länge     13 bytes                                                    14 bytes                                                    15 bytes
Nonce           b'0123456789012'                                            b'01234567890123'                                           b'012345678901234'
Python (ct|tag) 0xb35a69a245ab18fe3b6bae38b179c2a43b341f67c0451256b76bd7    0xff9be97fcb6e1ac57e6997bc3e84598a83ab70947ccac500fcf75e    0xff9be97fcb6e1ac57e6997bc3e84598a83ab70947ccac500fcf75e
BC/Kotlin       0xb35a69a245ab18fe3b6bae38b179c2a43b341f67c0451256b76bd7    0xff9be97fcb6e1ac57e6997bc3e84598a83ab70947ccac500fcf75e    0xa4355068324065f2ad194b058bdb86caa67c225b99021dbd588034

Python实现为nonce长度14和15返回相同的密文/标记,而BC/Kotlin的结果不同。这表示Python实现中存在错误。

不幸的是,RFC 7253,附录A。示例结果只提供了随机数大小均为12字节的测试向量,因此无法更清楚地分配错误。

也就是说,如果您使用一个15字节的nonce,那么这两个实现是不兼容的,问题很可能是由Python实现引起的。


编辑

链接示例的分析:

您同时受到这两个错误的影响。根据您的评论,您已经修复了Kotlin代码中的错误(密文和标签的串联(。由于您在示例中使用的nonce是15个字节(knQgYf1MsOs8smx9GtWM对应于解码为0x92742061fd4cb0eb3cb26c7d1ad58c的Base64(,因此我在回答的第二部分中描述的错误就是问题的原因。这个错误不在您的代码中,但在其中一个库中,很可能在Python库中。因此,你无法修复它(至少不是没有更大的努力(。

解决方案

如上面的测试所示,随机数长度为15字节的Python代码似乎只是忽略了第15个字节,即如果您在Python代码中使用14字节的随机数knQgYf1MsOs8smx9GtU=(0x92742061fd4cb0eb3cb26c7d1ad5(,则Python代码将返回与15字节的随机值knQgYf1MsOs8smx9GtWM相同的密文和标签,这就是为什么使用14字节随机数解密也是可能的:

key = "f009Cip5hM4Obbb6E2MT5npJBHlc82vD"
message_data = {"encryptedMessage":"XMQx/xbVVTbMdpMiTXVp5XPICm11Vw2pgALpVI0NgbdqLLmikhPuu9M+qQzyOVZlZZBRlscijpyAZDsLGcTSPP54O35oKNp//PuOrWsN/ZZMkCByKCSBysJLRiZV1OjZDg01gi5/nYNbUgGGd8uRGKfBaKjjXngZ1J89GOvDeWPQcjbfbdzd9w+jbZGZ5jnAIChOL1Uqohf+6KHtjR/H06fFTHwB1abzAQrGbCNBNXBmN9+zEu7Auy3NPWKrZ+SL5Nk=","tag":"ZcqXSBqYU5TjgdMC+bMeUQ==","nonce":"knQgYf1MsOs8smx9GtU="}
decrypted_message = decrypt_aes_message (key, message_data)
print (decrypted_message) # https://app.passiv.com/snapTrade/redeemToken?token=v9uJsXYsi%2B6s9kyohisc6DFntJ/yD6m/2zhmO5xp6Vmezcyi8nwx63YtkqnnaogZvFmqs7L99EtZ0mxN9mAQTNoThHj3GaypXXUdiQIzig%3D%3D&clientId=SCANZ&broker=ALPACA

如果在Kotlin代码中使用这个14字节的nonceknQgYf1MsOs8smx9GtU=,解密也会成功。这就是这个特定示例的解决方法!

只要库错误没有得到修复,一般的解决方法就是不使用15字节的随机数,而是最多使用14字节的随机值!


在这个网站上,还列出了具有15字节随机数的测试向量。正如预期的那样,PyCryptodome实现没有满足这些要求。我在PyCryptodome上提交了一个基于15字节随机数测试向量之一的问题:问题#664。

最新更新