Generate and Export RSA Key Pair on Android - android

I'm attempting to generate a KeyPair on Android and export the Public Key as a String so it's in the format -
-----BEGIN RSA PUBLIC KEY-----MIIB...
I've used the following to successfully generate the KeyPair -
fun generateKeyPair(): KeyPair {
val generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA)
generator.initialize(2048, SecureRandom())
val keypair = generator.genKeyPair()
return keypair
}
val keypair = generateKeyPair()
Log.d("Keypair", keypair.public.toString())
But this gives me -
OpenSSLRSAPublicKey{modulus=e0a6a5a...
Does anyone know how I can export the key in the aforementioned format?

For anyone else who comes across this the answer is -
val keyText = Base64.encodeToString(keypair.public.encoded, Base64.DEFAULT)

Related

Android KeyPairGenerator with RSA algorithm freeze UI

I know that my question is a duplicate of an existing one, but that question still doesn't have an answer. At the moment I have the following code to generate a key pair.
keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, ANDROID_KEYSTORE)
private fun createKeyPair() {
keyPairGenerator.initialize(
KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT)
.setKeySize(4096)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.build()
)
keyPairGenerator.generateKeyPair()
}
I chose the RSA algorithm because I need asymmetric encryption (encryption with public key
and decryption using biometrics). However, the key generation freezes ui for a few seconds. I have a progress bar and it freezes when the key pair is generated.
I've tried putting the generation code on a background thread, but that doesn't work and still freeze. I don't understand what I'm doing wrong, because I see similar logic in many apps, but I didn't notice freezes there.
Perhaps I need to use a different encryption algorithm? But the documentation does not say that there are any problems with the RSA algorithm and I don't understand how to rewrite this code and encryption code using ECC algorithm as advised in mentioned answer
here is my encryption code:
private const val ALGORITHM = "RSA"
private var cipher = Cipher.getInstance(TRANSFORMATION)
private fun initCipherForEncrypt() {
if (!keyStoreInstance.containsAlias(KEY_ALIAS)) {
keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, ANDROID_KEYSTORE)
createKeyPair()
}
val key: PublicKey = keyStoreInstance.getCertificate(KEY_ALIAS).publicKey
val unrestricted: PublicKey = KeyFactory.getInstance(key.algorithm).generatePublic(X509EncodedKeySpec(key.encoded))
val spec = OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT)
cipher.init(Cipher.ENCRYPT_MODE, unrestricted, spec)
}
fun encrypt(token: String): ByteArray {
initCipherForEncrypt()
return cipher.doFinal(token.toByteArray())
}

The SHA-256 digest not supported for MGF1 on Android using RSA OAEP decryption?

I'm working with a protocol that uses an algorithm which creates RSA/ECB/OAEPWithSHA-256AndMGF1Padding encrypted messages. For example, this Apple algorithm.
I have the following code
class RsaOaep {
private val cipher = Cipher.getInstance(transformation)
companion object {
// Results in "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
private const val transformation = "$KEY_ALGORITHM_RSA/$BLOCK_MODE_ECB/$ENCRYPTION_PADDING_RSA_OAEP"
// Note that MGF1ParameterSpec.SHA256 is not supported on Android!!!
private val spec = OAEPParameterSpec("SHA-256","MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT)
}
fun wrap(publicKey: Key, data: ByteArray): ByteArray {
val secretKey = SecretKeyGenerator().generateSecretKey()
cipher.init(Cipher.WRAP_MODE, publicKey, spec)
return cipher.wrap(secretKey)
}
fun decrypt(wrappedKey: ByteArray, privateKey: Key): ByteArray {
cipher.init(Cipher.UNWRAP_MODE, privateKey, spec)
return cipher.unwrap(wrappedKey, SecretKeyGenerator.KEY_ALGORITHM, Cipher.SECRET_KEY)
}
}
But my Android application can't decrypt them. I'm getting the following error.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example, PID: 9813
java.security.InvalidAlgorithmParameterException: Unsupported MGF1 digest: SHA-256. Only SHA-1 supported
at android.security.keystore.AndroidKeyStoreRSACipherSpi$OAEPWithMGF1Padding.initAlgorithmSpecificParameters(AndroidKeyStoreRSACipherSpi.java:228)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:147)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2980)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2891)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2796)
at javax.crypto.Cipher.chooseProvider(Cipher.java:773)
at javax.crypto.Cipher.init(Cipher.java:1288)
at javax.crypto.Cipher.init(Cipher.java:1223)
at com.example.RsaOaep.decrypt(RsaOaep.kt:21)
This limitation is hardcoded into the Android source code.
Why is that the case?
When will it change? ... and
How can I work around it?

