Store individual XteaKey components separately

This allows us to make them private (rather than internal) and the type
as a whole is immutable.

It does mean we need to convert the key to an IntArray every time we use
it now. However, I hope that the JVM will be smart enough to inline the
toIntArray() method and also smart enough to realise it doesn't escape,
allowing it to allocate it on the stack rather than the heap.

This commit also adds methods for converting XteaKeys to hex, and
converting from hex/integer arrays to an XteaKey object.

Signed-off-by: Graham <gpe@openrs2.dev>
Graham 4 years ago
parent 58335ca6d0
commit 3f59e1764c
  1. 2
      cache/src/test/java/dev/openrs2/cache/Js5CompressionTest.kt
  2. 55
      crypto/src/main/java/dev/openrs2/crypto/Xtea.kt
  3. 4
      crypto/src/test/java/dev/openrs2/crypto/XteaTest.kt

@ -9,7 +9,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
object Js5CompressionTest { object Js5CompressionTest {
private val KEY = XteaKey(intArrayOf(0x00112233, 0x44556677, 0x8899AABB.toInt(), 0xCCDDEEFF.toInt())) private val KEY = XteaKey.fromHex("00112233445566778899AABBCCDDEEFF")
@Test @Test
fun testCompressNone() { fun testCompressNone() {

@ -7,17 +7,54 @@ private const val ROUNDS = 32
private const val BLOCK_SIZE = 8 private const val BLOCK_SIZE = 8
private const val BLOCK_SIZE_MASK = BLOCK_SIZE - 1 private const val BLOCK_SIZE_MASK = BLOCK_SIZE - 1
inline class XteaKey(internal val k: IntArray) { class XteaKey(
private val k0: Int,
private val k1: Int,
private val k2: Int,
private val k3: Int
) {
val isZero: Boolean val isZero: Boolean
get() = k[0] == 0 && k[1] == 0 && k[2] == 0 && k[3] == 0 get() = k0 == 0 && k1 == 0 && k2 == 0 && k3 == 0
fun toIntArray(): IntArray {
return intArrayOf(k0, k1, k2, k3)
}
fun toHex(): String {
return Integer.toUnsignedString(k0, 16).padStart(8, '0') +
Integer.toUnsignedString(k1, 16).padStart(8, '0') +
Integer.toUnsignedString(k2, 16).padStart(8, '0') +
Integer.toUnsignedString(k3, 16).padStart(8, '0')
}
override fun toString(): String {
return toHex()
}
companion object { companion object {
val ZERO = XteaKey(IntArray(4)) val ZERO = XteaKey(0, 0, 0, 0)
fun fromIntArray(a: IntArray): XteaKey {
require(a.size == 4)
return XteaKey(a[0], a[1], a[2], a[3])
}
fun fromHex(s: String): XteaKey {
require(s.length == 32)
val k0 = Integer.parseUnsignedInt(s, 0, 8, 16)
val k1 = Integer.parseUnsignedInt(s, 8, 16, 16)
val k2 = Integer.parseUnsignedInt(s, 16, 24, 16)
val k3 = Integer.parseUnsignedInt(s, 24, 32, 16)
return XteaKey(k0, k1, k2, k3)
}
} }
} }
fun ByteBuf.xteaEncrypt(index: Int, length: Int, key: XteaKey) { fun ByteBuf.xteaEncrypt(index: Int, length: Int, key: XteaKey) {
require(key.k.size == 4) val k = key.toIntArray()
val end = index + (length and BLOCK_SIZE_MASK.inv()) val end = index + (length and BLOCK_SIZE_MASK.inv())
for (i in index until end step BLOCK_SIZE) { for (i in index until end step BLOCK_SIZE) {
@ -26,9 +63,9 @@ fun ByteBuf.xteaEncrypt(index: Int, length: Int, key: XteaKey) {
var v1 = getInt(i + 4) var v1 = getInt(i + 4)
for (j in 0 until ROUNDS) { for (j in 0 until ROUNDS) {
v0 += (((v1 shl 4) xor (v1 ushr 5)) + v1) xor (sum + key.k[sum and 3]) v0 += (((v1 shl 4) xor (v1 ushr 5)) + v1) xor (sum + k[sum and 3])
sum += GOLDEN_RATIO sum += GOLDEN_RATIO
v1 += (((v0 shl 4) xor (v0 ushr 5)) + v0) xor (sum + key.k[(sum ushr 11) and 3]) v1 += (((v0 shl 4) xor (v0 ushr 5)) + v0) xor (sum + k[(sum ushr 11) and 3])
} }
setInt(i, v0) setInt(i, v0)
@ -37,7 +74,7 @@ fun ByteBuf.xteaEncrypt(index: Int, length: Int, key: XteaKey) {
} }
fun ByteBuf.xteaDecrypt(index: Int, length: Int, key: XteaKey) { fun ByteBuf.xteaDecrypt(index: Int, length: Int, key: XteaKey) {
require(key.k.size == 4) val k = key.toIntArray()
val end = index + (length and BLOCK_SIZE_MASK.inv()) val end = index + (length and BLOCK_SIZE_MASK.inv())
for (i in index until end step BLOCK_SIZE) { for (i in index until end step BLOCK_SIZE) {
@ -47,9 +84,9 @@ fun ByteBuf.xteaDecrypt(index: Int, length: Int, key: XteaKey) {
var v1 = getInt(i + 4) var v1 = getInt(i + 4)
for (j in 0 until ROUNDS) { for (j in 0 until ROUNDS) {
v1 -= (((v0 shl 4) xor (v0 ushr 5)) + v0) xor (sum + key.k[(sum ushr 11) and 3]) v1 -= (((v0 shl 4) xor (v0 ushr 5)) + v0) xor (sum + k[(sum ushr 11) and 3])
sum -= GOLDEN_RATIO sum -= GOLDEN_RATIO
v0 -= (((v1 shl 4) xor (v1 ushr 5)) + v1) xor (sum + key.k[sum and 3]) v0 -= (((v1 shl 4) xor (v1 ushr 5)) + v1) xor (sum + k[sum and 3])
} }
setInt(i, v0) setInt(i, v0)

@ -8,9 +8,7 @@ import kotlin.test.assertEquals
object XteaTest { object XteaTest {
private class TestVector(key: String, plaintext: String, ciphertext: String) { private class TestVector(key: String, plaintext: String, ciphertext: String) {
val key = XteaKey(IntArray(4) { val key = XteaKey.fromHex(key)
Integer.parseUnsignedInt(key, it * 8, it * 8 + 8, 16)
})
val plaintext: ByteArray = ByteBufUtil.decodeHexDump(plaintext) val plaintext: ByteArray = ByteBufUtil.decodeHexDump(plaintext)
val ciphertext: ByteArray = ByteBufUtil.decodeHexDump(ciphertext) val ciphertext: ByteArray = ByteBufUtil.decodeHexDump(ciphertext)
} }

Loading…
Cancel
Save