How to create sign parameter value in UPI DeepLink URI - android

The Deep Link URI string look like
upi://pay?pa={payee_address}&pn={payee_name}&am={amount}&cu={currency_code}&tn={transaction_note}&sign={sign_key}

You can create a method to generate this UPI
private fun getUPIString(
payeeAddress: String,
payeeName: String,
payeeAmount: String,
currencyCode: String,
transactionNote: String,
signKey: String,
): String {
val upi =
"upi://pay?&pa=$payeeAddress&pn=$payeeName&am=$payeeAmount&cu=$currencyCode&tn=$transactionNote&sign=$signKey"
return upi.replace(" ", "+")
}
Then you do a simple Intent to create the chooser
val intent = Intent()
intent.action = Intent.ACTION_VIEW
intent.data = Uri.parse(upi)
val chooser = Intent.createChooser(intent, "Pay with...")
startActivityForResult(chooser, 1, null)
Yo create the sign key you can use :
private fun generateSignKey() {
val secureRandom = SecureRandom()
val keyPairGenerator = KeyPairGenerator("RSA")
keyPairGenerator.initialize(2048, secureRandom)
val keyPair = keyPairGenerator.generateKeyPair()
}
Now the keyPair contains the private key and the public key, so you need to use the private key for signing the UPI transaction (signKey) payload and the public key for encrypting the payload and also for verifying the signature, make sure you store somewhere secure the private key encrypted.

Related

Storing encrypted passwords using Room Database and Keystore System

