• 移动端 [Android & iOS] 压缩 ECDSA PublicKey


    移动端 [Android & iOS] 压缩 ECDSA PublicKey


    使用 Android KeyStore 和 iOS 的 Secure Enclave 提供的安全能力使用 P-256 来对 API 请求进行签名,服务器端再进行验证。

    但是发现不论是 iOS 还是安卓都没有提供一个便捷的方式从 iOS 的SecKeyCopyExternalRepresentation(SecKeyCopyPublicKey) 和 Android 的 KeyPair 中得到 33-bytes 的 compressed public key。

    压缩公钥(Compressed Public Key)是一种公钥编码方式,可以将 ECC(椭圆曲线密码学)公钥从 64 个字节压缩为 33 个字节。这种编码方式由一个字节的标识符和32个字节的公钥坐标的一部分(y坐标)组成,从而实现了公钥的压缩。在使用压缩公钥时,可以减少传输的数据量和存储空间,同时保持相同的安全性和加密效果。压缩公钥广泛应用于比特币、以太坊等区块链领域中。

    compressed_public_key = y is even?0x02:0x03 + x

    Android

    private fun secp256r1JKeyPair(
        packageManager: PackageManager,
        alias: String,
        throwIfNotExists: Boolean = false,
    ): KeyPair {
        val ks: KeyStore = KeyStore.getInstance(storeProvider).apply { load(null) }
        val keyPair: KeyPair = if (ks.containsAlias(alias)) {
            val entry = ks.getEntry(alias, null)
            if (entry !is KeyStore.PrivateKeyEntry) {
                throw TypeCastException()
            }
            KeyPair(entry.certificate.publicKey, entry.privateKey)
        } else if (throwIfNotExists) {
            throw KeyStoreException("No key was found with the alias $alias.")
        } else {
            val kpg: KeyPairGenerator =
                KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, storeProvider)
            var properties =
                KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
            val parameterSpec = KeyGenParameterSpec.Builder(alias, properties).apply {
                setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
                setDigests(KeyProperties.DIGEST_SHA256)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && hasStrongBox(packageManager)) {
                    setIsStrongBoxBacked(true)
                }
            }.build()
            kpg.initialize(parameterSpec)
            kpg.generateKeyPair()
        }
        return keyPair
    }
    
    @OptIn(ExperimentalUnsignedTypes::class)
    fun bnUByteArrayToUByteArray(bnUByteArray: UByteArray, expectLength: Int): UByteArray {
        if (bnUByteArray.size == expectLength + 1) {
            return bnUByteArray.sliceArray(1..expectLength)
        }
        if (bnUByteArray.size < expectLength) {
            return UByteArray(expectLength - bnUByteArray.size) { UByte.MIN_VALUE } + bnUByteArray
        }
        return bnUByteArray
    }
    
    @OptIn(ExperimentalUnsignedTypes::class)
    fun secp256r1PublicKey(
        packageManager: PackageManager,
        alias: String
    ): UByteArray {
        val kp = secp256r1JKeyPair(packageManager, alias)
        val publicKey = kp.public as ECPublicKey;
        val point = publicKey.w
        val x: BigInteger = point.affineX
        val y: BigInteger = point.affineY
        val xBytes: UByteArray = bnUByteArrayToUByteArray(x.toByteArray().toUByteArray(), 32)
    
        val yFLag = UByteArray(1)
        yFLag[0] = (if (y.testBit(0)) 0x03 else 0x02).toUByte()
        return yFLag + xBytes
    }
    
    @OptIn(ExperimentalUnsignedTypes::class)
    fun secp256r1Sign(
        packageManager: PackageManager,
        alias: String,
        payload: ByteArray
    ): UByteArray {
        val privateKey =
            secp256r1JKeyPair(packageManager, alias).private
        val signature = Signature.getInstance(signatureAlgorithm).run {
            initSign(privateKey)
            update(payload)
            sign()
        }
        val seq = DERSequence.fromByteArray(signature) as DLSequence
        val r = bnUByteArrayToUByteArray(
            (seq.getObjectAt(0) as ASN1Integer).value.toByteArray().toUByteArray(), 32
        )
        val s = bnUByteArrayToUByteArray(
            (seq.getObjectAt(1) as ASN1Integer).value.toByteArray().toUByteArray(), 32
        )
        return r + s
    }
    
    @OptIn(ExperimentalUnsignedTypes::class)
    private fun byteArrayToHexString(byteArray: UByteArray): String {
        return byteArray.joinToString(separator = "") { it.toString(16).padStart(2, '0') }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    iOS

    static func secp256r1Key(name: String, requiresBiometry: Bool = false) throws -> SecKey {
        let flags: SecAccessControlCreateFlags = requiresBiometry ? [.privateKeyUsage, .userPresence] : .privateKeyUsage
        let access =
        SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                                        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                                        flags,
                                        nil)!
        let tag = name.data(using: .utf8)!
        let attributes: [String: Any] = [
            kSecAttrKeyType as String           : kSecAttrKeyTypeEC,
            kSecAttrKeySizeInBits as String     : 256,
            kSecAttrTokenID as String           : kSecAttrTokenIDSecureEnclave,
            kSecPrivateKeyAttrs as String : [
                kSecAttrIsPermanent as String       : true,
                kSecAttrApplicationTag as String    : tag,
                kSecAttrAccessControl as String     : access
            ] as [String : Any]
        ]
      
        var error: Unmanaged<CFError>?
        guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
            throw error!.takeRetainedValue()
        }
      
        return privateKey
    }
    
    static func getCompressedPublicKey(key: SecKey) throws -> Data {
        guard let publicKeyData = SecKeyCopyExternalRepresentation(SecKeyCopyPublicKey(key)!, nil) as? Data else {
            throw NSError()
        }
        let x = publicKeyData.dropFirst().prefix(32)
        let y = publicKeyData.subdata(in: Range(33...64))
        return Data([0x02 | (y.last! & 0x01)]) + x
    }
    
    static func secp256r1Sign(name: String, payload: Data) -> Data {
        let key = secp256r1Key(name: name)
        var error: Unmanaged<CFError>?
        let asn1signature = SecKeyCreateSignature(key!, .ecdsaSignatureMessageX962SHA256, payload as CFData, &error)! as Data
        let signature = try! ECSignature(asn1: asn1signature)
        return signature.r + signature.s
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
  • 相关阅读:
    SpringBoot 日志文件
    Shell:判断当前用户
    Mycat中间件分配数据库
    51单片机驱动TCS3200颜色识别传感器
    mac电脑用谷歌浏览器对安卓手机H5页面进行inspect
    Perl语言入门学习
    帆软 列表自动滚动脚本
    算法---矩阵中战斗力最弱的 K 行(Kotlin)
    v-on事件处理指令;简写@事件名
    最新接口有关抖音,获取抖音分享口令url API
  • 原文地址:https://blog.csdn.net/qq_15988951/article/details/133344434