https://kotlinlang.org logo
#multiplatform
Title
# multiplatform
t

Thanh nguyen

03/08/2024, 4:20 AM
Hi every one, Can I encrypt RSA publicKey for IOS ? Example as android: actual fun loadPublicKey(myPublicKey:String): PublicKey? { val data = Base64.decode(myPublicKey) val spec = X509EncodedKeySpec(data) val fact = KeyFactory.getInstance(Algorithm.RSA) return fact.generatePublic(spec) }
s

Stefan Oltmann

03/08/2024, 5:57 AM
m

Matt Nelson

03/11/2024, 1:22 PM
It's tricky if you want those keys to be interoperable between platforms because of encodings. Example: Generate keys on
iOS
, use them on Jvm/Android. Darwin generates PKCS#1 encoded RSA keys, but also accepts X509 pubkeys when instantiating the
SecKeyRef
. If you take the public key generated by darwin target and try to load it for Jvm, it will fail. Must manipulate the DER encoding to transform from PKCS#1 to X509.
Copy code
internal class RSAKey private constructor() {

    @JvmInline
    internal value class Private(private val key: ByteArray) {

        internal fun <R: Any?> use(block: (key: ByteArray) -> R): R {
            val copy = key.copyOf()

            val result = try {
                block(copy)
            } finally {
                copy.fill(0)
            }

            return result
        }

        @Throws(GeneralSecurityException::class)
        internal fun <R: Any?> usePKCS8(block: (key: ByteArray) -> R): R {
            val copy = if (PKCS1offset() != 0) {
                // Assume it's already PKCS#8
                key.copyOf()
            } else {
                val kLen = key.size.toDERLen()

                // Need to pull that version byte from the PKCS#1 encoding
                // and use it in the PKCS#8 encoding.
                //
                // PKCS#1 DER encoding looks like the following.
                // SEQUENCE (tag: 48), key length bytes (2 to 5 bytes)
                // INTEGER (tag: 02), length byte (1 byte), version byte
                val v = key[1 + kLen.lastIndex + 1 + 1 + 1]

                val header = byteArrayOf(
                    INTEGER, 1, v,
                    *PKCS8,
                    *kLen,
                )

                val derLen = (header.size + key.size).toDERLen()

                byteArrayOf(SEQUENCE) + derLen + header + key
            }

            val result = try {
                block(copy)
            } finally {
                copy.fill(0)
            }

            return result
        }

        @Throws(GeneralSecurityException::class)
        internal fun <R: Any?> withPKCS1offset(block: (key: ByteArray, offset: Int) -> R): R {
            val offset = PKCS1offset()
            val copy = key.copyOf()

            val result = try {
                block(copy, offset)
            } finally {
                copy.fill(0)
            }

            return result
        }

        @Throws(GeneralSecurityException::class)
        private fun PKCS1offset(): Int {
            return try {
                var index = 0

                if (key[index++] != SEQUENCE) {
                    throw GeneralSecurityException("DER decoding failure. RSAPrivateKey is not DER encoded.")
                }

                // skip over DER length bytes
                index += key[index].toDERLenCount()

                // INTEGER, length (1 byte)
                if (key[index++] != INTEGER && key[index++] != 1.toByte()) {
                    throw GeneralSecurityException("DER decoding failure. Could not determine version.")
                }

                // skip over version byte
                index += 2

                // Assume PKCS#1 (no offset)
                if (key[index] == INTEGER) return 0

                for (i in PKCS8.indices) {
                    if (PKCS8[i] == key[index + i]) continue

                    // not PKCS#8, return no offset
                    return 0
                }

                index += PKCS8.size

                // skip over key length bytes
                index += key[index].toDERLenCount()

                // Index at which the PKCS#1 encoded RSA PrivateKey begins
                index
            } catch (e: IndexOutOfBoundsException) {
                throw GeneralSecurityException("Failed to parse RSAPrivateKey", e)
            }
        }

        private companion object {
            private val PKCS8 = byteArrayOf(
                SEQUENCE,
                *OID_RSA,
                NULL,
                OCTET_STRING,
            )
        }
    }

    @JvmInline
    internal value class Public(private val key: ByteArray) {

        @Throws(GeneralSecurityException::class)
        internal fun <R: Any?> useX509(block: (key: ByteArray) -> R): R {
            val copy = try {
                var index = 0

                if (key[index++] != SEQUENCE) {
                    throw GeneralSecurityException("DER decoding failure. RSAPublicKey is not DER encoded.")
                }

                // skip over DER length bytes
                index += key[index].toDERLenCount()

                when (key[index]) {
                    // Assume encoded as PKCS#1
                    INTEGER -> {
                        // NULL + key.size
                        val kLen = (1 + key.size).toDERLen()

                        val header = byteArrayOf(
                            SEQUENCE,
                            *OID_RSA,
                            NULL,
                            BIT_STRING,
                            *kLen,
                            NULL,
                        )

                        val derLen = (header.size + key.size).toDERLen()

                        byteArrayOf(SEQUENCE) + derLen + header + key
                    }
                    // Assume encoded as X509
                    SEQUENCE -> {
                        key.copyOf()
                    }
                    else -> {
                        throw GeneralSecurityException("DER decoding failure. Failed to determine DER encoding format.")
                    }
                }
            } catch (e: IndexOutOfBoundsException) {
                throw GeneralSecurityException("Failed to parse RSAPublicKey", e)
            }

            val result = try {
                block(copy)
            } finally {
                copy.fill(0)
            }

            return result
        }
    }

    private companion object {
        private const val BIT_STRING: Byte = 3
        private const val INTEGER: Byte = 2
        private const val NULL: Byte = 0
        private const val OCTET_STRING: Byte = 4
        private const val OID: Byte = 6
        private const val SEQUENCE: Byte = 48

        private val OID_RSA = byteArrayOf(13, OID, 9, 42, -122, 72, -122, -9, 13, 1, 1, 1, 5)

        @JvmStatic
        private fun Int.toDERLen(): ByteArray {
            var length = this

            if (length <= Byte.MAX_VALUE) {
                return ByteArray(1).apply { this[0] = length.toByte() }
            }

            val stack = ByteArray(5)
            var pos = stack.size

            do {
                stack[--pos] = length.toByte()
                length = length ushr 8
            } while (length != 0)

            val count = stack.size - pos
            stack[--pos] = (0x80 or count).toByte()

            if (pos == 0) return stack

            return stack.copyOfRange(fromIndex = pos, toIndex = stack.size)
        }

        @Throws(GeneralSecurityException::class)
        private fun Byte.toDERLenCount(): Int {
            return if (this < 0) {
                val c = 1 + (this xor Byte.MIN_VALUE)
                // Maximum 5 bytes
                if (c > 5) throw GeneralSecurityException("Invalid DER length")
                c
            } else {
                1
            }
        }
    }
}
o

Oleg Yukhnevich

03/11/2024, 1:28 PM
cryptography-kotlin supports PKCS8, PKCS1 and SPKI key encoding (works for all providers) 🙂
🙌 2