Embed AES/RSA/SHA-1 crypto implementation

This commit is contained in:
2026-09-09 01:23:47 +08:00
parent 74f9e6f529
commit c038082897
49 files changed
+931 -1107

No files matched your search

@@ -32,7 +32,6 @@ public class MinecraftClient internal constructor(
internal val protocolContext: ProtocolContext,
) : PacketEventDispatcher(), CoroutineScope {
internal val stateMachine = ClientStateMachine()
public val networkChannel: NetworkChannel = NetworkChannel(
host, port, stateMachine,
this, protocolContext
@@ -106,4 +105,6 @@ public fun createMinecraftClient(
ioDispatcher = ioDispatcher,
protocolContext = context
)
}
}
internal const val CURRENT_MINECRAFT_PROTOCOL_VERSION: Int = 776
@@ -1,10 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.client
internal const val CURRENT_MINECRAFT_PROTOCOL_VERSION: Int = 776
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.crypto
import cn.rtast.libmc.crypto.NetworkChannelCipher
internal expect class Aes128Cfb8ChannelCipher internal constructor(sharedKey: ByteArray) : NetworkChannelCipher {
override fun encrypt(buffer: ByteArray, offset: Int, length: Int)
override fun decrypt(buffer: ByteArray, offset: Int, length: Int)
override fun close()
}
@@ -0,0 +1,10 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.crypto
internal expect fun rsaEncrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray
@@ -0,0 +1,36 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.crypto
internal fun mcDigestToString(digest: ByteArray): String {
val isNegative = (digest[0].toInt() and 0x80) != 0
val bytes = if (isNegative) twosComplement(digest) else digest
var hex = bytes.joinToString("") { (it.toInt() and 0xFF).toString(16).padStart(2, '0') }
hex = hex.trimStart('0')
if (hex.isEmpty()) hex = "0"
return if (isNegative) "-$hex" else hex
}
internal fun twosComplement(bytes: ByteArray): ByteArray {
val result = ByteArray(bytes.size)
var carry = 1
for (i in bytes.size - 1 downTo 0) {
val inverted = (bytes[i].toInt().inv() and 0xFF) + carry
result[i] = (inverted and 0xFF).toByte()
carry = inverted ushr 8
}
return result
}
internal expect fun sha1Digest(data: ByteArray): ByteArray
internal fun minecraftServerIdHash(serverId: String, secretKey: ByteArray, publicKey: ByteArray): String {
val serverIdBytes = serverId.encodeToByteArray()
for (b in serverIdBytes) require((b.toInt() and 0xFF) <= 0x7F) { "serverId contains non-US-ASCII character" }
return mcDigestToString(sha1Digest(serverIdBytes + secretKey + publicKey))
}
@@ -9,6 +9,8 @@ package cn.rtast.libmc.protocol.event
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.protocol.client.MinecraftClient
import cn.rtast.libmc.protocol.crypto.minecraftServerIdHash
import cn.rtast.libmc.protocol.crypto.rsaEncrypt
import cn.rtast.libmc.protocol.packet.configuration.clientbound.*
import cn.rtast.libmc.protocol.packet.configuration.serverbound.*
import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundDisconnectLoginPacket
@@ -46,8 +48,7 @@ public class InternalPacketDispatcher(private val client: MinecraftClient) {
is ClientboundHelloPacket -> {
val sharedSecret = generateRandom16Bytes()
if (client.isOnlineMode) {
val serverHash = client.protocolContext.sha1Hasher!!
.hash(packet.serverId, sharedSecret, packet.publicKey)
val serverHash = minecraftServerIdHash(packet.serverId, sharedSecret, packet.publicKey)
client.protocolContext.authProvider!!.joinServer(
"https://sessionserver.mojang.com/session/minecraft/join",
client.accessToken!!,
@@ -55,9 +56,8 @@ public class InternalPacketDispatcher(private val client: MinecraftClient) {
serverHash
)
}
val encryptedSecret = client.protocolContext.rsaEncryptor!!.encrypt(packet.publicKey, sharedSecret)
val encryptedVerifyToken =
client.protocolContext.rsaEncryptor!!.encrypt(packet.publicKey, packet.verifyToken)
val encryptedSecret = rsaEncrypt(packet.publicKey, sharedSecret)
val encryptedVerifyToken = rsaEncrypt(packet.publicKey, packet.verifyToken)
client.networkChannel.sendPacket(ServerboundKeyPacket(encryptedSecret, encryptedVerifyToken))
client.networkChannel.session.enableEncryption(sharedSecret)
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.network
import cn.rtast.libmc.crypto.NetworkCipher
import cn.rtast.libmc.crypto.NetworkChannelCipher
import cn.rtast.libmc.network.ReadChannel
import cn.rtast.libmc.network.WriteChannel
@@ -16,7 +16,7 @@ import cn.rtast.libmc.network.WriteChannel
*/
internal class CipherReadChannel(
private val delegate: ReadChannel,
private val crypto: NetworkCipher,
private val crypto: NetworkChannelCipher,
) : ReadChannel {
override suspend fun readFully(out: ByteArray, start: Int, end: Int) {
delegate.readFully(out, start, end)
@@ -42,7 +42,7 @@ internal class CipherReadChannel(
*/
internal class CipherWriteChannel(
private val delegate: WriteChannel,
private val crypto: NetworkCipher,
private val crypto: NetworkChannelCipher,
) : WriteChannel {
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
val length = endIndex - startIndex
@@ -6,7 +6,6 @@
package cn.rtast.libmc.protocol.network
import cn.rtast.libmc.crypto.NetworkCipher
import cn.rtast.libmc.crypto.ProtocolContext
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.network.wrap
@@ -11,6 +11,7 @@ import cn.rtast.libmc.network.RawSocket
import cn.rtast.libmc.network.ReadChannel
import cn.rtast.libmc.network.WriteChannel
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.crypto.Aes128Cfb8ChannelCipher
public class NetworkSession internal constructor(
private val host: String,
@@ -18,7 +19,6 @@ public class NetworkSession internal constructor(
private val context: ProtocolContext,
) {
private var socket: RawSocket? = null
public var readChannel: ReadChannel? = null
private set
@@ -36,7 +36,7 @@ public class NetworkSession internal constructor(
public fun enableEncryption(sharedKey: ByteArray) {
val currentRead = requireNotNull(readChannel)
val currentWrite = requireNotNull(writeChannel)
val cipher = context.cipherFactory!!.invoke(sharedKey)
val cipher = Aes128Cfb8ChannelCipher(sharedKey)
this.readChannel = CipherReadChannel(currentRead, cipher)
this.writeChannel = CipherWriteChannel(currentWrite, cipher)
}
@@ -5,7 +5,7 @@
*/
package test
package client
import cn.rtast.libmc.primitives.FixedBitSet20
import cn.rtast.libmc.primitives.createFixedBitSet20
@@ -5,26 +5,35 @@
*/
package test
package client
import cn.rtast.libmc.network.withCustom
import cn.rtast.libmc.crypto.AuthenticationProvider
import cn.rtast.libmc.packet.ClientboundUnknownPacket
import cn.rtast.libmc.protocol.client.createMinecraftClient
import cn.rtast.libmc.protocol.context.DefaultProtocolContext
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPlayerChatMessagePacket
import cn.rtast.libmc.protocol.packet.play.serverbound.ServerboundChatMessagePacket
import cn.rtast.libmc.protocol.util.generateOfflineUuid
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.utils.io.*
import kotlinx.coroutines.launch
import org.junit.Test
import java.io.File
import kotlinx.io.buffered
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem
import test.KtorNetworkEngine
import kotlin.random.Random
import kotlin.test.Test
import kotlin.time.Clock
import kotlin.uuid.Uuid
class TestClientTestInJvm {
val accessToken = File("src/jvmTest/resources/accessToken.txt").readText()
class TestClient {
val accessToken = SystemFileSystem.source(Path("src/commonTest/resources/accessToken.txt"))
.buffered().use { it.readText() }
private val chatTracker = ClientChatTracker()
private val httpClient = HttpClient()
@Test
fun `test client`() {
@@ -32,7 +41,16 @@ class TestClientTestInJvm {
"127.0.0.1", 25565, "RTAkland",
Uuid.parse("bb033844-e68e-4909-a636-1a5d1821ddc4"),
accessToken,
context = DefaultProtocolContext
context = {
socketEngine = KtorNetworkEngine()
authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash ->
val status = httpClient.post(url) {
headers { header("Content-Type", "application/json") }
setBody("{\"accessToken\":\"$accessToken\", \"selectedProfile\":\"$uuid\", \"serverId\":\"$serverIdHash\"}")
}
require(status.status == HttpStatusCode.NoContent) { status.bodyAsText() }
}
}
)
cli.on { packet, direction -> println("$direction -> $packet") }
cli.launch { cli.connect() }
@@ -45,15 +63,10 @@ class TestClientTestInJvm {
val cli = createMinecraftClient(
"127.0.0.1", 25566, "11",
generateOfflineUuid("11"), null,
context = DefaultProtocolContext.withCustom {
context = {
socketEngine = KtorNetworkEngine()
}
)
// cli.on { packet, direction ->
// if (packet !is ClientboundWaypointPacket)
// println("$direction -> $packet")
// }
cli.onPacket<ClientboundUnknownPacket> {
println(it)
val snapshot = chatTracker.prepareForOutgoingMessage()
@@ -69,10 +82,6 @@ class TestClientTestInJvm {
cli.onPacket<ClientboundPlayerChatMessagePacket> {
chatTracker.onReceivePlayerChat(it.messageSignature)
}
// cli.onPacket<ClientboundServerDataPacket> { println(it) }
// cli.onPacket<ClientboundServerLinksPacket> { println(it) }
// cli.onPacket<ClientboundCodeOfConductPacket> { println(it) }
// cli.onPacket<ClientboundPlayerInfoUpdatePacket> { println(it) }
cli.launch { cli.connect() }
while (true) {
}
@@ -1,34 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package test
import cn.rtast.libmc.protocol.client.createMinecraftClient
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.uuid.Uuid
class TestClient {
@Test
fun `test client`() = runTest {
val cli = createMinecraftClient(
"127.0.0.1",
25565,
"RTAkland",
Uuid.parse("bb033844-e68e-4909-a636-1a5d1821ddc4"),
null
) {
// rsaEncryptor = RSA1024Encryptor { data, sharedKey -> }
}
cli.launch { cli.connect() }
cli.on { packet, direction -> println("${direction} -> $packet") }
while (true) {
}
}
}
@@ -1,18 +1,18 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
* Date: 2026/9/8
*/
package test
package cn.rtast.libmc.protocol.crypto
import cn.rtast.libmc.crypto.NetworkCipher
import cn.rtast.libmc.crypto.NetworkChannelCipher
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class JvmAesCipher(sharedKey: ByteArray) : NetworkCipher {
internal actual class Aes128Cfb8ChannelCipher internal actual constructor(sharedKey: ByteArray) : NetworkChannelCipher {
private val encryptCipher = Cipher.getInstance("AES/CFB8/NoPadding").apply {
init(Cipher.ENCRYPT_MODE, SecretKeySpec(sharedKey, "AES"), IvParameterSpec(sharedKey))
}
@@ -21,11 +21,13 @@ class JvmAesCipher(sharedKey: ByteArray) : NetworkCipher {
init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedKey, "AES"), IvParameterSpec(sharedKey))
}
override fun encrypt(buffer: ByteArray, offset: Int, length: Int) {
actual override fun encrypt(buffer: ByteArray, offset: Int, length: Int) {
encryptCipher.update(buffer, offset, length, buffer, offset)
}
override fun decrypt(buffer: ByteArray, offset: Int, length: Int) {
actual override fun decrypt(buffer: ByteArray, offset: Int, length: Int) {
decryptCipher.update(buffer, offset, length, buffer, offset)
}
actual override fun close() {}
}
@@ -0,0 +1,20 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.crypto
import java.security.KeyFactory
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
internal actual fun rsaEncrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
val keySpec = X509EncodedKeySpec(publicKeyBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val publicKey = keyFactory.generatePublic(keySpec)
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(data)
}
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.crypto
import java.security.MessageDigest
internal actual fun sha1Digest(data: ByteArray): ByteArray =
MessageDigest.getInstance("SHA-1").digest(data)
@@ -1,89 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
*/
package test
import cn.rtast.libmc.network.RawSocket
import cn.rtast.libmc.network.ReadChannel
import cn.rtast.libmc.network.SocketEngine
import cn.rtast.libmc.network.WriteChannel
import java.io.InputStream
import java.io.OutputStream
import java.net.InetSocketAddress
import java.net.Socket
class JavaSocketEngine : SocketEngine {
override fun create(host: String, port: Int): RawSocket = JavaNetworkSocket(host, port)
}
class JavaNetworkSocket(
private val host: String,
private val port: Int,
private val connectTimeoutMs: Int = 10000,
) : RawSocket {
private lateinit var socket: Socket
private lateinit var readChannel: JavaReadChannel
private lateinit var writeChannel: JavaWriteChannel
override suspend fun connect() {
val s = Socket()
s.tcpNoDelay = true
s.connect(InetSocketAddress(host, port), connectTimeoutMs)
socket = s
readChannel = JavaReadChannel(s.getInputStream())
writeChannel = JavaWriteChannel(s.getOutputStream())
}
override fun openReadChannel(): ReadChannel = readChannel
override fun openWriteChannel(): WriteChannel = writeChannel
override fun close() {
if (::socket.isInitialized && !socket.isClosed) {
runCatching { socket.close() }
}
}
}
class JavaReadChannel(private val inputStream: InputStream) : ReadChannel {
override suspend fun readByte(): Byte {
val b = inputStream.read()
if (b == -1) throw IllegalStateException("Socket stream reached EOF while reading byte")
return b.toByte()
}
override suspend fun readBytes(length: Int): ByteArray {
val buffer = ByteArray(length)
readFullyInternal(buffer, 0, length)
return buffer
}
override suspend fun readFully(out: ByteArray, start: Int, end: Int) {
readFullyInternal(out, start, end - start)
}
private fun readFullyInternal(out: ByteArray, offset: Int, length: Int) {
var bytesRead = 0
while (bytesRead < length) {
val count = inputStream.read(out, offset + bytesRead, length - bytesRead)
if (count == -1) {
throw IllegalStateException("Socket stream closed unexpectedly (read $bytesRead of $length bytes)")
}
bytesRead += count
}
}
}
class JavaWriteChannel(private val outputStream: OutputStream) : WriteChannel {
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
outputStream.write(value, startIndex, endIndex - startIndex)
}
override suspend fun flush() {
outputStream.flush()
}
}
@@ -0,0 +1,396 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.crypto
import cn.rtast.libmc.crypto.NetworkChannelCipher
internal actual class Aes128Cfb8ChannelCipher internal actual constructor(sharedKey: ByteArray) : NetworkChannelCipher {
private val encryptor = Aes128Cfb8(sharedKey, sharedKey)
private val decryptor = Aes128Cfb8(sharedKey, sharedKey)
actual override fun encrypt(buffer: ByteArray, offset: Int, length: Int) {
val stream = ByteArray(Aes128Cfb8.BLOCK_SIZE)
for (i in offset until (offset + length)) {
encryptor.getIv(stream)
encryptor.encryptBlock(stream, stream)
val ciphertext = (buffer[i].toInt() and 0xff xor stream[0].toInt() and 0xff).toByte()
encryptor.shiftFeedback(ciphertext)
buffer[i] = ciphertext
}
}
actual override fun decrypt(buffer: ByteArray, offset: Int, length: Int) {
val stream = ByteArray(Aes128Cfb8.BLOCK_SIZE)
for (i in offset until (offset + length)) {
val ciphertext = buffer[i]
decryptor.getIv(stream)
decryptor.encryptBlock(stream, stream)
buffer[i] = (ciphertext.toInt() and 0xff xor stream[0].toInt() and 0xff).toByte()
decryptor.shiftFeedback(ciphertext)
}
}
actual override fun close() {}
}
/**
* PERFORMANCE IMPROVEMENT REQUIRED.
*/
private class Aes128Cfb8(key: ByteArray, iv: ByteArray) {
companion object {
const val BLOCK_SIZE = 16
private val S_BOX = byteArrayOf(
0x63, 0x7c, 0x77, 0x7b, 0xf2.toByte(), 0x6b, 0x6f, 0xc5.toByte(),
0x30, 0x01, 0x67, 0x2b, 0xfe.toByte(), 0xd7.toByte(), 0xab.toByte(), 0x76,
0xca.toByte(), 0x82.toByte(), 0xc9.toByte(), 0x7d, 0xfa.toByte(), 0x59,
0x47, 0xf0.toByte(), 0xad.toByte(), 0xd4.toByte(), 0xa2.toByte(),
0xaf.toByte(), 0x9c.toByte(), 0xa4.toByte(), 0x72,
0xc0.toByte(), 0xb7.toByte(), 0xfd.toByte(), 0x93.toByte(), 0x26,
0x36, 0x3f, 0xf7.toByte(), 0xcc.toByte(), 0x34, 0xa5.toByte(),
0xe5.toByte(), 0xf1.toByte(), 0x71, 0xd8.toByte(), 0x31, 0x15,
0x04, 0xc7.toByte(), 0x23, 0xc3.toByte(), 0x18, 0x96.toByte(),
0x05, 0x9a.toByte(), 0x07, 0x12, 0x80.toByte(), 0xe2.toByte(),
0xeb.toByte(), 0x27, 0xb2.toByte(), 0x75,
0x09, 0x83.toByte(), 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0.toByte(),
0x52, 0x3b, 0xd6.toByte(), 0xb3.toByte(), 0x29, 0xe3.toByte(),
0x2f, 0x84.toByte(), 0x53, 0xd1.toByte(), 0x00, 0xed.toByte(),
0x20, 0xfc.toByte(), 0xb1.toByte(), 0x5b, 0x6a, 0xcb.toByte(),
0xbe.toByte(), 0x39, 0x4a, 0x4c, 0x58, 0xcf.toByte(),
0xd0.toByte(), 0xef.toByte(), 0xaa.toByte(), 0xfb.toByte(),
0x43, 0x4d, 0x33, 0x85.toByte(), 0x45, 0xf9.toByte(),
0x02, 0x7f, 0x50, 0x3c, 0x9f.toByte(), 0xa8.toByte(),
0x51, 0xa3.toByte(), 0x40, 0x8f.toByte(), 0x92.toByte(),
0x9d.toByte(), 0x38, 0xf5.toByte(), 0xbc.toByte(),
0xb6.toByte(), 0xda.toByte(), 0x21, 0x10, 0xff.toByte(),
0xf3.toByte(), 0xd2.toByte(), 0xcd.toByte(), 0x0c,
0x13, 0xec.toByte(), 0x5f, 0x97.toByte(), 0x44, 0x17,
0xc4.toByte(), 0xa7.toByte(), 0x7e, 0x3d, 0x64, 0x5d,
0x19, 0x73, 0x60, 0x81.toByte(), 0x4f, 0xdc.toByte(),
0x22, 0x2a, 0x90.toByte(), 0x88.toByte(), 0x46, 0xee.toByte(),
0xb8.toByte(), 0x14, 0xde.toByte(), 0x5e, 0x0b,
0xdb.toByte(), 0xe0.toByte(), 0x32, 0x3a, 0x0a, 0x49,
0x06, 0x24, 0x5c, 0xc2.toByte(), 0xd3.toByte(),
0xac.toByte(), 0x62, 0x91.toByte(), 0x95.toByte(), 0xe4.toByte(),
0x79, 0xe7.toByte(), 0xc8.toByte(), 0x37, 0x6d,
0x8d.toByte(), 0xd5.toByte(), 0x4e, 0xa9.toByte(), 0x6c,
0x56, 0xf4.toByte(), 0xea.toByte(), 0x65, 0x7a,
0xae.toByte(), 0x08, 0xba.toByte(), 0x78, 0x25,
0x2e, 0x1c, 0xa6.toByte(), 0xb4.toByte(), 0xc6.toByte(),
0xe8.toByte(), 0xdd.toByte(), 0x74, 0x1f, 0x4b,
0xbd.toByte(), 0x8b.toByte(), 0x8a.toByte(), 0x70,
0x3e, 0xb5.toByte(), 0x66, 0x48, 0x03, 0xf6.toByte(),
0x0e, 0x61, 0x35, 0x57, 0xb9.toByte(), 0x86.toByte(),
0xc1.toByte(), 0x1d, 0x9e.toByte(), 0xe1.toByte(),
0xf8.toByte(), 0x98.toByte(), 0x11, 0x69, 0xd9.toByte(),
0x8e.toByte(), 0x94.toByte(), 0x9b.toByte(), 0x1e,
0x87.toByte(), 0xe9.toByte(), 0xce.toByte(), 0x55,
0x28, 0xdf.toByte(), 0x8c.toByte(), 0xa1.toByte(),
0x89.toByte(), 0x0d, 0xbf.toByte(), 0xe6.toByte(),
0x42, 0x68, 0x41, 0x99.toByte(), 0x2d, 0x0f,
0xb0.toByte(), 0x54, 0xbb.toByte(), 0x16
)
private val INV_S_BOX = ByteArray(256).also { inverse ->
for (i in 0 until 256) inverse[S_BOX[i].toInt() and 0xff] = i.toByte()
}
private val RCON = byteArrayOf(
0x00, 0x01, 0x02, 0x04, 0x08, 0x10,
0x20, 0x40, 0x80.toByte(), 0x1b, 0x36, 0x6c, 0xd8.toByte(),
0xab.toByte(), 0x4d, 0x9a.toByte()
)
private fun Byte.u(): Int = toInt() and 0xff
private fun sBox(value: Byte): Byte = S_BOX[value.u()]
private fun invSBox(value: Byte): Byte = INV_S_BOX[value.u()]
private fun xtime(value: Byte): Byte {
val x = value.u()
return (((x shl 1) xor (((x ushr 7) and 1) * 0x1b)) and 0xff).toByte()
}
private fun multiply(x: Byte, y: Int): Byte {
var a = x.u()
var b = y
var result = 0
while (b != 0) {
if ((b and 1) != 0) result = result xor a
a = if ((a and 0x80) != 0) ((a shl 1) xor 0x1b) and 0xff else (a shl 1) and 0xff
b = b ushr 1
}
return result.toByte()
}
}
private val rounds: Int
private val roundKey: ByteArray
private val iv = ByteArray(BLOCK_SIZE)
private val ctrBuffer = ByteArray(BLOCK_SIZE)
private var ctrPosition = BLOCK_SIZE
init {
iv.copyInto(this.iv)
val nk = key.size / 4
rounds = nk + 6
roundKey = ByteArray(BLOCK_SIZE * (rounds + 1))
expandKey(key, nk, rounds, roundKey)
}
fun setIv(newIv: ByteArray) {
newIv.copyInto(iv)
ctrPosition = BLOCK_SIZE
}
fun encryptBlock(input: ByteArray, output: ByteArray = ByteArray(BLOCK_SIZE)) {
input.copyInto(output, 0, 0, BLOCK_SIZE)
cipher(output)
}
fun decryptBlock(input: ByteArray, output: ByteArray = ByteArray(BLOCK_SIZE)) {
input.copyInto(output, 0, 0, BLOCK_SIZE)
invCipher(output)
}
private fun cipher(state: ByteArray) {
addRoundKey(state, 0)
for (round in 1 until rounds) {
subBytes(state)
shiftRows(state)
mixColumns(state)
addRoundKey(state, round)
}
subBytes(state)
shiftRows(state)
addRoundKey(state, rounds)
}
private fun invCipher(state: ByteArray) {
addRoundKey(state, rounds)
for (round in rounds - 1 downTo 1) {
invShiftRows(state)
invSubBytes(state)
addRoundKey(state, round)
invMixColumns(state)
}
invShiftRows(state)
invSubBytes(state)
addRoundKey(state, 0)
}
private fun addRoundKey(state: ByteArray, round: Int) {
val offset = round * BLOCK_SIZE
for (i in 0 until BLOCK_SIZE) state[i] = (state[i].u() xor roundKey[offset + i].u()).toByte()
}
private fun subBytes(state: ByteArray) = run { for (i in 0 until BLOCK_SIZE) state[i] = sBox(state[i]) }
private fun invSubBytes(state: ByteArray) = run { for (i in 0 until BLOCK_SIZE) state[i] = invSBox(state[i]) }
private fun shiftRows(state: ByteArray) {
var tmp = state[1]
state[1] = state[5]
state[5] = state[9]
state[9] = state[13]
state[13] = tmp
tmp = state[2]
state[2] = state[10]
state[10] = tmp
tmp = state[6]
state[6] = state[14]
state[14] = tmp
tmp = state[3]
state[3] = state[15]
state[15] = state[11]
state[11] = state[7]
state[7] = tmp
}
private fun invShiftRows(state: ByteArray) {
var tmp = state[13]
state[13] = state[9]
state[9] = state[5]
state[5] = state[1]
state[1] = tmp
tmp = state[2]
state[2] = state[10]
state[10] = tmp
tmp = state[6]
state[6] = state[14]
state[14] = tmp
tmp = state[3]
state[3] = state[7]
state[7] = state[11]
state[11] = state[15]
state[15] = tmp
}
private fun mixColumns(state: ByteArray) {
for (column in 0 until 4) {
val i = column * 4
val a0 = state[i]
val a1 = state[i + 1]
val a2 = state[i + 2]
val a3 = state[i + 3]
val t = a0.u() xor a1.u() xor a2.u() xor a3.u()
state[i] = (a0.u() xor (xtime((a0.u() xor a1.u()).toByte()).u()) xor t).toByte()
state[i + 1] = (a1.u() xor (xtime((a1.u() xor a2.u()).toByte()).u()) xor t).toByte()
state[i + 2] = (a2.u() xor (xtime((a2.u() xor a3.u()).toByte()).u()) xor t).toByte()
state[i + 3] = (a3.u() xor (xtime((a3.u() xor a0.u()).toByte()).u()) xor t).toByte()
}
}
private fun invMixColumns(state: ByteArray) {
for (column in 0 until 4) {
val i = column * 4
val a = state[i]
val b = state[i + 1]
val c = state[i + 2]
val d = state[i + 3]
state[i] = (multiply(a, 0x0e).u() xor multiply(b, 0x0b).u()
xor multiply(c, 0x0d).u() xor multiply(d, 0x09).u()).toByte()
state[i + 1] = (multiply(a, 0x09).u() xor multiply(b, 0x0e).u()
xor multiply(c, 0x0b).u() xor multiply(d, 0x0d).u()).toByte()
state[i + 2] = (multiply(a, 0x0d).u() xor multiply(b, 0x09).u()
xor multiply(c, 0x0e).u() xor multiply(d, 0x0b).u()).toByte()
state[i + 3] = (multiply(a, 0x0b).u() xor multiply(b, 0x0d).u()
xor multiply(c, 0x09).u() xor multiply(d, 0x0e).u()).toByte()
}
}
fun ecbEncrypt(data: ByteArray): ByteArray {
val output = data.copyOf()
for (offset in output.indices step BLOCK_SIZE) cipherAt(output, offset)
return output
}
fun ecbDecrypt(data: ByteArray): ByteArray {
val output = data.copyOf()
for (offset in output.indices step BLOCK_SIZE) invCipherAt(output, offset)
return output
}
private fun cipherAt(data: ByteArray, offset: Int) {
val block = ByteArray(BLOCK_SIZE)
data.copyInto(block, 0, offset, offset + BLOCK_SIZE)
cipher(block)
block.copyInto(data, offset)
}
private fun invCipherAt(data: ByteArray, offset: Int) {
val block = ByteArray(BLOCK_SIZE)
data.copyInto(block, 0, offset, offset + BLOCK_SIZE)
invCipher(block)
block.copyInto(data, offset)
}
fun cbcEncrypt(data: ByteArray): ByteArray {
val output = data.copyOf()
val block = ByteArray(BLOCK_SIZE)
for (offset in output.indices step BLOCK_SIZE) {
for (i in 0 until BLOCK_SIZE) block[i] = (output[offset + i].u() xor iv[i].u()).toByte()
cipher(block)
block.copyInto(output, offset)
block.copyInto(iv)
}
return output
}
fun cbcDecrypt(data: ByteArray): ByteArray {
val output = data.copyOf()
val block = ByteArray(BLOCK_SIZE)
val nextIv = ByteArray(BLOCK_SIZE)
for (offset in output.indices step BLOCK_SIZE) {
output.copyInto(nextIv, 0, offset, offset + BLOCK_SIZE)
output.copyInto(block, 0, offset, offset + BLOCK_SIZE)
invCipher(block)
for (i in 0 until BLOCK_SIZE) block[i] = (block[i].u() xor iv[i].u()).toByte()
block.copyInto(output, offset)
nextIv.copyInto(iv)
}
return output
}
fun ctrXcrypt(data: ByteArray): ByteArray {
val output = data.copyOf()
for (i in output.indices) {
if (ctrPosition == BLOCK_SIZE) {
iv.copyInto(ctrBuffer)
cipher(ctrBuffer)
incrementCounter()
ctrPosition = 0
}
output[i] = (output[i].u() xor ctrBuffer[ctrPosition].u()).toByte()
ctrPosition++
}
return output
}
private fun incrementCounter() {
for (i in BLOCK_SIZE - 1 downTo 0) {
if (iv[i].u() == 0xff) iv[i] = 0 else {
iv[i] = (iv[i].u() + 1).toByte()
break
}
}
}
fun cfb8Encrypt(data: ByteArray): ByteArray {
val output = data.copyOf()
val stream = ByteArray(BLOCK_SIZE)
for (i in output.indices) {
iv.copyInto(stream)
cipher(stream)
val ciphertext = (output[i].u() xor stream[0].u()).toByte()
shiftFeedback(ciphertext)
output[i] = ciphertext
}
return output
}
fun cfb8Decrypt(data: ByteArray): ByteArray {
val output = data.copyOf()
val stream = ByteArray(BLOCK_SIZE)
for (i in output.indices) {
val ciphertext = output[i]
iv.copyInto(stream)
cipher(stream)
output[i] = (ciphertext.u() xor stream[0].u()).toByte()
shiftFeedback(ciphertext)
}
return output
}
fun shiftFeedback(value: Byte) {
for (i in 0 until BLOCK_SIZE - 1) iv[i] = iv[i + 1]
iv[BLOCK_SIZE - 1] = value
}
private fun expandKey(key: ByteArray, nk: Int, rounds: Int, output: ByteArray) {
key.copyInto(output)
var generated = key.size
var rconIndex = 1
val total = BLOCK_SIZE * (rounds + 1)
val temp = ByteArray(4)
while (generated < total) {
for (i in 0 until 4) temp[i] = output[generated - 4 + i]
if (generated % key.size == 0) {
val t = temp[0]
temp[0] = temp[1]
temp[1] = temp[2]
temp[2] = temp[3]
temp[3] = t
for (i in 0 until 4) temp[i] = sBox(temp[i])
temp[0] = (temp[0].u() xor RCON[rconIndex].u()).toByte()
rconIndex++
} else if (nk == 8 && generated % key.size == 16) for (i in 0 until 4) temp[i] = sBox(temp[i])
for (i in 0 until 4) {
output[generated] = (output[generated - key.size].u() xor temp[i].u()).toByte()
generated++
}
}
}
fun getIv(output: ByteArray): ByteArray = iv.copyInto(output)
}
@@ -0,0 +1,299 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.crypto
import kotlin.random.Random
internal actual fun rsaEncrypt(publicKeyBytes: ByteArray, data: ByteArray): ByteArray {
val (modulus, exponent) = parseRsaPublicKeyDer(publicKeyBytes)
val paddedData = pkcs1Pad(data, keySizeBytes = 128)
return rsa1024(paddedData, exponent, modulus)
}
private fun parseRsaPublicKeyDer(der: ByteArray): Pair<ByteArray, ByteArray> {
var offset = 0
fun readTag(): Int = der[offset++].toInt() and 0xFF
fun readLength(): Int {
var len = der[offset++].toInt() and 0xFF
if (len and 0x80 != 0) {
val numBytes = len and 0x7F; len = 0
repeat(numBytes) { len = (len shl 8) or (der[offset++].toInt() and 0xFF) }
}
return len
}
if (readTag() != 0x30) error("Invalid DER Format")
readLength()
val tag = readTag()
if (tag == 0x30) {
val algLen = readLength()
offset += algLen
} else offset--
if (readTag() == 0x03) {
readLength(); offset++
if (readTag() != 0x30) error("Invalid RSA Public Key")
readLength()
}
if (readTag() != 0x02) error("Modulus Required")
val modulusLen = readLength()
val modulus = der.copyOfRange(offset, offset + modulusLen)
offset += modulusLen
if (readTag() != 0x02) error("Exponent Required")
val exponentLen = readLength()
val exponent = der.copyOfRange(offset, offset + exponentLen)
return Pair(modulus, exponent)
}
private fun pkcs1Pad(data: ByteArray, keySizeBytes: Int): ByteArray {
val maxDataLen = keySizeBytes - 11
require(data.size <= maxDataLen)
val padded = ByteArray(keySizeBytes)
padded[0] = 0x00
padded[1] = 0x02
val psLen = keySizeBytes - data.size - 3
var i = 2
while (i < 2 + psLen) {
val randomByte = Random.nextInt(1, 256).toByte(); padded[i] = randomByte; i++
}
padded[i] = 0x00; i++
for (k in data.indices) padded[i + k] = data[k]
return padded
}
private fun rsa1024(input: ByteArray, exponent: ByteArray, modulus: ByteArray): ByteArray {
val dataLongs = bytesToLongArray16(input)
val expoLongs = bytesToLongArray16(exponent)
val modLongs = bytesToLongArray16(modulus)
val resLongs = LongArray(18)
rsa1024(resLongs, dataLongs, expoLongs, modLongs)
return longArray16ToBytes(resLongs)
}
private fun rsa1024(res: LongArray, data: LongArray, expo: LongArray, key: LongArray): Boolean {
val modData = LongArray(18)
val result = LongArray(18)
var tempExpo: Long
modBigNumber(modData, data, key, 16)
result[0] = 1L
val expoLen = bitLength(expo, 16) / 64
for (i in 0..expoLen) {
tempExpo = expo[i]
repeat(64) {
if ((tempExpo and 1L) != 0L) modMultiply1024(result, result, modData, key)
modMultiply1024(modData, modData, modData, key)
tempExpo = tempExpo ushr 1
}
}
for (i in 0 until 16) res[i] = result[i]
return true
}
private fun addBigNumber(res: LongArray, op1: LongArray, op2: LongArray, n: Int): Boolean {
var carry = 0L
val mask32 = 0xFFFFFFFFL
var i = 0
while (i < n) {
val j = (op1[i] and mask32) + (op2[i] and mask32) + carry
val k = (op1[i] ushr 32) + (op2[i] ushr 32) + (j ushr 32)
carry = k ushr 32
res[i] = ((k and mask32) shl 32) or (j and mask32)
i++
}
if (i < res.size) res[i] = carry
return false
}
private fun multBigNumber(res: LongArray, op1: LongArray, op2: Int, n: Int): Boolean {
var carry1: Long
var carry2 = 0L
val op2UL = op2.toLong() and 0xFFFFFFFFL
val mask32 = 0xFFFFFFFFL
var i = 0
while (i < n) {
var j = (op1[i] and mask32) * op2UL
var k = (op1[i] ushr 32) * op2UL
carry1 = k ushr 32
k = (k and mask32) + (j ushr 32)
j = (j and mask32) + carry2
k += (j ushr 32)
carry2 = carry1 + (k ushr 32)
res[i] = ((k and mask32) shl 32) or (j and mask32)
i++
}
if (i < res.size) res[i] = carry2
return false
}
private fun modMultiply1024(res: LongArray, op1: LongArray, op2: LongArray, mod: LongArray): Boolean {
val mult1 = LongArray(33)
val mult2 = LongArray(33)
val result = LongArray(33)
val xmod = LongArray(33)
for (i in 0 until 16) xmod[i] = mod[i]
for (i in 0 until 16) {
mult1.fill(0L)
mult2.fill(0L)
val op2Low = (op2[i] and 0xFFFFFFFFL).toInt()
val op2High = ((op2[i] ushr 32) and 0xFFFFFFFFL).toInt()
multBigNumber(mult1, op1, op2Low, 16)
multBigNumber(mult2, op1, op2High, 16)
slnBigNumber(mult2, mult2, 33, 32)
addBigNumber(mult2, mult2, mult1, 32)
slnBigNumber(mult2, mult2, 33, 64 * i)
addBigNumber(result, result, mult2, 32)
}
modBigNumber(result, result, xmod, 33)
for (i in 0 until 16) res[i] = result[i]
return false
}
private fun modBigNumber(res: LongArray, op1: LongArray, op2: LongArray, n: Int): Boolean {
val lenOp1 = bitLength(op1, n)
val lenOp2 = bitLength(op2, n)
val lenDif = lenOp1 - lenOp2
for (i in 0 until n) res[i] = op1[i]
if (lenDif < 0) return true
if (lenDif == 0) {
while (compare(res, op2, n) >= 0) subBigNumber(res, res, op2, n)
return true
}
val op2Work = op2.copyOf()
slnBigNumber(op2Work, op2Work, n, lenDif)
repeat(lenDif) {
srnBigNumber(op2Work, op2Work, n, 1)
while (compare(res, op2Work, n) >= 0) subBigNumber(res, res, op2Work, n)
}
return true
}
private fun compare(op1: LongArray, op2: LongArray, n: Int): Int {
for (i in n - 1 downTo 0) {
val a = op1[i]
val b = op2[i]
if (a != b) {
val aUnsigned = a xor Long.MIN_VALUE
val bUnsigned = b xor Long.MIN_VALUE
return if (aUnsigned > bUnsigned) 1 else -1
}
}
return 0
}
private fun subBigNumber(res: LongArray, op1: LongArray, op2: LongArray, n: Int): Boolean {
var carry = false
val op1Copy = op1.copyOf()
for (i in 0 until n) {
var v1 = op1Copy[i]
if (carry) {
if (v1 != 0L) carry = false
v1 -= 1L
op1Copy[i] = v1
}
if ((v1 xor Long.MIN_VALUE) < (op2[i] xor Long.MIN_VALUE)) carry = true
res[i] = v1 - op2[i]
}
return carry
}
private fun slnBigNumber(res: LongArray, op: LongArray, len: Int, n: Int): Boolean {
val xShift = n / 64
val yShift = n % 64
var i = len
while (i - xShift > 0) {
res[i - 1] = op[i - 1 - xShift]
i--
}
while (i > 0) {
res[i - 1] = 0L
i--
}
if (yShift == 0) return true
var carry = 0L
for (idx in 0 until len) {
val j = res[idx]
val nextCarry = j ushr (64 - yShift)
res[idx] = (j shl yShift) or carry
carry = nextCarry
}
return true
}
private fun srnBigNumber(res: LongArray, op: LongArray, len: Int, n: Int): Boolean {
val xShift = n / 64
val yShift = n % 64
var i = 0
while (i + xShift < len) {
res[i] = op[i + xShift]; i++
}
while (i < len) {
res[i] = 0L; i++
}
if (yShift == 0) return true
var carry = 0L
for (idx in len downTo 1) {
val j = res[idx - 1]
val nextCarry = j shl (64 - yShift)
res[idx - 1] = (j ushr yShift) or carry
carry = nextCarry
}
return true
}
private fun bitLength(op: LongArray, n: Int): Int {
var len = 0
val unit = 1L
for (idx in n downTo 1) {
if (op[idx - 1] == 0L) continue
for (i in 64 downTo 1) {
if ((op[idx - 1] and (unit shl (i - 1))) != 0L) {
len = (64 * (idx - 1)) + i
break
}
}
if (len != 0) break
}
return len
}
private fun bytesToLongArray16(bytes: ByteArray): LongArray {
val cleanBytes = if (bytes.size > 128 && bytes[0] == 0.toByte()) bytes.copyOfRange(1, bytes.size) else bytes
val padded = ByteArray(128)
val startIdx = 128 - cleanBytes.size
for (k in cleanBytes.indices) padded[startIdx + k] = cleanBytes[k]
for (k in 0 until 64) {
val tmp = padded[k]
padded[k] = padded[127 - k]
padded[127 - k] = tmp
}
val result = LongArray(16)
for (i in 0 until 16) {
var value = 0L
for (j in 0 until 8) {
val byteVal = padded[i * 8 + j].toLong() and 0xFFL
value = value or (byteVal shl (j * 8))
}
result[i] = value
}
return result
}
private fun longArray16ToBytes(array: LongArray): ByteArray {
val bytes = ByteArray(128)
for (i in 0 until 16) {
val value = array[i]
for (j in 0 until 8) bytes[i * 8 + j] = ((value ushr (j * 8)) and 0xFFL).toByte()
}
for (k in 0 until 64) {
val tmp = bytes[k]
bytes[k] = bytes[127 - k]
bytes[127 - k] = tmp
}
return bytes
}
@@ -0,0 +1,96 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/8
*/
package cn.rtast.libmc.protocol.crypto
internal actual fun sha1Digest(data: ByteArray): ByteArray = Sha1.digest(data)
/**
* PERFORMANCE IMPROVEMENT REQUIRED.
*/
private object Sha1 {
private fun rotateLeft(value: Int, bits: Int): Int {
return (value shl bits) or (value ushr (32 - bits))
}
private fun align(address: Int, alignment: Int): Int {
val tmp = alignment - 1
return (address + tmp) and tmp.inv()
}
fun digest(input: ByteArray): ByteArray {
val bitLength = input.size.toLong() * 8L
val bufferSize = align(input.size + 9, 64)
val buffer = ByteArray(bufferSize)
input.copyInto(buffer)
buffer[input.size] = 0x80.toByte()
for (i in 0 until 8) buffer[bufferSize - 8 + i] = ((bitLength ushr ((7 - i) * 8)) and 0xFFL).toByte()
var h0 = 0x67452301
var h1 = -0x10325477
var h2 = -0x67452302
var h3 = 0x10325476
var h4 = -0x3C2D1E10
val w = IntArray(80)
for (offset in buffer.indices step 64) {
for (i in 0 until 16) {
val idx = offset + (i * 4)
w[i] = ((buffer[idx].toInt() and 0xFF) shl 24) or
((buffer[idx + 1].toInt() and 0xFF) shl 16) or
((buffer[idx + 2].toInt() and 0xFF) shl 8) or
(buffer[idx + 3].toInt() and 0xFF)
}
for (i in 16 until 80) w[i] = rotateLeft(w[i - 3] xor w[i - 8] xor w[i - 14] xor w[i - 16], 1)
var a = h0
var b = h1
var c = h2
var d = h3
var e = h4
for (i in 0 until 80) {
val f: Int
val k: Int
when (i) {
in 0..19 -> {
f = (b and c) or (b.inv() and d)
k = 0x5A827999
}
in 20..39 -> {
f = b xor c xor d
k = 0x6ED9EBA1.toInt()
}
in 40..59 -> {
f = (b and c) or (b and d) or (c and d)
k = -0x70E44324
}
else -> {
f = b xor c xor d
k = -0x359D3E2A
}
}
val temp = rotateLeft(a, 5) + f + e + k + w[i]
e = d; d = c
c = rotateLeft(b, 30)
b = a; a = temp
}
h0 += a; h1 += b
h2 += c; h3 += d
h4 += e
}
val result = ByteArray(20)
val state = intArrayOf(h0, h1, h2, h3, h4)
for (i in 0 until 5) {
val v = state[i]
result[i * 4] = (v ushr 24).toByte()
result[i * 4 + 1] = (v ushr 16).toByte()
result[i * 4 + 2] = (v ushr 8).toByte()
result[i * 4 + 3] = v.toByte()
}
return result
}
}