我需要解密一条aes消息.我能够让它在python上工作(使用的是pycrypdome库),但在kotlin/Java上就不成功了.因为解密模式是OCB,而默认情况下Java不支持它,所以我不得不使用Bouly 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()

以下是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代码在cipher.doFinal上抛出异常org.bouncycastle.crypto.InvalidCipherTextException: mac check in OCB failed

文件debug.zip具有完整的问题再生器. 在压缩文件中,您将找到:

  • PY_WINKING_CODE.py-正在运行的python脚本(需要使用pycrypdome才能运行.您可以使用pip install pycryptodome安装PYCRYPTION dome)
  • BC-DEBUG-Gradle项目重现问题

推荐答案

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

Bug in Kotlin code

虽然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

Library bug

另一个问题是,两种实现对于120位(15字节)的随机数长度产生不同的结果,这是OCB允许的最大随机数长度(请参见RFC 7253, 4.2. Encryption: OCB-ENCRYPT):

下面的测试使用固定密钥和明文,并对随机数长度为13、14和15个字节的Python和BC/Kotlin代码之间的结果进行比较:

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

对于随机数长度14和15,Python实现返回相同的密文/标记,而BC/Kotlin返回不同的结果.这表示在Python实现中存在错误.

遗憾的是,RFC 7253, Appendix A. Sample Results只提供随机数都是12个字节的测试向量,因此无法更清楚地分配错误.

也就是说,如果您使用15字节的随机数,两种实现都不兼容,问题很可能是由Python实现引起的.


Edit

Analysis of linked example:

您同时受到两种错误的影响.根据您的 comments ,您已经修复了Kotlin代码(密文和标签的连接)中的错误.由于您在示例中使用的随机数是15个字节(knQgYf1MsOs8smx9GtWM对应于Base64解码为0x92742061fd4cb0eb3cb26c7d1ad58c),我的答案的第二部分中描述的错误就是问题的原因.这个错误不在您的代码中,而是在一个库中,很可能是在Python库中.因此,您无法修复它(至少在不付出更大努力的情况下是如此).

Workarounds

如上面的测试所示,随机数长度为15字节的Python代码似乎简单地忽略了第15个字节,即,如果在您的Python代码中使用14字节的随机数knQgYf1MsOs8smx9GtU=(0x92742061fd4cb0eb3cb26c7d1ad5),则与15字节的随机数knQgYf1MsOs8smx9GtWM一样,Python代码返回same密文和same标记,这就是使用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个字节的随机数knQgYf1MsOs8smx9GtU=,解密也是成功的.这就是这个特定示例的解决方法!

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


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

Java相关问答推荐

为什么JFrame paint()多次绘制同一点(或根本不绘制)?

为什么我的画布没有显示在PFA应用程序中?

Android视图覆盖不阻止点击它后面的控件

我想了解Java中的模块化.编译我的应用程序时,我有一个ResolutionException

Java自定义ThreadPool—暂停任务提交并取消当前排队任务

工件部署期间出错[Tomcat 8.5.45]

将关键字与正文中的_Allowed匹配,但带有__Signing可选后缀

使用GridBagLayout正确渲染

为什么使用JDK21获取锁定锁比使用JDK11慢

如何对多个字段进行分组和排序?

我可以在MacOS上使用什么Java函数来在适当的设备上以适当的音量播放适当的alert 声音?

Java SSLKeyManager出厂密码

如何在盒子的顶部和底部创建两张不同图片(大小相同)的盒子?

带有可选部分的Java DateTimeForMatter

除0错误/抱歉我的句子是PT

将java.util.Date(01.01.0001)转换为java.time.LocalDate将返回29.12.0000

为什么没有加载java.se模块?

谷歌应用引擎本地服务器赢得';t在eclipse上运行

如何使用我的RLE程序解决此问题

spring 数据Elastic search 与 spring 启动数据Elastic search 之间的区别是什么?