有两个问题,一个是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.