AndroidKeyStore – SecureKeyImport: How to use the imported key? (Receiving errors on calling `keyStore.getEntry`)

I tried using the secure key import feature of AndroidKeyStore, following the example at:

  • https://android.googlesource.com/platform/cts/+/master/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java

When I want to use the imported private key afterwards, calling androidKeyStore.getKey(importedKeyAlias, null), I get the following error:

java.security.UnrecoverableKeyException: Failed to obtain X.509 form of public key. Keystore has no public certificate stored.

I tried manually adding a certificate using androidKeyStore.setCertificateEntry(importedKeyAlias, generateCertificate(...)), but this call results in the error message

java.security.KeyStoreException: Entry exists and is not a trusted certificate.

I am executing the code on a Samsung Galaxy A34 5G, hence missing the StrongBox hardware, but according to my understanding this should still work with isStrongBox = false.

Question: How can I use the imported key?

Maybe there is an error in my code, so here is a MWE of the first approach, without trying to manually add a certificate.

fun minimumWorkingExample(isStrongBox: Boolean) {
    // primitives
    val wrappingKeyAlias = "wrappingKey"
    val importedKeyAlias = "importedKey"

    val importedKeySizeInBits = 2048
    val symmetricWrappingKeyTransformation = "RSA/ECB/OAEPPadding"

    // provisioning
    val androidKeyStoreProviderName = "AndroidKeyStore"
    val androidKeyStore =
        KeyStore.getInstance(androidKeyStoreProviderName).apply { load(null, null) }

    val challenge = Random.nextBytes(256 / 8)

    val wrappingKeyPairSpec = wrappingKeyPairSpecification(
        alias = wrappingKeyAlias,
        isStrongBoxBacked = isStrongBox,
        challenge = challenge,
    )

    KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA).apply {
        initialize(wrappingKeyPairSpec)
    }.generateKeyPair()

    val keyPairToBeImported =
        KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA).apply {
            initialize(importedKeySizeInBits, SecureRandom())
        }.generateKeyPair()

    // looking at data https://8gwifi.org/PemParserFunctions.jsp
    System.out.println("private key format: ${keyPairToBeImported.private.format}")
    System.out.println(
        "private key data: ${
            android.util.Base64.encodeToString(
                keyPairToBeImported.private.encoded, android.util.Base64.DEFAULT
            )
        }"
    )

    val wrappedPrivateKey = wrapKey(
        // as in the example source code
        androidKeyStore.getCertificateChain(wrappingKeyAlias).first().publicKey,
        keyPairToBeImported.private.encoded,
        keyFormat = 1, // represents "PKCS8" according to https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/types.hal
        transformation = symmetricWrappingKeyTransformation,
        wrappedKeyAuthorizationList(importedKeySizeInBits), // similar to the the example source code
    )

    val wrappedKeyEntry: KeyStore.Entry = WrappedKeyEntry(
        wrappedPrivateKey,
        wrappingKeyAlias,
        symmetricWrappingKeyTransformation,
        wrappingKeyPairSpec,
    )

    androidKeyStore.setEntry(importedKeyAlias, wrappedKeyEntry, null);
    // androidKeyStore.setCertificateEntry(keyAlias, generateCertificate(wrappedKeyPair.publicKey, somePrivateKeyThatIsNotImportant))
    System.out.println("Key available: ${androidKeyStore.containsAlias(importedKeyAlias)}")

    // load
    val importedKey = androidKeyStore.getEntry(importedKeyAlias, null)
    System.out.println("isPrivateKey: ${importedKey is PrivateKey}")
}

fun wrappingKeyPairSpecification(
    alias: String,
    isStrongBoxBacked: Boolean,
    challenge: ByteArray,
) = KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_WRAP_KEY)
    .setDigests(KeyProperties.DIGEST_SHA256)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
    .setBlockModes(KeyProperties.BLOCK_MODE_ECB).setIsStrongBoxBacked(isStrongBoxBacked)
    .setAttestationChallenge(challenge)
    .build()

@Throws(Exception::class)
fun wrapKey(
    publicKey: PublicKey?,
    keyMaterial: ByteArray?,
    keyFormat: Long,
    transformation: String,
    authorizationList: DERSequence
): ByteArray {
    return wrapKey(
        publicKey,
        keyMaterial,
        keyFormat,
        authorizationList,
        transformation,
        true,
    )
}