I'm developing an offline auth process for users to access their accounts. My idea was to store their hashed & encrypted email and passwords and let them introduce their email and password to auth , i'm using a Room database to store their credentials . The problem is when i try to decrypt their passwords and check if those match the decryption function throws an exception (-1) ,(I'm using the Keystore Sistem , Cipher , tha salt changes constantly with AES-256 algorithm). Any other suggestion to make an offline auth?
fun encryptText(bytes:ByteArray, outputStream: OutputStream ):ByteArray{
val encryptedBytes = encryptCipher.doFinal(bytes)
outputStream.use {
it.write(encryptCipher.iv.size)
it.write(encryptCipher.iv)
it.write(encryptedBytes.size)
it.write(encryptedBytes)
}
return encryptedBytes
}
fun decryptText(inputStream:InputStream):ByteArray{
Log.d("Test" ,"input decryptor = " +inputStream.readBytes().decodeToString())
return inputStream.use {
val ivSize = inputStream.read()
val iv = ByteArray(ivSize)
it.read(iv)
val encryptedBytesSize = inputStream.read()
val encryptedBytes = ByteArray(encryptedBytesSize)
it.read(encryptedBytes)
getDecryptCipherForIv(iv).doFinal(encryptedBytes)
}
}
#Entity(tableName = "account_credentials")
data class AccountCredentials(#PrimaryKey
val uuid:String,
val firebaseId:String?,
#ColumnInfo(name = "password_bytes",typeAffinity = ColumnInfo.BLOB) val hashedPassword:ByteArray,
#ColumnInfo(name = "email_bytes",typeAffinity = ColumnInfo.BLOB) val email:ByteArray
)
private fun encryptCredentials(credentials: AccountCredentials):AccountCredentials{
val passwordStream = ByteArrayOutputStream()
val emailStream = ByteArrayOutputStream()
CryptographicManager().encryptText(credentials.email , emailStream)
CryptographicManager().encryptText(credentials.hashedPassword , passwordStream)
return credentials.copy(hashedPassword = passwordStream.toByteArray() , email =emailStream.toByteArray() )
}
I have tried to change the data class field to string instead of ByteArray but the same result .

Find a value in Kotlin Data Class

i'm actually new to Kotlin android development. I'm making an app that uses Google sheets as a database. My app can successfully run after Google sign in. In fact I want my user to sign in to app, if their email ID is present in the Emails sheet in the Google sheet. So I have done following steps in my code so far.
Sign in user with Google Sign In
Retrieve data from "Emails" sheet in my Google Spreadsheet
Then store them in to a data class (Customers data class in Shipment.kt)
Then I check whether signed in user's email ID available in the data class or not. This is where I need help. It's giving me this error
"Type inference failed. The value of the type parameter T should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly."
Can someone help me on this?
Below is my data class in Shipment.kt
package com.example.demoonlinesheet
data class Shipments(
val shipperName:String,
val volume:String,
val eta:String,
val etd:String)
data class Customers(
val companyName:String,
val customerName:String,
val emailID:String)
Below is the code that I have written so far in MainActivity.kt
const val RC_SIGN_IN = 123
const val EXTRA_MESSAGE = "com.example.demoonlinesheet.MESSAGE"
const val EXTRA_MESSAGE2 = "com.example.demoonlinesheet.MESSAGE"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Configure sign-in to request the user's ID, email address, and basic
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build()
// Build a GoogleSignInClient with the options specified by gso.
var mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
sign_in_button.setOnClickListener{
val signInIntent = mGoogleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
val acct = GoogleSignIn.getLastSignedInAccount(this)
if (acct != null) {
startActivity(Intent(this, SecondActivity::class.java).apply{
putExtra(EXTRA_MESSAGE, acct.displayName)
//putExtra(EXTRA_MESSAGE2, acct.email)
} )
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
// The Task returned from this call is always completed, no need to attach
// a listener.
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
handleSignInResult(task)
}
}
private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
val companyList= arrayListOf<Customers>()
val url="https://sheets.googleapis.com/v4/spreadsheets/{sheetID}/values/{sheetName}?alt=json&key={APIKey}"
val queue = Volley.newRequestQueue(this)
val jsonObjectRequest = JsonObjectRequest(Request.Method.GET, url, null,
{
fun onResponse(response: JSONObject) {
try {
// val feedObj = response.getJSONObject("")
val entryArray = response.getJSONArray("values")
for (i in 2 until entryArray.length()) {
val entryObj = entryArray.getJSONArray(i)
val companyName = entryObj[0].toString()
val customerName = entryObj[1].toString()
val emailID = entryObj[2].toString()
// entryObj.getJSONObject("gsx\$lastname").getString("\$t")
companyList.add(Customers(companyName, customerName, emailID))
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
}, {
fun onErrorResponse(error: VolleyError?) {
Toast.makeText(this#MainActivity, "Fail to get data..", Toast.LENGTH_SHORT)
.show()
}
})
queue.add(jsonObjectRequest)
fun checkUser() {
val getAccount = completedTask.getResult(ApiException::class.java)
val emailLoggedIn = getAccount.email.toString()
val companies = listOf<Customers>()
//this is where I get the error message "Type inference failed. The value of the type parameter T should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly."
if (emailLoggedIn in companies){
//do something here
}
try {
val account = completedTask.getResult(ApiException::class.java)
val loggedname = account.displayName
//startActivity(Intent(this#MainActivity, SecondActivity::class.java))
val intent = Intent(this, SecondActivity::class.java).apply {
putExtra(EXTRA_MESSAGE, loggedname)
}
startActivity(intent)
} catch (e: ApiException) {
}
}
}
From your code:
if (emailLoggedIn in companies) {
//do something here
}
emailLoggedIn is a String, companies is a List<Customers>.
How does List know how to compare String and Customers? :)
You need something like this:
if (companies.any { it.emailID == emailLoggedIn }){
//do something here
}
I should also mention that you can leave the if condition unchanged and add the following code that overloads the in keyword:
operator fun List<Customers>.contains(email: String): Boolean {
return this.any { it.emailID == email }
}
But, in my opinion, this overload looks terrible and confusing :)

How to Sign a String using RSA Private Key Android Kotlin

We are facing issue on Signing String using Private RSA Key
val key:'-----BEGIN RSA PRIVATE KEY-----ASDSADSAADSDASA-----END RSA PRIVATE KEY-----';
val data:'DATA TO BE SIGNED';
val privateKeyPEM = key
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
.replace(System.lineSeparator().toRegex(), "")
.replace("-----END RSA PRIVATE KEY-----", "")
val encoded: ByteArray = Base64.getDecoder().decode(privateKeyPEM)
val spec = PKCS8EncodedKeySpec(encoded)
val kf = KeyFactory.getInstance("RSA")
val privateKey: RSAPrivateKey = kf.generatePrivate(spec) as RSAPrivateKey
val signature: Signature = Signature.getInstance("SHA256withRSA")
signature.initSign(privateKey)
signature.update(data)
val signedString: signature.sign()
So this above code of Kotlin is not generating the correct sign as the sample given by payment integrator company as given below in Javascript
'calculateSignature': function (data, timestamp, nonce) {
//console.log('data', data);
//console.log('timestamp =', timestamp);
//console.log('nonce =', nonce);
var dataToSign = data + timestamp + nonce;
console.log('dataToSign', dataToSign);
var clientPrivateKey = pm.environment.get('clientPrivateKey');
hashAlgorithm = pm.environment.get('hashAlgorithm');
console.log('clientPrivateKey =', clientPrivateKey);
console.log('hashAlgorithm =', hashAlgorithm);
var rsa = new RSAKey();
rsa.readPrivateKeyFromPEMString(clientPrivateKey);
var signature = rsa.sign(dataToSign, hashAlgorithm);
console.log('signature =', signature);
return signature;
},
Can anyone please suggest me where we are doing wrong step. any help or suggestion would be appreciated

KeyStoreException: Signature/MAC verification failed when trying to decrypt

I am trying to create a simple Kotlin object that wraps access to the app's shared preferences by encrypting content before saving it.
Encrypting seems to work OK but when I try to decrypt, I get an javax.crypto.AEADBadTagException which stems from an android.security.KeyStoreException with a message of "Signature/MAC verification failed".
I have tried debugging to see what's the underlying issue but I can't find anything. No search has given me any clue. I seem to follow a few guides to the letter without success.
private val context: Context?
get() = this.application?.applicationContext
private var application: Application? = null
private val transformation = "AES/GCM/NoPadding"
private val androidKeyStore = "AndroidKeyStore"
private val ivPrefix = "_iv"
private val keyStore by lazy { this.createKeyStore() }
private fun createKeyStore(): KeyStore {
val keyStore = KeyStore.getInstance(this.androidKeyStore)
keyStore.load(null)
return keyStore
}
private fun createSecretKey(alias: String): SecretKey {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, this.androidKeyStore)
keyGenerator.init(
KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
)
return keyGenerator.generateKey()
}
private fun getSecretKey(alias: String): SecretKey {
return if (this.keyStore.containsAlias(alias)) {
(this.keyStore.getEntry(alias, null) as KeyStore.SecretKeyEntry).secretKey
} else {
this.createSecretKey(alias)
}
}
private fun removeSecretKey(alias: String) {
this.keyStore.deleteEntry(alias)
}
private fun encryptText(alias: String, textToEncrypt: String): String {
val cipher = Cipher.getInstance(this.transformation)
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(alias))
val ivString = Base64.encodeToString(cipher.iv, Base64.DEFAULT)
this.storeInSharedPrefs(alias + this.ivPrefix, ivString)
val byteArray = cipher.doFinal(textToEncrypt.toByteArray(charset("UTF-8")))
return String(byteArray)
}
private fun decryptText(alias: String, textToDecrypt: String): String? {
val ivString = this.retrieveFromSharedPrefs(alias + this.ivPrefix) ?: return null
val iv = Base64.decode(ivString, Base64.DEFAULT)
val spec = GCMParameterSpec(iv.count() * 8, iv)
val cipher = Cipher.getInstance(this.transformation)
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(alias), spec)
try {
val byteArray = cipher.doFinal(textToDecrypt.toByteArray(charset("UTF-8")))
return String(byteArray)
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
private fun storeInSharedPrefs(key: String, value: String) {
this.context?.let {
PreferenceManager.getDefaultSharedPreferences(it).edit()?.putString(key, value)?.apply()
}
}
private fun retrieveFromSharedPrefs(key: String): String? {
val validContext = this.context ?: return null
return PreferenceManager.getDefaultSharedPreferences(validContext).getString(key, null)
}
Can anyone point me in the right direction ?
I had similar issue. It was all about android:allowBackup="true".
Issue
This issue will occur while uninstalling the app and then re-installing it again. KeyStore will get cleared on uninstall but the preferences not getting removed, so will end up trying to decrypt with a new key thus exception thrown.
Solution
Try disabling android:allowBackup as follows:
<application android:allowBackup="false" ... >
I encountered the same exception/issue 'android.security.KeyStoreException: Signature/MAC verification failed' on Cipher encryption 'AES/GCM/NoPadding'.
On my end, what helped to resolve this issue is to create a byte array holder first, with size that is obtained by calling Cipher.getOutputSize(int inputLen), then calling the doFinal overload Cipher.doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) to set the ciphertext in your byte array holder.
private var iv: ByteArray? = null
fun doEncryptionOperation() {
val keyStore = KeyStore.getInstance(PROVIDER_ANDROID_KEYSTORE).apply {
load(null)
}
// Assumption: key with alias 'secret_key' has already been stored
val entry = keyStore.getEntry("secret_key", null)
val secretKeyEntry = entry as KeyStore.SecretKeyEntry
val key secretKeyEntry.secretKey
val plainText = "Sample plain text"
val cipherText = encryptSymmetric(key, plainText.toByteArray())
val decrypted = decryptSymmetric(key, cipherText)
val decryptedStr = String(decrypted)
val same = decryptedStr == plainText
Log.d("SampleTag", "Is plaintext same from decrypted text? $same")
}
fun encryptSymmetric(key: SecretKey, plainText: ByteArray): ByteArray? {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, key)
iv = cipher.iv
val len = plainText.size
val outLen = cipher.getOutputSize(len) // get expected cipher output size
val result = ByteArray(outLen) // create byte array with outLen
cipher.doFinal(plainText, 0, len, result,0) // doFinal passing plaintext data and result array
return result
}
fun decryptSymmetric(key: SecretKey, cipherText: ByteArray): ByteArray? {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val tagLen = 128 // default GCM tag length
cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(tagLen,iv))
cipher.update(input.data)
val result = cipher.doFinal()
return result
}
Additionally, using AEAD, don't forget to call Cipher.updateAAD() in ENCRYPT_MODE, and set the same AEAD tag in the DECRYPT_MODE. Otherwise, you will encounter the same javax.crypto.AEADBadTagException.
When you change your authentiation tag length from iv.count() to 128 it will work.
I had a similar problem. I had an application where the admin and an ordinary user could log in and both of them had a remember me option. So, when the user previously pressed the remember me option, the program needs to fetch the encrypted password, decrypt it, and put it in the input field.
I was storing both encrypted passwords with their initialization vectors in the SharedPreferences file but when I was trying to decrypt them via Cipher (The secret key was stored in the AndroidKeyStore with the same alias for the secret key) it was decrypting one password but was giving me the same error as yours when I was decrypting another password.
Then, I used 2 different aliases for these 2 passwords when I was encrypting and decrypting them and the error is gone.
Github gist: Code example

