Vin*_*tas 3 java bouncycastle aes kotlin
我需要解密 AES 消息。我能够使其在 python 上工作(使用 pycryptodome 库),但在 kotlin/java 上没有成功。由于解密模式是OCB,而Java默认不支持它,所以我不得不使用Bouncy Castle库。
这是工作的 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()
Run Code Online (Sandbox Code Playgroud)
这是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
}
Run Code Online (Sandbox Code Playgroud)
java代码抛出异常org.bouncycastle.crypto.InvalidCipherTextException:OCB中的mac检查在cipher.doFinal上失败
文件debug.zip具有完整的问题重现器。在 zip 文件中您会发现:
有两个问题,一个是 Kotlin 代码中的错误,另一个是库错误:
\n虽然 PyCryptodome 分别处理密文和标签,但 BC/Kotlin 期望两者按以下顺序连接:ciphertext|tag。\n因此必须在 Kotlin 代码中添加
以下行:encryptedMessage += tag
fun decryptAesMessage2(sharedKey: String, encryptedMessageData: Map<String, String>): ByteArray {\n var encryptedMessage = encryptedMessageData["encryptedMessage"]!!.utf8Base64Decode()\n var tag = encryptedMessageData["tag"]!!.utf8Base64Decode()\n encryptedMessage += tag // Fix\n var nonce = encryptedMessageData["nonce"]!!.utf8Base64Decode()\n\n var key = KeyParameter(sharedKey.toByteArray(Charsets.UTF_8))\n var params = AEADParameters(key, tag.size*8, nonce)\n var cipher = OCBBlockCipher(AESEngine(), AESEngine())\n\n cipher.init(false, params)\n\n val out = ByteArray(cipher.getOutputSize(encryptedMessage.size))\n var offset = cipher.processBytes(encryptedMessage, 0, encryptedMessage.size, out, 0)\n offset += cipher.doFinal(out, offset) // Throwing exception here\n\n return out\n}\nRun Code Online (Sandbox Code Playgroud)\n测试:下面,使用Python代码和修复后的Kotlin代码成功解密相同的测试数据:
\nPython:
\nencrypted_message = {\n \'encryptedMessage\': \'LzoelJ9Nv4cruj0JUlxFrNR+mqyO2rvwqDHYwnj0OkvJ+BBvug+ORYVkxA==\', \n \'tag\': \'hl56drXePWiLkVavVwF3/w==\', \n \'nonce\': b64encode(b\'012345678901\').decode()\n}\ndt = decrypt_aes_message(\'01234567890123456789012345678901\', encrypted_message)\nprint(dt) # The quick brown fox jumps over the lazy dog\nRun Code Online (Sandbox Code Playgroud)\n科特林:
\nval encrypted_message = mutableMapOf<String, String>()\nencrypted_message["encryptedMessage"] = "LzoelJ9Nv4cruj0JUlxFrNR+mqyO2rvwqDHYwnj0OkvJ+BBvug+ORYVkxA=="\nencrypted_message["tag"] = "hl56drXePWiLkVavVwF3/w=="\nencrypted_message["nonce"] = Base64.getEncoder().encodeToString("012345678901".toByteArray(Charsets.UTF_8))\nval dt = decryptAesMessage2("01234567890123456789012345678901", encrypted_message)\nprintln(String(dt, Charsets.UTF_8)) // The quick brown fox jumps over the lazy dog\nRun Code Online (Sandbox Code Playgroud)\n另一个问题是,两种实现对于 120 位(15 字节)的随机数长度(OCB 允许的最大随机数长度)产生不同的结果(请参阅RFC 7253, 4.2. 加密:OCB-ENCRYPT):
\n以下测试使用固定密钥和明文,并比较随机数长度为 13、14 和 15 字节的 Python 和 BC/Kotlin 代码之间的结果:
\nKey: b\'01234567890123456789012345678901\'\nPlaintext: b\'testmessage\'\n\nNonce L\xc3\xa4nge 13 bytes 14 bytes 15 bytes\nNonce b\'0123456789012\' b\'01234567890123\' b\'012345678901234\'\nPython (ct|tag) 0xb35a69a245ab18fe3b6bae38b179c2a43b341f67c0451256b76bd7 0xff9be97fcb6e1ac57e6997bc3e84598a83ab70947ccac500fcf75e 0xff9be97fcb6e1ac57e6997bc3e84598a83ab70947ccac500fcf75e\nBC/Kotlin 0xb35a69a245ab18fe3b6bae38b179c2a43b341f67c0451256b76bd7 0xff9be97fcb6e1ac57e6997bc3e84598a83ab70947ccac500fcf75e 0xa4355068324065f2ad194b058bdb86caa67c225b99021dbd588034\nRun Code Online (Sandbox Code Playgroud)\nPython 实现对于随机数长度 14 和 15 返回相同的密文/标签,而 BC/Kotlin 的结果不同。这表明 Python 实现中存在错误。
\n不幸的是,RFC 7253,附录A.示例结果仅提供了随机数均为12字节大小的测试向量,因此无法更清楚地分配该错误。
\n即,如果您使用 15 字节随机数,则两种实现不兼容,其中问题很可能是由 Python 实现引起的。
\n您受到这两个错误的影响。根据您的评论,您已经修复了 Kotlin 代码中的错误(密文和标签的串联)。由于您在示例中使用的随机数是 15 个字节(knQgYf1MsOs8smx9GtWM对应于 Base64 解码0x92742061fd4cb0eb3cb26c7d1ad58c),因此我的答案第二部分中描述的错误是问题的原因。此错误不在您的代码中,而是在其中一个库中,很可能在 Python 库中。因此你无法修复它(至少不付出更大的努力)。
如上面的测试所示,对于 15 个字节的 nonce 长度的 Python 代码似乎只是忽略了第 15 个字节,即如果您在 Python 代码中使用 14 个字节的 nonce knQgYf1MsOs8smx9GtU=( 0x92742061fd4cb0eb3cb26c7d1ad5),则 Python 代码返回相同的密文和相同的密文。标签与 15 字节随机数一样knQgYf1MsOs8smx9GtWM,这就是为什么也可以使用 14 字节随机数进行解密:
key = "f009Cip5hM4Obbb6E2MT5npJBHlc82vD"\nmessage_data = {"encryptedMessage":"XMQx/xbVVTbMdpMiTXVp5XPICm11Vw2pgALpVI0NgbdqLLmikhPuu9M+qQzyOVZlZZBRlscijpyAZDsLGcTSPP54O35oKNp//PuOrWsN/ZZMkCByKCSBysJLRiZV1OjZDg01gi5/nYNbUgGGd8uRGKfBaKjjXngZ1J89GOvDeWPQcjbfbdzd9w+jbZGZ5jnAIChOL1Uqohf+6KHtjR/H06fFTHwB1abzAQrGbCNBNXBmN9+zEu7Auy3NPWKrZ+SL5Nk=","tag":"ZcqXSBqYU5TjgdMC+bMeUQ==","nonce":"knQgYf1MsOs8smx9GtU="}\n\ndecrypted_message = decrypt_aes_message (key, message_data)\nprint (decrypted_message) # https://app.passiv.com/snapTrade/redeemToken?token=v9uJsXYsi%2B6s9kyohisc6DFntJ/yD6m/2zhmO5xp6Vmezcyi8nwx63YtkqnnaogZvFmqs7L99EtZ0mxN9mAQTNoThHj3GaypXXUdiQIzig%3D%3D&clientId=SCANZ&broker=ALPACA\nRun Code Online (Sandbox Code Playgroud)\n如果knQgYf1MsOs8smx9GtU=在Kotlin代码中使用这个14字节的nonce,解密也是成功的。这是此特定示例的解决方法!
只要库错误没有修复,一般的解决方法就是不使用 15 字节的随机数,而只使用最大 14 字节的随机数!
\n在这个网站上还列出了带有 15 字节随机数的测试向量。正如预期的那样,PyCryptodome 实现无法满足这些要求。我已经根据 15 字节随机数测试向量之一在 PyCryptodome 上提交了一个问题:Issue #664。
\n| 归档时间: |
|
| 查看次数: |
174 次 |
| 最近记录: |