@Throws(Exception::class)
fun wrapKey(
    publicKey: PublicKey?,
    keyMaterial: ByteArray?,
    keyFormat: Long,
    authorizationList: DERSequence,
    transformation: String,
    correctWrappingRequired: Boolean, // this should be true, but let's keep this for testing purposes
): ByteArray {
    // Build description
    val descriptionItems: ASN1EncodableVector = ASN1EncodableVector()
    descriptionItems.add(ASN1Integer(keyFormat))
    descriptionItems.add(authorizationList)
    val wrappedKeyDescription: DERSequence = DERSequence(descriptionItems)

    // Generate 12 byte initialization vector
    val iv = ByteArray(12)
    // TODO: use this for production code: SecureRandom.getInstanceStrong().nextBytes(iv)
    Random.nextBytes(iv)
    // Generate 256 bit AES key. This is the ephemeral key used to encrypt the secure key.
    val aesKeyBytes = ByteArray(32)
    // TODO: use this for production code: SecureRandom.getInstanceStrong().nextBytes(aesKeyBytes)
    Random.nextBytes(aesKeyBytes)

    // Encrypt ephemeral keys
    val spec = OAEPParameterSpec(
        "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT
    )
    val pkCipher = Cipher.getInstance(transformation)
    if (correctWrappingRequired) {
        pkCipher.init(Cipher.ENCRYPT_MODE, publicKey, spec)
    } else {
        // Use incorrect OAEPParameters while initializing cipher. By default, main digest and
        // MGF1 digest are SHA-1 here.
        pkCipher.init(Cipher.ENCRYPT_MODE, publicKey)
    }
    val encryptedEphemeralKeys = pkCipher.doFinal(aesKeyBytes)

    // Encrypt secure key
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    val secretKeySpec = SecretKeySpec(aesKeyBytes, "AES")
    val gcmParameterSpec = GCMParameterSpec(GCM_TAG_SIZE, iv)
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec)
    val aad: ByteArray = wrappedKeyDescription.getEncoded()
    cipher.updateAAD(aad)
    var encryptedSecureKey = cipher.doFinal(keyMaterial)
    // Get GCM tag. Java puts the tag at the end of the ciphertext data :(
    val len = encryptedSecureKey.size
    val tagSize: Int = (GCM_TAG_SIZE / 8)
    val tag = Arrays.copyOfRange(encryptedSecureKey, len - tagSize, len)
    // Remove GCM tag from end of output
    encryptedSecureKey = Arrays.copyOfRange(encryptedSecureKey, 0, len - tagSize)

    // Build ASN.1 DER encoded sequence WrappedKeyWrapper
    val items: ASN1EncodableVector = ASN1EncodableVector()
    items.add(ASN1Integer(WRAPPED_FORMAT_VERSION))
    items.add(DEROctetString(encryptedEphemeralKeys))
    items.add(DEROctetString(iv))
    items.add(wrappedKeyDescription)
    items.add(DEROctetString(encryptedSecureKey))
    items.add(DEROctetString(tag))
    return DERSequence(items).getEncoded(ASN1Encoding.DER)
}


private fun removeTagType(tag: Int): Int {
    val kmTagTypeMask = 0x0FFFFFFF
    return tag and kmTagTypeMask
}

private fun wrappedKeyAuthorizationList(size: Int): DERSequence {
    // https://source.android.com/docs/security/features/keystore/tags?hl=de
    val algorithmRsaIdentifier = 1L
    val paddingNoneIdentifier = 1L

    val allPurposes = ASN1EncodableVector()
    allPurposes.add(ASN1Integer(KeyProperties.PURPOSE_SIGN.toLong()))
    val purposeSet: DERSet = DERSet(allPurposes)
    val purpose = DERTaggedObject(true, removeTagType(KM_TAG_PURPOSE), purposeSet)
    val algorithm = DERTaggedObject(
        true,
        removeTagType(KM_TAG_ALGORITHM),
        // see https://source.android.com/docs/security/features/keystore/tags?hl=de
        ASN1Integer(algorithmRsaIdentifier),
    )
    val keySize =
        DERTaggedObject(true, removeTagType(KM_TAG_KEY_SIZE), ASN1Integer(size.toLong()))
    val allDigests: ASN1EncodableVector = ASN1EncodableVector()
    val digestSet: DERSet = DERSet(allDigests)
    val digest = DERTaggedObject(true, removeTagType(KM_TAG_DIGEST), digestSet)
    val allPaddings: ASN1EncodableVector = ASN1EncodableVector()
    allPaddings.add(ASN1Integer(paddingNoneIdentifier)) // see https://source.android.com/docs/security/features/keystore/tags?hl=de
    val paddingSet: DERSet = DERSet(allPaddings)
    val padding = DERTaggedObject(true, removeTagType(KM_TAG_PADDING), paddingSet)
    val noAuthRequired = // TODO: add user authentication
        DERTaggedObject(true, removeTagType(KM_TAG_NO_AUTH_REQUIRED), DERNull.INSTANCE)
    // Build sequence
    val allItems: ASN1EncodableVector = ASN1EncodableVector()
    allItems.add(purpose)
    allItems.add(algorithm)
    allItems.add(keySize)
    allItems.add(digest)
    allItems.add(padding)
    allItems.add(noAuthRequired)
    return DERSequence(allItems)
}

Relevant dependencies

// https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on
implementation("org.bouncycastle:bcprov-jdk18on:1.78.1")

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật