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())
}
Related
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?
Im investigating the use of com.google.crypto.tink:tink-android:1.6.1 in my current Android project.
The data I am encrypting includes the OAuth2 Access Token/Refresh Token I employ for my remote API calls, e.g. Access Token is my Bearer token for the Authorisation HTTP header.
Im concerned I have made an error in my encryption/decryption logic as I am experiencing an intermittent problem where I cannot Refresh the Token. The error from the server
{"error_description":"unknown, invalid, or expired refresh token","error":"invalid_grant"}
The refresh token cannot be expired as it lasts 24 hours.
My code that initialises Tink resembles this:-
private fun manageTink() {
try {
AeadConfig.register()
authenticatedEncryption = generateNewKeysetHandle().getPrimitive(Aead::class.java)
} catch (e: GeneralSecurityException) {
throw RuntimeException(e)
} catch (e: IOException) {
throw RuntimeException(e)
}
}
#Throws(IOException::class, GeneralSecurityException::class)
private fun generateNewKeysetHandle(): KeysetHandle =
AndroidKeysetManager
.Builder()
.withSharedPref(this, TINK_KEYSET_NAME, PREF_FILE_NAME)
.withKeyTemplate(KeyTemplates.get("AES256_GCM"))
.withMasterKeyUri(MASTER_KEY_URI)
.build()
.keysetHandle
Here is my code for encryption/decryption
import android.util.Base64
import com.google.crypto.tink.Aead
import javax.inject.Inject
const val BASE64_ENCODE_SETTINGS = Base64.NO_WRAP or Base64.NO_PADDING
data class Security #Inject constructor(private val authenticatedEncryption: Aead) {
fun conceal(plainText: String, associatedData: String): String {
val plain64 = Base64.encode(plainText.encodeToByteArray(), BASE64_ENCODE_SETTINGS)
val associated64 = Base64.encode(associatedData.encodeToByteArray(), BASE64_ENCODE_SETTINGS)
val encrypted: ByteArray? = authenticatedEncryption.encrypt(plain64, associated64)
return Base64.encodeToString(encrypted, BASE64_ENCODE_SETTINGS)
}
fun reveal(encrypted64: String, associatedData: String): String {
val encrypted = Base64.decode(encrypted64.encodeToByteArray(), BASE64_ENCODE_SETTINGS)
val associated64 = Base64.encode(associatedData.encodeToByteArray(), BASE64_ENCODE_SETTINGS)
val decrypted: ByteArray? = authenticatedEncryption.decrypt(encrypted, associated64)
return String(Base64.decode(decrypted, BASE64_ENCODE_SETTINGS))
}
}
Could the use of Base64 encode/decode be the issue?
Where is my mistake?
If Tink can decrypt your token, the issue should be with your token/the encoding and not with Tink (It is using authenticated encryption, so you are guaranteed that the bytes that you encrypted are going to be the bytes that you decrypt, or you get an error).
To also address the concern in the comment: Tink will happily encrypt and decrypt any byte strings (with the associated data also being allowed to be any byte string), and not be concerned with whether they are printable or even valid Unicode. You will have to base64 encode the output of Tink if you want to use it as a string, though, as it will be uniformly random distributed bytes, i.e. contain invalid Unicode and non printable characters with high probability.
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
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")
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)