How to construct private key from generated previously ECDSA both encoded key pair?

Having generated the private key like this:
fun getKeyPair(): Pair<ByteArray, ByteArray> {
Security.addProvider(provider)
val generator = KeyPairGenerator.getInstance("ECDSA")
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1")
generator.initialize(ecSpec)
val keyPair = generator.generateKeyPair()
val publicKey = keyPair.public as ECPublicKey
val privateKey = keyPair.private
return Pair(publicKey.q.getEncoded(true), privateKey.getEncoded())
}
The public key can be reconstructed again like this:
Security.addProvider(...spongy castle provider)
val ecSpecs = ECNamedCurveTable.getParameterSpec("secp256r1")
val q = ecSpecs.curve.decodePoint(publicKeyEncoded)
val pubSpec = ECPublicKeySpec(q, ecSpecs)
val keyFactory = KeyFactory.getInstance("ECDSA")
val generatedPublic = keyFactory.generatePublic(pubSpec)
How it is possible to reconstruct private key from bytes also along with this?
UPDATE:
This code works well in actual app but it doesnt in JUnit testing:
val keyFactory = KeyFactory.getInstance("ECDSA")
val privSpec = PKCS8EncodedKeySpec(privateEncoded)
val generatedPrivate = keyFactory.generatePrivate(privSpec)
In JUnit test I am getting this error:
java.security.spec.InvalidKeySpecException: encoded key spec not recognised
My private key as encoded bytes has 150 bytes size.
Since the key is encoded using the standard Key.getEncoded(), the following standard solution should work:
val keyFactory = KeyFactory.getInstance("EC")
val privSpec = PKCS8EncodedKeySpec(privateEncoded)
val generatedPrivate = keyFactory.generatePrivate(privSpec)
The encoded key should contain all the required information to rebuild the private key without specifying additional parameters like you need to do for the reduced public key.

Categories

Resources