mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-26 20:20:14 +08:00
[core] enhance(ECDH): reconstruct ECDH (#2161)
* enhance(ECDH): reconstruct ECDH common: 移动特定于 QQ 平台的实现到 OicqECDH,重新设计 ECDH,使 ECDH 类只针对算法本身,而不过多包括 QQ 协议的使用细节 jvm: 尝试优先使用平台加密实现,可能改善性能 jvm & android: 使用 curveName `secp256r1` 代替 `prime256v1`,前者在 Java 中更常用,可以被更多的 JCE 实现所识别(虽然都是指同一条曲线) android: 使用系统自带的实现以减少依赖,并尝试兼容 Android P+ 版本 native: 中间储存时保留OpenSSL内部结构而不反复 new & free,提高性能 (为了实现智能指针,需要用到 `@ExperimentalStdlibApi` 的 `createCleaner`,但这种风险应该可以接受) native: 直接使用 point/bignum 到 bytes 的转换,避免了 hex string 作为中间层,提高效率 * test(ECDH): fix AndroidTest * style(Ecdh): obey official Kotlin coding conventions > When using an acronym as part of a declaration name, capitalize it if it consists of two letters (IOStream); capitalize only the first letter if it is longer (XmlFormatter, HttpInputStream). > [View origin](https://kotlinlang.org/docs/coding-conventions.html) Co-authored-by: ArcticLampyrid <arcticlampyrid@outlook.com>
This commit is contained in:
parent
d32a8a566c
commit
397d824d33
@ -58,7 +58,6 @@ kotlin {
|
||||
|
||||
findByName("jvmBaseMain")?.apply {
|
||||
dependencies {
|
||||
implementation(bouncycastle)
|
||||
implementation(`log4j-api`)
|
||||
implementation(`netty-all`)
|
||||
implementation(`ktor-client-okhttp`)
|
||||
@ -84,13 +83,13 @@ kotlin {
|
||||
implementation(kotlin("test-junit5", Versions.kotlinCompiler))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
implementation(kotlin("test-common"))
|
||||
//implementation("org.bouncycastle:bcprov-jdk15on:1.64")
|
||||
implementation(bouncycastle)
|
||||
}
|
||||
}
|
||||
|
||||
findByName("jvmMain")?.apply {
|
||||
dependencies {
|
||||
//implementation("org.bouncycastle:bcprov-jdk15on:1.64")
|
||||
implementation(bouncycastle)
|
||||
// api(kotlinx("coroutines-debug", Versions.coroutines))
|
||||
}
|
||||
}
|
||||
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import net.mamoe.mirai.utils.decodeBase64
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import net.mamoe.mirai.utils.recoverCatchingSuppressed
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.Provider
|
||||
import java.security.Signature
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import javax.crypto.KeyAgreement
|
||||
|
||||
/**
|
||||
* 绕过在Android P之后的版本无法使用EC的限制
|
||||
* https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/sun/security/jca/Providers.java;l=371;bpv=1;bpt=1
|
||||
* https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html
|
||||
* */
|
||||
@Suppress("DEPRECATION") // since JDK 9
|
||||
private class AndroidProvider : Provider("sbAndroid", 1.0, "") {
|
||||
override fun getService(type: String?, algorithm: String?): Service? {
|
||||
if (type == "KeyFactory" && algorithm == "EC") {
|
||||
return object : Service(this, type, algorithm, "", emptyList(), emptyMap()) {
|
||||
override fun newInstance(constructorParameter: Any?): Any {
|
||||
return org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi.EC()
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.getService(type, algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
private val ANDROID_PROVIDER by lazy { AndroidProvider() }
|
||||
private val ecKf by lazy {
|
||||
runCatching { KeyFactory.getInstance("EC", "BC") }
|
||||
.recoverCatchingSuppressed { KeyFactory.getInstance("EC", ANDROID_PROVIDER) }
|
||||
.getOrThrow()
|
||||
}
|
||||
|
||||
internal actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
||||
actual companion object {
|
||||
private const val curveName = "prime256v1" // p-256
|
||||
|
||||
actual val isECDHAvailable: Boolean
|
||||
|
||||
init {
|
||||
isECDHAvailable = kotlin.runCatching {
|
||||
ecKf // init
|
||||
|
||||
fun testECDH() {
|
||||
ECDHKeyPairImpl(
|
||||
KeyPairGenerator.getInstance("ECDH")
|
||||
.also { it.initialize(ECGenParameterSpec(curveName)) }
|
||||
.genKeyPair()).let {
|
||||
calculateShareKey(it.privateKey, it.publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
if (kotlin.runCatching { testECDH() }.isSuccess) {
|
||||
return@runCatching
|
||||
}
|
||||
|
||||
testECDH()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}.isSuccess
|
||||
}
|
||||
|
||||
actual fun generateKeyPair(initialPublicKey: ECDHPublicKey): ECDHKeyPair {
|
||||
if (!isECDHAvailable) {
|
||||
return ECDHKeyPair.DefaultStub
|
||||
}
|
||||
return ECDHKeyPairImpl(
|
||||
KeyPairGenerator.getInstance("ECDH")
|
||||
.also { it.initialize(ECGenParameterSpec(curveName)) }
|
||||
.genKeyPair(), initialPublicKey)
|
||||
}
|
||||
|
||||
actual fun calculateShareKey(
|
||||
privateKey: ECDHPrivateKey,
|
||||
publicKey: ECDHPublicKey
|
||||
): ByteArray {
|
||||
val instance = KeyAgreement.getInstance("ECDH", "BC")
|
||||
instance.init(privateKey)
|
||||
instance.doPhase(publicKey, true)
|
||||
return instance.generateSecret().copyOf(16).md5()
|
||||
}
|
||||
|
||||
actual fun verifyPublicKey(version: Int, publicKey: String, publicKeySign: String): Boolean {
|
||||
val arrayForVerify = "305$version$publicKey".toByteArray()
|
||||
val signInstance = Signature.getInstance("SHA256WithRSA")
|
||||
signInstance.initVerify(publicKeyForVerify)
|
||||
signInstance.update(arrayForVerify)
|
||||
return signInstance.verify(publicKeySign.decodeBase64())
|
||||
}
|
||||
|
||||
actual fun constructPublicKey(key: ByteArray): ECDHPublicKey {
|
||||
return ecKf.generatePublic(X509EncodedKeySpec(key))
|
||||
}
|
||||
}
|
||||
|
||||
actual fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray {
|
||||
return calculateShareKey(keyPair.privateKey, peerPublicKey)
|
||||
}
|
||||
|
||||
actual override fun toString(): String {
|
||||
return "ECDH(keyPair=$keyPair)"
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import java.security.Security
|
||||
|
||||
internal actual fun Ecdh.Companion.create(): Ecdh<*, *> =
|
||||
if (kotlin.runCatching {
|
||||
// When running tests on JVM desktop, `ClassNotFoundException` will be got
|
||||
android.os.Build.VERSION.SDK_INT >= 23
|
||||
}.getOrDefault(false)) {
|
||||
// For newer Android, BC is deprecated, but AndroidKeyStore (default) handles ECDH well
|
||||
// Do not specify a provider as Google recommends
|
||||
JceEcdh()
|
||||
} else {
|
||||
// For older Android, AndroidKeyStore (default) is buggy and cannot handle EC key generation without tricks
|
||||
// See https://developer.android.com/training/articles/keystore#SupportedKeyPairGenerators for details
|
||||
|
||||
// Let's use BC instead, BC is bundled into older Android
|
||||
JceEcdhWithProvider(Security.getProvider("BC"))
|
||||
}
|
@ -19,9 +19,8 @@ import net.mamoe.mirai.internal.network.component.ComponentKey
|
||||
import net.mamoe.mirai.internal.network.getRandomByteArray
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.get_mpasswd
|
||||
import net.mamoe.mirai.internal.utils.accountSecretsFile
|
||||
import net.mamoe.mirai.internal.utils.crypto.ECDHInitialPublicKey
|
||||
import net.mamoe.mirai.internal.utils.crypto.QQEcdhInitialPublicKey
|
||||
import net.mamoe.mirai.internal.utils.crypto.TEA
|
||||
import net.mamoe.mirai.internal.utils.crypto.defaultInitialPublicKey
|
||||
import net.mamoe.mirai.internal.utils.io.ProtoBuf
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
|
||||
@ -73,7 +72,7 @@ internal interface AccountSecrets {
|
||||
|
||||
var tgtgtKey: ByteArray
|
||||
val randomKey: ByteArray
|
||||
var ecdhInitialPublicKey: ECDHInitialPublicKey
|
||||
var ecdhInitialPublicKey: QQEcdhInitialPublicKey
|
||||
}
|
||||
|
||||
|
||||
@ -87,7 +86,7 @@ internal data class AccountSecretsImpl(
|
||||
override var ksid: ByteArray,
|
||||
override var tgtgtKey: ByteArray,
|
||||
override val randomKey: ByteArray,
|
||||
override var ecdhInitialPublicKey: ECDHInitialPublicKey,
|
||||
override var ecdhInitialPublicKey: QQEcdhInitialPublicKey,
|
||||
) : AccountSecrets, ProtoBuf {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
@ -141,7 +140,7 @@ internal fun AccountSecretsImpl(
|
||||
ksid = EMPTY_BYTE_ARRAY,
|
||||
tgtgtKey = (account.passwordMd5 + ByteArray(4) + account.id.toInt().toByteArray()).md5(),
|
||||
randomKey = getRandomByteArray(16),
|
||||
ecdhInitialPublicKey = defaultInitialPublicKey
|
||||
ecdhInitialPublicKey = QQEcdhInitialPublicKey.default
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -17,25 +17,24 @@ import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.component.ComponentKey
|
||||
import net.mamoe.mirai.internal.utils.crypto.ECDH
|
||||
import net.mamoe.mirai.internal.utils.crypto.ECDHInitialPublicKey
|
||||
import net.mamoe.mirai.internal.utils.crypto.ECDHWithPublicKey
|
||||
import net.mamoe.mirai.internal.utils.crypto.defaultInitialPublicKey
|
||||
import net.mamoe.mirai.internal.utils.crypto.QQEcdh
|
||||
import net.mamoe.mirai.internal.utils.crypto.QQEcdhInitialPublicKey
|
||||
import net.mamoe.mirai.internal.utils.crypto.verify
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
||||
/**
|
||||
* Updater for updating [ECDHInitialPublicKey].
|
||||
* Updater for updating [QQEcdhInitialPublicKey].
|
||||
*/
|
||||
internal interface EcdhInitialPublicKeyUpdater {
|
||||
/**
|
||||
* Refresh the [ECDHInitialPublicKey]
|
||||
* Refresh the [QQEcdhInitialPublicKey]
|
||||
*/
|
||||
suspend fun refreshInitialPublicKeyAndApplyECDH()
|
||||
suspend fun refreshInitialPublicKeyAndApplyEcdh()
|
||||
|
||||
fun getECDHWithPublicKey(): ECDHWithPublicKey
|
||||
fun getQQEcdh(): QQEcdh
|
||||
|
||||
companion object : ComponentKey<EcdhInitialPublicKeyUpdater>
|
||||
}
|
||||
@ -67,19 +66,15 @@ internal class EcdhInitialPublicKeyUpdaterImpl(
|
||||
val keyVer: Int
|
||||
)
|
||||
|
||||
companion object {
|
||||
val json = Json {}
|
||||
var qqEcdh: QQEcdh? = null
|
||||
override fun getQQEcdh(): QQEcdh {
|
||||
if (qqEcdh == null) {
|
||||
error("Calling getQQEcdh without calling refreshInitialPublicKeyAndApplyEcdh")
|
||||
}
|
||||
return qqEcdh!!
|
||||
}
|
||||
|
||||
var ecdhWithPublicKey: ECDHWithPublicKey? = null
|
||||
override fun getECDHWithPublicKey(): ECDHWithPublicKey {
|
||||
if (ecdhWithPublicKey == null) {
|
||||
error("Calling getECDHWithPublicKey without calling refreshInitialPublicKeyAndApplyECDH")
|
||||
}
|
||||
return ecdhWithPublicKey!!
|
||||
}
|
||||
|
||||
override suspend fun refreshInitialPublicKeyAndApplyECDH() {
|
||||
override suspend fun refreshInitialPublicKeyAndApplyEcdh() {
|
||||
|
||||
val initialPublicKey = kotlin.runCatching {
|
||||
val currentPublicKey = bot.client.ecdhInitialPublicKey
|
||||
@ -94,24 +89,20 @@ internal class EcdhInitialPublicKeyUpdaterImpl(
|
||||
.get("https://keyrotate.qq.com/rotate_key?cipher_suite_ver=305&uin=${bot.client.uin}")
|
||||
.bodyAsText()
|
||||
}
|
||||
val resp = json.decodeFromString(ServerRespPOJO.serializer(), respStr)
|
||||
val resp = Json.decodeFromString(ServerRespPOJO.serializer(), respStr)
|
||||
resp.pubKeyMeta.let { meta ->
|
||||
val isValid = ECDH.verifyPublicKey(
|
||||
version = meta.keyVer,
|
||||
publicKey = meta.pubKey,
|
||||
publicKeySign = meta.pubKeySign
|
||||
)
|
||||
check(isValid) { "Ecdh public key which from server is invalid" }
|
||||
val key = QQEcdhInitialPublicKey(meta.keyVer, meta.pubKey, currentTimeSeconds() + resp.querySpan)
|
||||
check(key.verify(meta.pubKeySign)) { "Ecdh public key which from server is invalid" }
|
||||
logger.info("Successfully fetched ecdh public key from server.")
|
||||
ECDHInitialPublicKey(meta.keyVer, meta.pubKey, currentTimeSeconds() + resp.querySpan)
|
||||
key
|
||||
}
|
||||
}
|
||||
}.getOrElse {
|
||||
logger.error("Failed to fetch ECDH public key from server, using default key instead", it)
|
||||
defaultInitialPublicKey
|
||||
QQEcdhInitialPublicKey.default
|
||||
}
|
||||
bot.client.ecdhInitialPublicKey = initialPublicKey
|
||||
ecdhWithPublicKey = ECDHWithPublicKey(initialPublicKey)
|
||||
qqEcdh = QQEcdh(initialPublicKey)
|
||||
}
|
||||
|
||||
|
||||
|
@ -17,8 +17,8 @@ import net.mamoe.mirai.internal.network.components.PacketCodec.Companion.PacketL
|
||||
import net.mamoe.mirai.internal.network.components.PacketCodecException.Kind.*
|
||||
import net.mamoe.mirai.internal.network.handler.selector.NetworkException
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.*
|
||||
import net.mamoe.mirai.internal.utils.crypto.Ecdh
|
||||
import net.mamoe.mirai.internal.utils.crypto.TEA
|
||||
import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey
|
||||
import net.mamoe.mirai.utils.*
|
||||
|
||||
|
||||
@ -254,20 +254,20 @@ internal class PacketCodecImpl : PacketCodec {
|
||||
val encryptionMethod = this.readUShort().toInt()
|
||||
|
||||
this.discardExact(1)
|
||||
val ecdhWithPublicKey =
|
||||
(client as QQAndroidClient).bot.components[EcdhInitialPublicKeyUpdater].getECDHWithPublicKey()
|
||||
val qqEcdh =
|
||||
(client as QQAndroidClient).bot.components[EcdhInitialPublicKeyUpdater].getQQEcdh()
|
||||
return when (encryptionMethod) {
|
||||
4 -> {
|
||||
val size = (this.remaining - 1).toInt()
|
||||
val data =
|
||||
TEA.decrypt(
|
||||
this.readBytes(),
|
||||
ecdhWithPublicKey.keyPair.maskedShareKey,
|
||||
qqEcdh.initialQQShareKey,
|
||||
length = size
|
||||
)
|
||||
|
||||
val peerShareKey =
|
||||
ecdhWithPublicKey.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey())
|
||||
qqEcdh.calculateQQShareKey(Ecdh.Instance.importPublicKey(readUShortLVByteArray()))
|
||||
TEA.decrypt(data, peerShareKey)
|
||||
}
|
||||
3 -> {
|
||||
@ -285,7 +285,7 @@ internal class PacketCodecImpl : PacketCodec {
|
||||
val byteArrayBuffer = this.readBytes(size)
|
||||
|
||||
runCatching {
|
||||
TEA.decrypt(byteArrayBuffer, ecdhWithPublicKey.keyPair.maskedShareKey, length = size)
|
||||
TEA.decrypt(byteArrayBuffer, qqEcdh.initialQQShareKey, length = size)
|
||||
}.getOrElse {
|
||||
TEA.decrypt(byteArrayBuffer, client.randomKey, length = size)
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ internal class SsoProcessorImpl(
|
||||
components[BdhSessionSyncer].loadServerListFromCache()
|
||||
try {
|
||||
if (client.wLoginSigInfoInitialized) {
|
||||
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyECDH()
|
||||
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
|
||||
kotlin.runCatching {
|
||||
FastLoginImpl(handler).doLogin()
|
||||
}.onFailure { e ->
|
||||
@ -144,7 +144,7 @@ internal class SsoProcessorImpl(
|
||||
}
|
||||
} else {
|
||||
client = createClient(ssoContext.bot)
|
||||
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyECDH()
|
||||
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
|
||||
SlowLoginImpl(handler).doLogin()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -11,8 +11,7 @@ package net.mamoe.mirai.internal.network.protocol.packet
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.utils.crypto.ECDHKeyPair
|
||||
import net.mamoe.mirai.internal.utils.crypto.ECDHWithPublicKey
|
||||
import net.mamoe.mirai.internal.utils.crypto.QQEcdh
|
||||
import net.mamoe.mirai.internal.utils.io.encryptAndWrite
|
||||
import net.mamoe.mirai.internal.utils.io.writeShortLVByteArray
|
||||
|
||||
@ -62,26 +61,26 @@ internal class EncryptMethodSessionKeyLoginState3(override val sessionKey: ByteA
|
||||
override val currentLoginState: Int get() = 3
|
||||
}
|
||||
|
||||
internal class EncryptMethodECDH135(override val ecdh: ECDHWithPublicKey) :
|
||||
EncryptMethodECDH {
|
||||
internal class EncryptMethodEcdh135(override val ecdh: QQEcdh) :
|
||||
EncryptMethodEcdh {
|
||||
override val id: Int get() = 135
|
||||
}
|
||||
|
||||
internal class EncryptMethodECDH7(override val ecdh: ECDHWithPublicKey) :
|
||||
EncryptMethodECDH {
|
||||
internal class EncryptMethodEcdh7(override val ecdh: QQEcdh) :
|
||||
EncryptMethodEcdh {
|
||||
override val id: Int get() = 7 // 135
|
||||
}
|
||||
|
||||
internal interface EncryptMethodECDH : EncryptMethod {
|
||||
internal interface EncryptMethodEcdh : EncryptMethod {
|
||||
companion object {
|
||||
operator fun invoke(ecdh: ECDHWithPublicKey): EncryptMethodECDH {
|
||||
return if (ecdh.keyPair === ECDHKeyPair.DefaultStub) {
|
||||
EncryptMethodECDH135(ecdh)
|
||||
} else EncryptMethodECDH7(ecdh)
|
||||
operator fun invoke(ecdh: QQEcdh): EncryptMethodEcdh {
|
||||
return if (ecdh.fallbackMode) {
|
||||
EncryptMethodEcdh135(ecdh)
|
||||
} else EncryptMethodEcdh7(ecdh)
|
||||
}
|
||||
}
|
||||
|
||||
val ecdh: ECDHWithPublicKey
|
||||
val ecdh: QQEcdh
|
||||
|
||||
override fun makeBody(client: QQAndroidClient, body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket {
|
||||
/* //new curve p-256
|
||||
@ -97,14 +96,8 @@ internal interface EncryptMethodECDH : EncryptMethod {
|
||||
writeFully(client.randomKey)
|
||||
writeShort(0x0131)
|
||||
writeShort(ecdh.version.toShort())// public key version
|
||||
if (ecdh.keyPair === ECDHKeyPair.DefaultStub) {
|
||||
writeShortLVByteArray(ECDHKeyPair.DefaultStub.defaultPublicKey)
|
||||
encryptAndWrite(ECDHKeyPair.DefaultStub.defaultShareKey, body)
|
||||
} else {
|
||||
// for p-256, drop(26). // but not really sure.
|
||||
writeShortLVByteArray(ecdh.keyPair.maskedPublicKey)
|
||||
|
||||
encryptAndWrite(ecdh.keyPair.maskedShareKey, body)
|
||||
}
|
||||
writeShortLVByteArray(ecdh.publicKey)
|
||||
encryptAndWrite(ecdh.initialQQShareKey, body)
|
||||
}
|
||||
}
|
@ -274,7 +274,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
|
||||
|
||||
internal fun BytePacketBuilder.writeOicqRequestPacket(
|
||||
client: QQAndroidClient,
|
||||
encryptMethod: EncryptMethod = EncryptMethodECDH(client.bot.components[EcdhInitialPublicKeyUpdater].getECDHWithPublicKey()),
|
||||
encryptMethod: EncryptMethod = EncryptMethodEcdh(client.bot.components[EcdhInitialPublicKeyUpdater].getQQEcdh()),
|
||||
commandId: Int,
|
||||
bodyBlock: BytePacketBuilder.() -> Unit
|
||||
) {
|
||||
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import net.mamoe.mirai.utils.hexToBytes
|
||||
|
||||
internal expect interface ECDHPrivateKey
|
||||
|
||||
internal expect interface ECDHPublicKey
|
||||
|
||||
internal expect class ECDHKeyPairImpl : ECDHKeyPair
|
||||
|
||||
internal interface ECDHKeyPair {
|
||||
val privateKey: ECDHPrivateKey
|
||||
val publicKey: ECDHPublicKey
|
||||
|
||||
/**
|
||||
* 私匙和动态公匙([ECDHInitialPublicKey]) 计算得到的 shareKey
|
||||
*/
|
||||
val maskedShareKey: ByteArray
|
||||
|
||||
/**
|
||||
* 私匙和动态公匙([ECDHInitialPublicKey]) 计算得到的 publicKey
|
||||
*/
|
||||
val maskedPublicKey: ByteArray
|
||||
|
||||
object DefaultStub : ECDHKeyPair {
|
||||
val defaultPublicKey =
|
||||
"04edb8906046f5bfbe9abbc5a88b37d70a6006bfbabc1f0cd49dfb33505e63efc5d78ee4e0a4595033b93d02096dcd3190279211f7b4f6785079e19004aa0e03bc".hexToBytes()
|
||||
val defaultShareKey = "c129edba736f4909ecc4ab8e010f46a3".hexToBytes()
|
||||
|
||||
override val privateKey: Nothing get() = error("stub!")
|
||||
override val publicKey: Nothing get() = error("stub!")
|
||||
override val maskedShareKey: ByteArray get() = defaultShareKey
|
||||
override val maskedPublicKey: ByteArray
|
||||
get() = defaultPublicKey
|
||||
}
|
||||
}
|
||||
|
||||
internal expect class ECDH(keyPair: ECDHKeyPair) {
|
||||
val keyPair: ECDHKeyPair
|
||||
|
||||
/**
|
||||
* 由 [keyPair] 的私匙和 [peerPublicKey] 计算 shareKey
|
||||
*/
|
||||
fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray
|
||||
|
||||
companion object {
|
||||
val isECDHAvailable: Boolean
|
||||
|
||||
|
||||
/**
|
||||
* This API is platform dependent.
|
||||
* On JVM you need to add `signHead`,
|
||||
* but on Native you need to provide a key with initial byte value 0x04 and of 65 bytes' length.
|
||||
*/
|
||||
fun constructPublicKey(key: ByteArray): ECDHPublicKey
|
||||
|
||||
/**
|
||||
* 由完整的 rsaKey 校验 publicKey
|
||||
*/
|
||||
fun verifyPublicKey(version: Int, publicKey: String, publicKeySign: String): Boolean
|
||||
|
||||
/**
|
||||
* 生成随机密匙对
|
||||
*/
|
||||
fun generateKeyPair(initialPublicKey: ECDHPublicKey = defaultInitialPublicKey.key): ECDHKeyPair
|
||||
|
||||
/**
|
||||
* 由一对密匙计算服务器需要的 shareKey
|
||||
*/
|
||||
fun calculateShareKey(privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey): ByteArray
|
||||
}
|
||||
|
||||
override fun toString(): String
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
internal fun ecdhWithPublicKey(initialPublicKey: ECDHInitialPublicKey = defaultInitialPublicKey): ECDHWithPublicKey =
|
||||
ECDHWithPublicKey(initialPublicKey)
|
||||
|
||||
internal data class ECDHWithPublicKey(private val initialPublicKey: ECDHInitialPublicKey = defaultInitialPublicKey) {
|
||||
private val ecdhInstance: ECDH = ECDH(ECDH.generateKeyPair(initialPublicKey.key))
|
||||
val version: Int = initialPublicKey.version
|
||||
val keyPair: ECDHKeyPair = ecdhInstance.keyPair
|
||||
|
||||
/**
|
||||
* 由 [keyPair] 的私匙和 [peerPublicKey] 计算 shareKey
|
||||
*/
|
||||
fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray =
|
||||
ecdhInstance.calculateShareKeyByPeerPublicKey(peerPublicKey)
|
||||
|
||||
}
|
||||
// gen by p-256
|
||||
//3059301306072A8648CE3D020106082A8648CE3D03010703420004FA540CB3F755D0A6572338777A4D0BEAFA86664D53040B27331CBF1B7F3C226CE8A1C05EFA9028F85510B103D8175172895C9F9FE4C80A47894BCA2BE569BFCB
|
||||
//3059301306072A8648CE3D020106082A8648CE3D03010703420004949D41D7C14B92F0CB94B6232FB87BA51B0D5AB661FBAF95599A97472FFC4F50BC8CEC5865E79DB3782459A6E9A2298954CD198A25274CEEA8F925342D763D62
|
||||
|
||||
/*
|
||||
// p-256
|
||||
get() = ECDH.constructPublicKey(
|
||||
("3059301306072A8648CE3D020106082A8648CE3D03010703420004" +
|
||||
"EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFB" +
|
||||
"C91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E"
|
||||
).chunkedHexToBytes()
|
||||
)
|
||||
* */
|
||||
|
||||
@Serializable
|
||||
internal data class ECDHInitialPublicKey(val version: Int = 1, val keyStr: String, val expireTime: Long = 0) {
|
||||
@Transient
|
||||
internal val key: ECDHPublicKey = keyStr.hexToBytes().adjustToPublicKey()
|
||||
}
|
||||
|
||||
internal val defaultInitialPublicKey: ECDHInitialPublicKey by lazy { ECDHInitialPublicKey(keyStr = "04EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFBC91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E") }
|
||||
|
||||
|
||||
internal expect fun ByteArray.adjustToPublicKey(): ECDHPublicKey
|
||||
|
||||
internal val ECDH.Companion.curveName get() = "prime256v1" // p-256
|
44
mirai-core/src/commonMain/kotlin/utils/crypto/Ecdh.kt
Normal file
44
mirai-core/src/commonMain/kotlin/utils/crypto/Ecdh.kt
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import kotlin.jvm.JvmStatic
|
||||
|
||||
internal data class EcdhKeyPair<TPublicKey, TPrivate>(val public: TPublicKey, val private: TPrivate)
|
||||
|
||||
internal interface Ecdh<TPublicKey, TPrivate> {
|
||||
fun generateKeyPair(): EcdhKeyPair<TPublicKey, TPrivate>
|
||||
fun calculateShareKey(peerKey: TPublicKey, privateKey: TPrivate): ByteArray
|
||||
|
||||
/**
|
||||
* @param encoded The encoding should conform with
|
||||
* Sec. 2.3.3 of the SECG SEC 1 ("Elliptic Curve Cryptography") standard,
|
||||
* with compression is off.
|
||||
* @see <a href="https://www.secg.org/sec1-v2.pdf">SECG SEC 1: Elliptic Curve Cryptography</a>
|
||||
*/
|
||||
fun importPublicKey(encoded: ByteArray): TPublicKey
|
||||
|
||||
/**
|
||||
* @return The encoding conforms with
|
||||
* Sec. 2.3.3 of the SECG SEC 1 ("Elliptic Curve Cryptography") standard,
|
||||
* with compression is off.
|
||||
* @see <a href="https://www.secg.org/sec1-v2.pdf">SECG SEC 1: Elliptic Curve Cryptography</a>
|
||||
*/
|
||||
fun exportPublicKey(key: TPublicKey): ByteArray
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val Instance by lazy {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
Ecdh.create() as Ecdh<Any, Any>
|
||||
}
|
||||
}
|
||||
}
|
||||
internal expect fun Ecdh.Companion.create() : Ecdh<*, *>
|
60
mirai-core/src/commonMain/kotlin/utils/crypto/QQEcdh.kt
Normal file
60
mirai-core/src/commonMain/kotlin/utils/crypto/QQEcdh.kt
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import net.mamoe.mirai.utils.cast
|
||||
import net.mamoe.mirai.utils.hexToBytes
|
||||
import net.mamoe.mirai.utils.md5
|
||||
|
||||
private val defaultPublicKey =
|
||||
"04edb8906046f5bfbe9abbc5a88b37d70a6006bfbabc1f0cd49dfb33505e63efc5d78ee4e0a4595033b93d02096dcd3190279211f7b4f6785079e19004aa0e03bc".hexToBytes()
|
||||
private val defaultQQShareKey = "c129edba736f4909ecc4ab8e010f46a3".hexToBytes()
|
||||
|
||||
@Serializable
|
||||
internal data class QQEcdhInitialPublicKey(val version: Int = 1, val keyStr: String, val expireTime: Long = 0) {
|
||||
@Transient
|
||||
internal val key = Ecdh.Instance.importPublicKey(keyStr.hexToBytes())
|
||||
companion object {
|
||||
internal val default: QQEcdhInitialPublicKey by lazy {
|
||||
QQEcdhInitialPublicKey(keyStr = "04EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFBC91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal expect fun QQEcdhInitialPublicKey.verify(sign: String): Boolean
|
||||
|
||||
internal data class QQEcdh(private val initialPublicKey: QQEcdhInitialPublicKey = QQEcdhInitialPublicKey.default) {
|
||||
val version: Int = initialPublicKey.version
|
||||
private val keyPair = try {
|
||||
Ecdh.Instance.generateKeyPair()
|
||||
} catch (e:Throwable){
|
||||
null
|
||||
}
|
||||
val publicKey: ByteArray = keyPair?.let {
|
||||
Ecdh.Instance.exportPublicKey(it.public)
|
||||
} ?: defaultPublicKey
|
||||
|
||||
val initialQQShareKey: ByteArray = keyPair?.let {
|
||||
Ecdh.Instance.calculateShareKey(initialPublicKey.key, it.private).copyOf(16).md5()
|
||||
} ?: defaultQQShareKey
|
||||
|
||||
val fallbackMode : Boolean = keyPair == null
|
||||
|
||||
/**
|
||||
* 由 [keyPair] 的私匙和 [peerKey] 计算 shareKey
|
||||
*/
|
||||
fun calculateQQShareKey(peerKey: Any): ByteArray {
|
||||
check (keyPair != null) {
|
||||
"cannot calculate QQShareKey in fallback mode"
|
||||
}
|
||||
return Ecdh.Instance.calculateShareKey(peerKey.cast(), keyPair.private).copyOf(16).md5()
|
||||
}
|
||||
}
|
@ -15,29 +15,31 @@ import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class ECDHTest : AbstractTest() {
|
||||
internal class EcdhTest : AbstractTest() {
|
||||
|
||||
@Test
|
||||
fun `can generate key pair`() {
|
||||
val alice = ECDH.generateKeyPair()
|
||||
val bob = ECDH.generateKeyPair()
|
||||
val alice = Ecdh.Instance.generateKeyPair()
|
||||
val bob = Ecdh.Instance.generateKeyPair()
|
||||
|
||||
val aliceSecret = ECDH.calculateShareKey(alice.privateKey, bob.publicKey)
|
||||
val bobSecret = ECDH.calculateShareKey(bob.privateKey, alice.publicKey)
|
||||
val aliceSecret = Ecdh.Instance.calculateShareKey(bob.public, alice.private)
|
||||
val bobSecret = Ecdh.Instance.calculateShareKey(alice.public, bob.private)
|
||||
|
||||
println(aliceSecret.toUHexString())
|
||||
assertContentEquals(aliceSecret, bobSecret)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can get masked keys`() {
|
||||
val alice = ECDH.generateKeyPair()
|
||||
fun `can export and import public keys`() {
|
||||
val alice = Ecdh.Instance.generateKeyPair()
|
||||
|
||||
println(alice)
|
||||
val maskedPublicKey = alice.maskedPublicKey
|
||||
println(maskedPublicKey.toUHexString())
|
||||
assertEquals(0x04, maskedPublicKey.first())
|
||||
println(alice.maskedShareKey.toUHexString())
|
||||
val publicKey = Ecdh.Instance.exportPublicKey(alice.public)
|
||||
println(publicKey.toUHexString())
|
||||
assertEquals(0x04, publicKey.first())
|
||||
|
||||
val importedAlicePubKey = Ecdh.Instance.importPublicKey(publicKey)
|
||||
assertEquals(alice.public, importedAlicePubKey)
|
||||
}
|
||||
|
||||
/*
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmName("ECDHKt_jvmBase")
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import net.mamoe.mirai.utils.decodeBase64
|
||||
import net.mamoe.mirai.utils.hexToBytes
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
|
||||
|
||||
|
||||
@Suppress("ACTUAL_WITHOUT_EXPECT")
|
||||
internal actual typealias ECDHPrivateKey = PrivateKey
|
||||
@Suppress("ACTUAL_WITHOUT_EXPECT")
|
||||
internal actual typealias ECDHPublicKey = PublicKey
|
||||
|
||||
internal actual class ECDHKeyPairImpl(
|
||||
private val delegate: KeyPair,
|
||||
initialPublicKey: ECDHPublicKey = defaultInitialPublicKey.key
|
||||
) : ECDHKeyPair {
|
||||
override val privateKey: ECDHPrivateKey get() = delegate.private
|
||||
override val publicKey: ECDHPublicKey get() = delegate.public
|
||||
override val maskedPublicKey: ByteArray by lazy { publicKey.encoded.copyOfRange(26, 91) }
|
||||
override val maskedShareKey: ByteArray by lazy { ECDH.calculateShareKey(privateKey, initialPublicKey) }
|
||||
}
|
||||
|
||||
|
||||
internal val publicKeyForVerify: ECDHPublicKey by lazy {
|
||||
KeyFactory.getInstance("RSA")
|
||||
.generatePublic(X509EncodedKeySpec("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuJTW4abQJXeVdAODw1CamZH4QJZChyT08ribet1Gp0wpSabIgyKFZAOxeArcCbknKyBrRY3FFI9HgY1AyItH8DOUe6ajDEb6c+vrgjgeCiOiCVyum4lI5Fmp38iHKH14xap6xGaXcBccdOZNzGT82sPDM2Oc6QYSZpfs8EO7TYT7KSB2gaHz99RQ4A/Lel1Vw0krk+DescN6TgRCaXjSGn268jD7lOO23x5JS1mavsUJtOZpXkK9GqCGSTCTbCwZhI33CpwdQ2EHLhiP5RaXZCio6lksu+d8sKTWU1eEiEb3cQ7nuZXLYH7leeYFoPtbFV4RicIWp0/YG+RP7rLPCwIDAQAB".decodeBase64()))
|
||||
}
|
||||
|
||||
private val signHead = "3059301306072a8648ce3d020106082a8648ce3d030107034200".hexToBytes()
|
||||
|
||||
internal actual fun ByteArray.adjustToPublicKey(): ECDHPublicKey {
|
||||
return ECDH.constructPublicKey(signHead + this)
|
||||
}
|
91
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/JceEcdh.kt
Normal file
91
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/JceEcdh.kt
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.AlgorithmParameters
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.interfaces.ECPrivateKey
|
||||
import java.security.interfaces.ECPublicKey
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.ECParameterSpec
|
||||
import java.security.spec.ECPoint
|
||||
import java.security.spec.ECPublicKeySpec
|
||||
import javax.crypto.KeyAgreement
|
||||
|
||||
internal open class JceEcdh : Ecdh<ECPublicKey, ECPrivateKey> {
|
||||
protected open fun newECKeyPairGenerator() = KeyPairGenerator.getInstance("EC")
|
||||
protected open fun newECKeyFactory() = KeyFactory.getInstance("EC")
|
||||
protected open fun newECAlgorithmParameters() = AlgorithmParameters.getInstance("EC")
|
||||
protected open fun newECDHKeyAgreement() = KeyAgreement.getInstance("ECDH")
|
||||
|
||||
override fun generateKeyPair(): EcdhKeyPair<ECPublicKey, ECPrivateKey> {
|
||||
return newECKeyPairGenerator()
|
||||
.apply {
|
||||
// AKA. prime256v1
|
||||
// But `secp256r1` is more common
|
||||
initialize(ECGenParameterSpec("secp256r1"))
|
||||
}
|
||||
.genKeyPair()
|
||||
.let {
|
||||
EcdhKeyPair(it.public as ECPublicKey, it.private as ECPrivateKey)
|
||||
}
|
||||
}
|
||||
|
||||
override fun calculateShareKey(peerKey: ECPublicKey, privateKey: ECPrivateKey): ByteArray {
|
||||
return newECDHKeyAgreement().apply {
|
||||
init(privateKey)
|
||||
doPhase(peerKey, true)
|
||||
}.generateSecret()
|
||||
}
|
||||
|
||||
override fun importPublicKey(encoded: ByteArray): ECPublicKey {
|
||||
val params: ECParameterSpec = newECAlgorithmParameters().apply {
|
||||
init(ECGenParameterSpec("secp256r1"))
|
||||
}.getParameterSpec(ECParameterSpec::class.java)
|
||||
|
||||
require(encoded[0] == 0x04.toByte()) { "Only uncompressed format is supported" }
|
||||
val fieldSize = params.curve.field.fieldSize
|
||||
val elementSize = (fieldSize + 7) / 8
|
||||
val affineXBytes = ByteArray(elementSize)
|
||||
val affineYBytes = ByteArray(elementSize)
|
||||
System.arraycopy(encoded, 1, affineXBytes, 0, elementSize)
|
||||
System.arraycopy(encoded, elementSize + 1, affineYBytes, 0, elementSize)
|
||||
val point = ECPoint(BigInteger(1, affineXBytes), BigInteger(1, affineYBytes))
|
||||
|
||||
val keySpec = ECPublicKeySpec(point, params)
|
||||
return newECKeyFactory().generatePublic(keySpec) as ECPublicKey
|
||||
}
|
||||
|
||||
override fun exportPublicKey(key: ECPublicKey): ByteArray {
|
||||
val point = key.w
|
||||
val fieldSize = key.params.curve.field.fieldSize
|
||||
val elementSize = (fieldSize + 7) / 8
|
||||
val x = point.affineX.toByteArray()
|
||||
val y = point.affineY.toByteArray()
|
||||
val startOfX = countLeadingZeros(x)
|
||||
val startOfY = countLeadingZeros(y)
|
||||
val encoded = ByteArray(elementSize * 2 + 1)
|
||||
encoded[0] = 0x04 // uncompressed
|
||||
System.arraycopy(x, startOfX, encoded, elementSize - x.size + startOfX + 1, x.size - startOfX)
|
||||
System.arraycopy(y, startOfY, encoded, encoded.size - y.size + startOfY, y.size - startOfY)
|
||||
return encoded
|
||||
}
|
||||
|
||||
private fun countLeadingZeros(bytes: ByteArray): Int {
|
||||
for (i in bytes.indices) {
|
||||
if (bytes[i] != 0.toByte()) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return bytes.size
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import java.security.AlgorithmParameters
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.Provider
|
||||
import javax.crypto.KeyAgreement
|
||||
|
||||
internal class JceEcdhWithProvider(val provider: Provider): JceEcdh() {
|
||||
override fun newECKeyPairGenerator() = KeyPairGenerator.getInstance("EC", provider)
|
||||
override fun newECKeyFactory() = KeyFactory.getInstance("EC", provider)
|
||||
override fun newECAlgorithmParameters() = AlgorithmParameters.getInstance("EC", provider)
|
||||
override fun newECDHKeyAgreement() = KeyAgreement.getInstance("ECDH", provider)
|
||||
}
|
28
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/QQEcdhJvm.kt
Normal file
28
mirai-core/src/jvmBaseMain/kotlin/utils/crypto/QQEcdhJvm.kt
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import net.mamoe.mirai.utils.decodeBase64
|
||||
import java.security.KeyFactory
|
||||
import java.security.Signature
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
|
||||
internal val publicKeyForVerify by lazy {
|
||||
KeyFactory.getInstance("RSA")
|
||||
.generatePublic(X509EncodedKeySpec("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuJTW4abQJXeVdAODw1CamZH4QJZChyT08ribet1Gp0wpSabIgyKFZAOxeArcCbknKyBrRY3FFI9HgY1AyItH8DOUe6ajDEb6c+vrgjgeCiOiCVyum4lI5Fmp38iHKH14xap6xGaXcBccdOZNzGT82sPDM2Oc6QYSZpfs8EO7TYT7KSB2gaHz99RQ4A/Lel1Vw0krk+DescN6TgRCaXjSGn268jD7lOO23x5JS1mavsUJtOZpXkK9GqCGSTCTbCwZhI33CpwdQ2EHLhiP5RaXZCio6lksu+d8sKTWU1eEiEb3cQ7nuZXLYH7leeYFoPtbFV4RicIWp0/YG+RP7rLPCwIDAQAB".decodeBase64()))
|
||||
}
|
||||
|
||||
internal actual fun QQEcdhInitialPublicKey.verify(sign: String): Boolean {
|
||||
val arrayForVerify = "305$version$keyStr".toByteArray()
|
||||
val signInstance = Signature.getInstance("SHA256WithRSA").apply {
|
||||
initVerify(publicKeyForVerify)
|
||||
update(arrayForVerify)
|
||||
}
|
||||
return signInstance.verify(sign.decodeBase64())
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import net.mamoe.mirai.utils.decodeBase64
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.Security
|
||||
import java.security.Signature
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import javax.crypto.KeyAgreement
|
||||
|
||||
internal actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
||||
actual companion object {
|
||||
|
||||
actual val isECDHAvailable: Boolean
|
||||
|
||||
init {
|
||||
isECDHAvailable = kotlin.runCatching {
|
||||
fun testECDH() {
|
||||
ECDHKeyPairImpl(
|
||||
KeyPairGenerator.getInstance("ECDH")
|
||||
.also { it.initialize(ECGenParameterSpec(curveName)) }
|
||||
.genKeyPair()).let {
|
||||
calculateShareKey(it.privateKey, it.publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
if (kotlin.runCatching { testECDH() }.isSuccess) {
|
||||
return@runCatching
|
||||
}
|
||||
|
||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) != null) {
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
}
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
testECDH()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}.isSuccess
|
||||
}
|
||||
|
||||
actual fun generateKeyPair(initialPublicKey: ECDHPublicKey): ECDHKeyPair {
|
||||
if (!isECDHAvailable) {
|
||||
return ECDHKeyPair.DefaultStub
|
||||
}
|
||||
return ECDHKeyPairImpl(
|
||||
KeyPairGenerator.getInstance("ECDH")
|
||||
.also { it.initialize(ECGenParameterSpec(curveName)) }
|
||||
.genKeyPair(), initialPublicKey)
|
||||
}
|
||||
|
||||
actual fun verifyPublicKey(version: Int, publicKey: String, publicKeySign: String): Boolean {
|
||||
val arrayForVerify = "305$version$publicKey".toByteArray()
|
||||
val signInstance = Signature.getInstance("SHA256WithRSA")
|
||||
signInstance.initVerify(publicKeyForVerify)
|
||||
signInstance.update(arrayForVerify)
|
||||
return signInstance.verify(publicKeySign.decodeBase64())
|
||||
}
|
||||
|
||||
actual fun calculateShareKey(
|
||||
privateKey: ECDHPrivateKey,
|
||||
publicKey: ECDHPublicKey,
|
||||
): ByteArray {
|
||||
val instance = KeyAgreement.getInstance("ECDH", "BC")
|
||||
instance.init(privateKey)
|
||||
instance.doPhase(publicKey, true)
|
||||
return instance.generateSecret().copyOf(16).md5()
|
||||
}
|
||||
|
||||
actual fun constructPublicKey(key: ByteArray): ECDHPublicKey {
|
||||
return KeyFactory.getInstance("EC", "BC").generatePublic(X509EncodedKeySpec(key))
|
||||
}
|
||||
}
|
||||
|
||||
actual fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray {
|
||||
return calculateShareKey(keyPair.privateKey, peerPublicKey)
|
||||
}
|
||||
|
||||
actual override fun toString(): String {
|
||||
return "ECDH(keyPair=$keyPair)"
|
||||
}
|
||||
}
|
27
mirai-core/src/jvmMain/kotlin/utils/crypto/EcdhJvmDesktop.kt
Normal file
27
mirai-core/src/jvmMain/kotlin/utils/crypto/EcdhJvmDesktop.kt
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
|
||||
internal actual fun Ecdh.Companion.create(): Ecdh<*, *> =
|
||||
kotlin.runCatching {
|
||||
// try platform default EC/ECDH implementations first, which may have better performance
|
||||
// note that they may not work properly but being created successfully
|
||||
JceEcdh().apply {
|
||||
val keyPair = generateKeyPair()
|
||||
calculateShareKey(keyPair.public, keyPair.private)
|
||||
val encoded = exportPublicKey(keyPair.public)
|
||||
importPublicKey(encoded)
|
||||
}
|
||||
}.getOrElse {
|
||||
// fallback to BouncyCastle
|
||||
JceEcdhWithProvider(BouncyCastleProvider())
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import net.mamoe.mirai.utils.hexToBytes
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
import openssl.*
|
||||
import platform.posix.errno
|
||||
|
||||
private const val curveId = NID_X9_62_prime256v1
|
||||
|
||||
// shared, not freed!
|
||||
private val group by lazy { EC_GROUP_new_by_curve_name(curveId) ?: error("Failed to get EC_GROUP") }
|
||||
|
||||
private val convForm by lazy { EC_GROUP_get_point_conversion_form(group) }
|
||||
|
||||
// shared, not freed!
|
||||
private val bnCtx by lazy { BN_CTX_new() }
|
||||
|
||||
// ====ATTENTION====
|
||||
// Do not use [platform.posix.free] easily
|
||||
// For anything allocated by OpenSSL, <type>_free or CRYPTO_free
|
||||
// (the underlying of OPENSSL_free macro) should be called.
|
||||
// It's more than dangerous to assume OpenSSL uses the same memory manager as general posix functions,
|
||||
// easily causing memory leaking (usually on *nix) or crash (usually on Windows)
|
||||
|
||||
internal actual interface ECDHPublicKey : OpenSSLKey {
|
||||
val encoded: ByteArray
|
||||
|
||||
/**
|
||||
* @return It is the caller's responsibility to free this memory with a subsequent call to [EC_POINT_free]
|
||||
*/
|
||||
fun toPoint(): CPointer<EC_POINT>
|
||||
}
|
||||
|
||||
internal actual interface ECDHPrivateKey : OpenSSLKey {
|
||||
/**
|
||||
* @return It is the caller's responsibility to free this memory with a subsequent call to [BN_free]
|
||||
*/
|
||||
fun toBignum(): CPointer<BIGNUM>
|
||||
}
|
||||
|
||||
internal class OpenSslPrivateKey(
|
||||
override val hex: String, // use Kotlin's memory
|
||||
) : ECDHPrivateKey {
|
||||
|
||||
override fun toBignum(): CPointer<BIGNUM> {
|
||||
val bn = BN_new() ?: error("Failed BN_new")
|
||||
val values = cValuesOf(bn)
|
||||
BN_hex2bn(values, hex).let { r ->
|
||||
if (r <= 0) error("Failed BN_hex2bn: $r")
|
||||
}
|
||||
return bn
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromKey(key: CPointer<EC_KEY>): OpenSslPrivateKey {
|
||||
// Note that the private key (bignum) is associated with the key
|
||||
// We can't free it, or it'll crash when EC_KEY_free
|
||||
val bn = EC_KEY_get0_private_key(key) ?: error("Failed EC_KEY_get0_private_key")
|
||||
val ptr = BN_bn2hex(bn) ?: error("Failed EC_POINT_bn2point")
|
||||
val hex = try {
|
||||
ptr.toKString()
|
||||
} finally {
|
||||
CRYPTO_free(ptr, "OpenSslPrivateKey.Companion.fromKey(key: CPointer<EC_KEY>)", -1)
|
||||
}
|
||||
return OpenSslPrivateKey(hex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal interface OpenSSLKey {
|
||||
val hex: String
|
||||
}
|
||||
|
||||
internal class OpenSslPublicKey(override val hex: String) : ECDHPublicKey {
|
||||
override val encoded: ByteArray = hex.hexToBytes()
|
||||
|
||||
override fun toPoint(): CPointer<EC_POINT> {
|
||||
val point = EC_POINT_new(group)
|
||||
EC_POINT_hex2point(group, hex, point, bnCtx) ?: error("Failed EC_POINT_hex2point")
|
||||
return point!!
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromKey(key: CPointer<EC_KEY>): OpenSslPublicKey =
|
||||
fromPoint(EC_KEY_get0_public_key(key) ?: error("Failed to get private key"))
|
||||
|
||||
fun fromPoint(point: CPointer<EC_POINT>): OpenSslPublicKey {
|
||||
return OpenSslPublicKey(point.toKtHex())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal actual class ECDHKeyPairImpl(
|
||||
override val privateKey: OpenSslPrivateKey,
|
||||
override val publicKey: OpenSslPublicKey,
|
||||
initialPublicKey: ECDHPublicKey
|
||||
) : ECDHKeyPair {
|
||||
|
||||
override val maskedPublicKey: ByteArray by lazy { publicKey.encoded }
|
||||
override val maskedShareKey: ByteArray by lazy { ECDH.calculateShareKey(privateKey, initialPublicKey) }
|
||||
|
||||
companion object {
|
||||
fun fromKey(
|
||||
key: CPointer<EC_KEY>,
|
||||
initialPublicKey: ECDHPublicKey = defaultInitialPublicKey.key
|
||||
): ECDHKeyPairImpl {
|
||||
return ECDHKeyPairImpl(OpenSslPrivateKey.fromKey(key), OpenSslPublicKey.fromKey(key), initialPublicKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CPointer<EC_POINT>.toKtHex(): String {
|
||||
val ptr = EC_POINT_point2hex(group, this, convForm, bnCtx) ?: error("Failed EC_POINT_point2hex")
|
||||
return try {
|
||||
ptr.toKString()
|
||||
} finally {
|
||||
CRYPTO_free(ptr, "CPointer<EC_POINT>.toKtHex()", -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
||||
|
||||
/**
|
||||
* 由 [keyPair] 的私匙和 [peerPublicKey] 计算 shareKey
|
||||
*/
|
||||
actual fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray {
|
||||
return calculateShareKey(keyPair.privateKey, peerPublicKey)
|
||||
}
|
||||
|
||||
actual companion object {
|
||||
actual val isECDHAvailable: Boolean get() = true
|
||||
|
||||
/**
|
||||
* 由完整的 publicKey ByteArray 得到 [ECDHPublicKey]
|
||||
*/
|
||||
actual fun constructPublicKey(key: ByteArray): ECDHPublicKey {
|
||||
val p = EC_POINT_new(group) ?: error("Failed to create EC_POINT")
|
||||
|
||||
// TODO: 2022/6/1 native: check memory
|
||||
EC_POINT_hex2point(group, key.toUHexString("").lowercase(), p, bnCtx)
|
||||
|
||||
return OpenSslPublicKey.fromPoint(p)
|
||||
}
|
||||
|
||||
/**
|
||||
* 由完整的 rsaKey 校验 publicKey
|
||||
*/
|
||||
actual fun verifyPublicKey(
|
||||
version: Int,
|
||||
publicKey: String,
|
||||
publicKeySign: String
|
||||
): Boolean = true
|
||||
|
||||
/**
|
||||
* 生成随机密匙对
|
||||
*/
|
||||
actual fun generateKeyPair(initialPublicKey: ECDHPublicKey): ECDHKeyPair {
|
||||
val key: CPointer<EC_KEY> = EC_KEY_new_by_curve_name(curveId)
|
||||
?: throw IllegalStateException("Failed to create key curve, $errno")
|
||||
try {
|
||||
if (1 != EC_KEY_generate_key(key)) {
|
||||
throw IllegalStateException("Failed to generate key, $errno")
|
||||
}
|
||||
return ECDHKeyPairImpl.fromKey(key, initialPublicKey)
|
||||
} finally {
|
||||
EC_KEY_free(key)
|
||||
}
|
||||
}
|
||||
|
||||
fun calculateCanonicalShareKey(privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey): ByteArray {
|
||||
check(publicKey is OpenSslPublicKey)
|
||||
check(privateKey is OpenSslPrivateKey)
|
||||
|
||||
val k = EC_KEY_new_by_curve_name(curveId) ?: error("Failed to create EC key")
|
||||
try {
|
||||
val privateBignum = privateKey.toBignum()
|
||||
try {
|
||||
EC_KEY_set_private_key(k, privateBignum).let { r ->
|
||||
if (r != 1) error("Failed EC_KEY_set_private_key: $r")
|
||||
}
|
||||
|
||||
val fieldSize = EC_GROUP_get_degree(group)
|
||||
if (fieldSize <= 0) {
|
||||
error("Failed EC_GROUP_get_degree: $fieldSize")
|
||||
}
|
||||
|
||||
var secretLen = (fieldSize + 7) / 8
|
||||
|
||||
val publicPoint = publicKey.toPoint()
|
||||
try {
|
||||
ByteArray(secretLen.convert()).usePinned { pin ->
|
||||
secretLen = ECDH_compute_key(pin.addressOf(0), secretLen.convert(), publicPoint, k, null)
|
||||
if (secretLen <= 0) {
|
||||
error("Failed to compute secret")
|
||||
}
|
||||
|
||||
return pin.get().copyOf(secretLen)
|
||||
}
|
||||
} finally {
|
||||
EC_POINT_free(publicPoint)
|
||||
}
|
||||
} finally {
|
||||
BN_free(privateBignum)
|
||||
}
|
||||
} finally {
|
||||
EC_KEY_free(k)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun calculateShareKey(
|
||||
privateKey: ECDHPrivateKey,
|
||||
publicKey: ECDHPublicKey
|
||||
): ByteArray = calculateCanonicalShareKey(privateKey, publicKey).copyOf(16).md5()
|
||||
}
|
||||
|
||||
actual override fun toString(): String = "ECDH($keyPair)"
|
||||
}
|
||||
|
||||
internal actual fun ByteArray.adjustToPublicKey(): ECDHPublicKey {
|
||||
return ECDH.constructPublicKey(this)
|
||||
}
|
13
mirai-core/src/nativeMain/kotlin/utils/crypto/EcdhNative.kt
Normal file
13
mirai-core/src/nativeMain/kotlin/utils/crypto/EcdhNative.kt
Normal file
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
|
||||
internal actual fun Ecdh.Companion.create(): Ecdh<*, *> = OpenSslEcdh()
|
147
mirai-core/src/nativeMain/kotlin/utils/crypto/OpenSslEcdh.kt
Normal file
147
mirai-core/src/nativeMain/kotlin/utils/crypto/OpenSslEcdh.kt
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import openssl.*
|
||||
import platform.posix.errno
|
||||
import kotlin.native.internal.createCleaner
|
||||
|
||||
private const val curveId = NID_X9_62_prime256v1
|
||||
private val group by lazy { EC_GROUP_new_by_curve_name(curveId) ?: error("Failed to get EC_GROUP") }
|
||||
private val convForm by lazy { EC_GROUP_get_point_conversion_form(group) }
|
||||
private val bnCtx by lazy { BN_CTX_new() }
|
||||
|
||||
|
||||
internal class OpenSslECPublicKey private constructor(val point: CPointer<EC_POINT>) {
|
||||
@Suppress("unused")
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private val cleaner = createCleaner(point) {
|
||||
EC_POINT_free(it)
|
||||
}
|
||||
|
||||
fun export(): ByteArray {
|
||||
val len = EC_POINT_point2oct(group, point, convForm, null, 0, null)
|
||||
val bytes = ByteArray(len.convert())
|
||||
bytes.usePinned {
|
||||
EC_POINT_point2oct(group, point, convForm, it.addressOf(0).reinterpret(), len, bnCtx)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
return (other as? OpenSslECPublicKey)?.let {
|
||||
EC_POINT_cmp(group, point, it.point, bnCtx) == 0
|
||||
} ?: false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return export().hashCode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun copyFrom(source: CPointer<EC_POINT>): OpenSslECPublicKey {
|
||||
return OpenSslECPublicKey(EC_POINT_dup(source, group) ?: error("Failed to dup a EC_POINT"))
|
||||
}
|
||||
|
||||
fun import(encoded: ByteArray): OpenSslECPublicKey {
|
||||
val point = EC_POINT_new(group) ?: error("Failed to create EC_POINT")
|
||||
encoded.usePinned {
|
||||
EC_POINT_oct2point(group, point, it.addressOf(0).reinterpret(), it.get().size.convert(), bnCtx)
|
||||
}
|
||||
return OpenSslECPublicKey(point)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class OpenSslECPrivateKey private constructor(val bn: CPointer<BIGNUM>) {
|
||||
@Suppress("unused")
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private val cleaner = createCleaner(bn) {
|
||||
BN_free(it)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
return (other as? OpenSslECPrivateKey)?.let {
|
||||
BN_cmp(bn, other.bn) == 0
|
||||
} ?: false
|
||||
}
|
||||
|
||||
fun export(): ByteArray {
|
||||
val len = (BN_num_bits(bn)+7)/8
|
||||
val bytes = ByteArray(len)
|
||||
bytes.usePinned {
|
||||
BN_bn2bin(bn, it.addressOf(0).reinterpret())
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return export().hashCode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun copyFrom(source: CPointer<BIGNUM>): OpenSslECPrivateKey {
|
||||
return OpenSslECPrivateKey(BN_dup(source) ?: error("Failed to dup a BIGNUM"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class OpenSslEcdh : Ecdh<OpenSslECPublicKey, OpenSslECPrivateKey> {
|
||||
override fun generateKeyPair(): EcdhKeyPair<OpenSslECPublicKey, OpenSslECPrivateKey> {
|
||||
val key: CPointer<EC_KEY> = EC_KEY_new_by_curve_name(curveId)
|
||||
?: throw IllegalStateException("Failed to create key curve, $errno")
|
||||
try {
|
||||
if (1 != EC_KEY_generate_key(key)) {
|
||||
throw IllegalStateException("Failed to generate key, $errno")
|
||||
}
|
||||
val public =
|
||||
OpenSslECPublicKey.copyFrom(EC_KEY_get0_public_key(key) ?: error("Failed EC_key_get0_public_key"))
|
||||
val private =
|
||||
OpenSslECPrivateKey.copyFrom(EC_KEY_get0_private_key(key) ?: error("Failed EC_KEY_get0_private_key"))
|
||||
return EcdhKeyPair(public, private)
|
||||
} finally {
|
||||
EC_KEY_free(key)
|
||||
}
|
||||
}
|
||||
|
||||
override fun calculateShareKey(peerKey: OpenSslECPublicKey, privateKey: OpenSslECPrivateKey): ByteArray {
|
||||
val k = EC_KEY_new_by_curve_name(curveId) ?: error("Failed to create EC key")
|
||||
try {
|
||||
EC_KEY_set_private_key(k, privateKey.bn).let { r ->
|
||||
if (r != 1) error("Failed EC_KEY_set_private_key: $r")
|
||||
}
|
||||
val fieldSize = EC_GROUP_get_degree(group)
|
||||
if (fieldSize <= 0) {
|
||||
error("Failed EC_GROUP_get_degree: $fieldSize")
|
||||
}
|
||||
var secretLen = (fieldSize + 7) / 8
|
||||
ByteArray(secretLen.convert()).usePinned { pin ->
|
||||
secretLen = ECDH_compute_key(pin.addressOf(0), secretLen.convert(), peerKey.point, k, null)
|
||||
if (secretLen <= 0) {
|
||||
error("Failed to compute secret")
|
||||
}
|
||||
return pin.get().copyOf(secretLen)
|
||||
}
|
||||
} finally {
|
||||
EC_KEY_free(k)
|
||||
}
|
||||
}
|
||||
|
||||
override fun importPublicKey(encoded: ByteArray): OpenSslECPublicKey {
|
||||
return OpenSslECPublicKey.import(encoded)
|
||||
}
|
||||
|
||||
override fun exportPublicKey(key: OpenSslECPublicKey): ByteArray {
|
||||
return key.export()
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils.crypto
|
||||
|
||||
internal actual fun QQEcdhInitialPublicKey.verify(sign: String): Boolean {
|
||||
// FIXME
|
||||
return true
|
||||
}
|
Loading…
Reference in New Issue
Block a user