I have this public key that is generated from Delphi 10.2 TurboPower LockBox 2 module:
MIGIAoGBALtEMVXxHBWzBx/AzO/aOHrYEQZB3VlqYBvqX/SHES7ehERXaCbUO5aEwyZcDrdh2dMTy/abNDaFJK4bEqghpC6yvCNvnTqjAz+bsD9UqS0w5CUh3KHwqhPv+HFGcF7rAuU9uoJcWXbTC9tUBEG7rdmdmMatIgL1Y4ebOACQHn1xAgIlKg==
This strang can be dumped by https://8gwifi.org/PemParserFunctions.jsp as:
Algo RSA
Format X.509
ASN1 Dump
RSA Public Key [60:df:88:a2:71:97:21:f2:45:88:94:6c:5a:63:1e:1e:8f:a0:50:a0]
modulus: bb443155f11c15b3071fc0ccefda387ad8110641dd596a601bea5ff487112ede8444576826d43b9684c3265c0eb761d9d313cbf69b34368524ae1b12a821a42eb2bc236f9d3aa3033f9bb03f54a92d30e42521dca1f0aa13eff87146705eeb02e53dba825c5976d30bdb540441bbadd99d98c6ad2202f563879b3800901e7d71
public exponent: 252a
And dumped by https://lapo.it/asn1js as:
SEQUENCE (2 elem)
Offset: 0
Length: 3+136
(constructed)
Value:
(2 elem)
INTEGER (1024 bit) 131502922565366914567306533865977926602809520527330321378421375088553…
INTEGER 9514
So, several applications recognized this as valid public RSA key (1024). This string can not be loaded with the Android Kotlin code:
val publicKeyBytes: ByteArray = Base64.decode(publicKeyString, Base64.DEFAULT)
val X509PublicKey: X509EncodedKeySpec = X509EncodedKeySpec(publicKeyBytes)
//val RSAPublicKey: RSAPublicKeySpec = RSAPublicKeySpec()
val kf: KeyFactory = KeyFactory.getInstance("RSA")
val publicKey: PublicKey = kf.generatePublic(X509PublicKey)
The error code is:
java.security.spec.InvalidKeySpecException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: Error parsing public key
at com.android.org.conscrypt.OpenSSLKey.getPublicKey(OpenSSLKey.java:305)
at com.android.org.conscrypt.OpenSSLRSAKeyFactory.engineGeneratePublic(OpenSSLRSAKeyFactory.java:55)
at java.security.KeyFactory.generatePublic(KeyFactory.java:361)
So - I can try to use RSAPublicKeySpec instead of X509EncodedKeySpec, but RSAPublicKeySpec expects 2 BigIntegers (exponent and modulus) as the constructor arguments.
My question is - how can I parse my public-key-string (see exmaple string and its decodings/dumps by 2 web services) into pair of BigIntegers to use for RSAPublicKeySpec?
To extract modulus and public exponent from the PKCS#1 key the ASN.1 parser of BouncyCastle can be used. The required classes are PemReader
and ASN1Sequence
.
From the ASN1Sequence
modulus and public exponet can be read. Both can then be used to create RSAPublicKeySpec
.
Example:
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DLSequence
import org.bouncycastle.util.io.pem.PemReader
import java.io.FileReader
import java.security.KeyFactory
import java.security.PublicKey
import java.security.spec.RSAPublicKeySpec
import javax.crypto.Cipher
import java.util.Base64
...
val inputFile: String = "<path to PKCS#1 PEM file>"
// Key import
var publicKey: PublicKey? = null
FileReader(inputFile).use { fileReader ->
PemReader(fileReader).use { pemReader ->
val seq: DLSequence = ASN1Sequence.fromByteArray(pemReader.readPemObject().content) as DLSequence
val modulus: ASN1Integer = seq.elementAt(0) as ASN1Integer
val pubExp: ASN1Integer = seq.elementAt(1) as ASN1Integer
val publicKeySpec = RSAPublicKeySpec(modulus.positiveValue, pubExp.positiveValue)
val factory: KeyFactory = KeyFactory.getInstance("RSA")
publicKey = factory.generatePublic(publicKeySpec)
}
}
// Encrypt
val cipher: Cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val ciphertext: ByteArray = cipher.doFinal("The quick brown fox jumps over the lazy dog".toByteArray())
val ciphertextB64: String = Base64.getEncoder().encodeToString(ciphertext);
println(ciphertextB64)
The code requires a PEM encoded public key in PKCS#1 format.
As already pointed out in this answer, the key is not OK (the public exponent is even). Nevertheless, extracting the required information works without error message.
However, with an invalid key, the result should generally not be considered reliable.