How can I generate PrivateKey from NIST P-256 string private key In android

I have private key in string format. It's type is NIST P-256 and it looks like this:
-----BEGIN PRIVATE KEY-----
MIGH...
HQ9+...
ZYrJ...
-----END PRIVATE KEY-----
I removed header with footer and spaces.
I have:
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.spec.PKCS8EncodedKeySpec
val privateKeyString = "myKey"
val sigBytes: ByteArray = Base64Utils.decode(privateKeyString)
val privateKeySpec = PKCS8EncodedKeySpec(sigBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val privateKey : PrivateKey = keyFactory.generatePrivate(privateKeySpec)
Where Im getting:
Caused by:
java.security.spec.InvalidKeySpecException: Unexpected key type
I saw an approach where people removed
-----BEGIN PRIVATE KEY----- and
-----END PRIVATE KEY-----
But It doesn't help.
Probably I should use something different from PKCS8EncodedKeySpec
But Im not sure, pls someone help me)
The private key to be imported is a PEM encoded key in PKCS#8 format. PKCS8EncodedKeySpec() expects a DER encoded key which is derived from the PEM encoded key by removing header, footer and line breaks and Base64 decoding the rest.
Since an EC key is to be imported, EC must be specified as algorithm instead of RSA:
val keyFactory = KeyFactory.getInstance("EC")

Need basic encryption understanding from existing Android code

I am new in Android and I have started to working on existing project which have some encryption algorithm,
Below is existing code
var secureRandom = SecureRandom()
var masterKey = ByteArray(32)
secureRandom.nextBytes(masterKey)
var keyGen = KeyPairGenerator.getInstance("RSA")
keyGen.initialize(2048)
var keyPair = keyGen.generateKeyPair()
var pubKey = keyPair.public
var subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(pubKey.encoded))
var pubKeyEncoded = Base64.encodeToString(subjectPublicKeyInfo.parsePublicKey().encoded, Base64.DEFAULT)
var sb = StringBuilder()
sb.append("-----BEGIN RSA PUBLIC KEY-----\n")
sb.append(pubKeyEncoded)
sb.append("\n-----END RSA PUBLIC KEY-----\n")
val publicKey = sb.toString()
val privateKey = keyPair.private as RSAPrivateKey
val string = StringWriter()
var writer = PemWriter(string)
writer.writeObject(privateKey)//<-----Getting an error like Type Mismatch
writer.close()
Can anyone help me how to resolve this issue, I am totally new in this encryption fields, anyone suggest me from where I can get deeper understanding on cryptography,
from above code I have just understand that we are getting two keys like public key and private key for AES
We are encrypting public key by below code
var subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(pubKey.encoded))
var pubKeyEncoded = Base64.encodeToString(subjectPublicKeyInfo.parsePublicKey().encoded, Base64.DEFAULT)
But I still did not understand what will be use of SubjectPublicKeyInfo, ASN1Sequence, PemWriter if anyone have knowledge then please explain me.
I am also getting compile time error like Required PemObjectGenerator! found RSAPrivateKey
I can not help you in more detail but one thing i have found that
PEMWriter is already deprecated. You are not getting this message because you are importing PEMWritter from different package like util package
Instead import it from
org.spongycastle.openssl.PEMWriter
at that time you will get warning like it is deprecated so instead of use below
JcaPEMWriter
it is by using following package
org.spngycastle.openssl.jcajce.JcaPEMWriter
your error will be gone

different code's behavior in unit test and in runtime

The same code works in runtime and doesnt work in test
There is such code
private fun generatePrivateKeyFromText(key: String): Key {
val kf = KeyFactory.getInstance("RSA")
val keySpecPKCS8 = PKCS8EncodedKeySpec(Base64.decodeBase64(key))
return kf.generatePrivate(keySpecPKCS8)
}
When I run or debug app it works ok, but this code fails on generatePrivate while testing
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
#Test
fun decrypt() {
val encrypt = "MoRxCpLJNqxfXGVeU73zZFi+X2j2TLUTyIn1XRqCoEfeN8rNBR/YrEtumAz+8/0AaEsvx0+qTilfbw+edZd8Wfum4McWQ8oWXifvWLgoXybhxWUmCdi2fwA9Gw0parY6CSNYUDA2UuLrLLaDGMz/Jj4s4XmXKp5zuec1zXVdrPM="
val prkey = "MIICXAIBAAKBgQCAaTCQOxAZPfTXfgel2MMPUCLAf32TxfsXu71/c3mVFGtDPB+7IpmxCmEvAv6nlq1ymX1zRR5gIzNt4DZoM0RhE58eoksUXcmFcRnMX5V4bnI8DitHLdB2EZzdvnPX0Umzs+tE7I1ouDIocNQRsEoQeDcNPfz5av2zMPsg0Xl/yQIDAQABAoGAV8UOX5cvQsGZZ+2J1q8ZbI8OodrCf83z+V3mgYXxVZe2VSd0XNmiiWMZ2CNI4k3YUhtdpvtYbsfAsFpvdbuNAXMW82Zwsd0oluPzzoLELGkFvaUJlh2YGmizrBnEwvF0usJYwjsjUbXw3o1xKX8ILk5FBfdr2+L65YIIZ0UhqoECQQD/B0P8iZhoOTx7myhwlFCuVeSadwaOMGy2CgXRLvTFAtstz8YVO+D+yPKsEpAvMlFgEnkTt7tl8DRxMpKnfmO5AkEAgOZudjUD72xWfSJpCEcX76WGSASWE+fLCZ8C/HumIZ+0trW5/bsmBrI/6SldEJcy4b2bHh2nOggC/6R5rEUkkQJAAg779IDj0wuLOnAxLl90G0QkOT72tZUce4evLlYTsbdpL4B619cI5OWYV906frcIQx9DDO6xu4vp0HQZDPMPOQJAOVbH8ntY2ctmmdmRwWXmpusJ1cV8gTROJGSArpHOcAycFd628sCqhLYMKgsFZBjuQG7YrsfgGLdxpgijO1eykQJBAOE8+BrQwFWyOcgnUShPHo8mDOBkeplGr9VZdnWktac2aBr1Biovy+pipUyjSCAaFgOsSU0FDcK0I5ulTOpgMRg="
val decrypt = CryptoService.decrypt(encrypt, prkey)
assertEquals("Pika-pika", decrypt)
}
fun decrypt(ciphertext: String, key: String): String {
var decodedBytes: ByteArray? = null
try {
val c = Cipher.getInstance("RSA")
c.init(Cipher.DECRYPT_MODE, generatePrivateKeyFromText(key))
decodedBytes = c.doFinal(Base64.decodeBase64(ciphertext))
} catch (e: Exception) {
Log.e("Crypto", "RSA decryption error: $e")
}
return String(decodedBytes ?: ByteArray(0))
}
Working function is in Fragment
private fun testCrypto() {
val encrypt = "MoRxCpLJNqxfXGVeU73zZFi+X2j2TLUTyIn1XRqCoEfeN8rNBR/YrEtumAz+8/0AaEsvx0+qTilfbw+edZd8Wfum4McWQ8oWXifvWLgoXybhxWUmCdi2fwA9Gw0parY6CSNYUDA2UuLrLLaDGMz/Jj4s4XmXKp5zuec1zXVdrPM="
val prkey = "MIICXAIBAAKBgQCAaTCQOxAZPfTXfgel2MMPUCLAf32TxfsXu71/c3mVFGtDPB+7IpmxCmEvAv6nlq1ymX1zRR5gIzNt4DZoM0RhE58eoksUXcmFcRnMX5V4bnI8DitHLdB2EZzdvnPX0Umzs+tE7I1ouDIocNQRsEoQeDcNPfz5av2zMPsg0Xl/yQIDAQABAoGAV8UOX5cvQsGZZ+2J1q8ZbI8OodrCf83z+V3mgYXxVZe2VSd0XNmiiWMZ2CNI4k3YUhtdpvtYbsfAsFpvdbuNAXMW82Zwsd0oluPzzoLELGkFvaUJlh2YGmizrBnEwvF0usJYwjsjUbXw3o1xKX8ILk5FBfdr2+L65YIIZ0UhqoECQQD/B0P8iZhoOTx7myhwlFCuVeSadwaOMGy2CgXRLvTFAtstz8YVO+D+yPKsEpAvMlFgEnkTt7tl8DRxMpKnfmO5AkEAgOZudjUD72xWfSJpCEcX76WGSASWE+fLCZ8C/HumIZ+0trW5/bsmBrI/6SldEJcy4b2bHh2nOggC/6R5rEUkkQJAAg779IDj0wuLOnAxLl90G0QkOT72tZUce4evLlYTsbdpL4B619cI5OWYV906frcIQx9DDO6xu4vp0HQZDPMPOQJAOVbH8ntY2ctmmdmRwWXmpusJ1cV8gTROJGSArpHOcAycFd628sCqhLYMKgsFZBjuQG7YrsfgGLdxpgijO1eykQJBAOE8+BrQwFWyOcgnUShPHo8mDOBkeplGr9VZdnWktac2aBr1Biovy+pipUyjSCAaFgOsSU0FDcK0I5ulTOpgMRg="
val decrypt = CryptoService.decrypt(encrypt, prkey)
println(decrypt) // "Pika-pika"
}
I call it on onViewCreated
Updated:
I added BC provider (thanks, #JamesKPolk)
private fun generatePrivateKeyFromText(key: String): Key {
Security.addProvider(BouncyCastleProvider())
val kf = KeyFactory.getInstance(algorithm)
val keySpecPKCS8 = PKCS8EncodedKeySpec(Base64.decodeBase64(key))
return kf.generatePrivate(keySpecPKCS8)
}
But it is still ok in runtime and not while testing
javax.crypto.BadPaddingException: Decryption error
So problem for different running code didnt go.
What the difference between runtime and test which crashes code?
The issue is that the private key is not a PKCS8EncodedKeySpec, but rather an RSAPrivateKey object from PKCS#1. The BC provider, however, will still decode this mistake without complaint. However, other providers will rightfully complain. My guess is that the runtime is using an older version of Android where the default provider is BC, but your test is using a newer version where that isn't the case.
The fix is to make your private key a proper PKCS8EncodedKeySpec. Alternatively, you can explicitly request the "BC" provider. To do so, you need to specify "BC" in the getInstance() call: val keyFactory = KeyFactory.getInstance("RSA", "BC")
However, note that it appears that BC provider support is on its way out.
To convert a private key in the PKCS#1 format, either wrap a 'BEGIN RSA PRIVATE KEY'-style header and footer around the base64 blob or decode the base64 blob and place that in a file, then run:
openssl pkcs8 -topk8 -in privkey.pem -outform der -nocrypt | openssl base64 -A
or
openssl pkcs8 -topk8 -in privkey.der -inform der -nocrypt | openssl base64 -A
A second issue comes from relying on defaults. Instead of doing
val c = Cipher.getInstance("RSA")
which gets you defaults for mode and padding and thus is non-portable, always specify the full "algorithm/mode/padding" transformation string to Cipher.getInstance(). In you case, it appears the data is not padded (an insecure mode) you'd need something like
val c = Cipher.getInstance("RSA/ECB/NoPadding")
However, you really should use proper randomized padding, and currently that is OAEP padding.
Summary
The runtime environment is Android, but I think the test environment is Oracle Java (or maybe openjdk). There are evidently two critical differences in those environments:
Android uses the BC provider for KeyFactory which will handle private keys encoded in PKCS#1 RSAPrivateKey format. Oracle Java only supports PKCS8 encoded keys.
In Android, Cipher.getInstance("RSA") defaults to Cipher.getInstance("RSA/ECB/NoPadding"), but Oracle Java defaults to Cipher.getInstance("RSA/ECB/PKCS1Padding")

Categories

Resources