I am trying to write a couple functions that can encrypt and decrypt text using AES/GCM encryption in conjunction with PBKDF2 key generation. I am transitioning my code from CTR (SIC) encryption, and the MAC check failure is killing me when all else is working.
fun encryptAESBasic(input: String, password: String): String {
val masterpw = getKey(password)
val random = SecureRandom()
val salt = ByteArray(16)
random.nextBytes(salt)
val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val spec: KeySpec = PBEKeySpec(masterpw.toString().toCharArray(), salt, 100, 256)
val tmp: SecretKey = factory.generateSecret(spec)
val cipher = Cipher.getInstance("AES/CTR/NoPadding")
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
cipher.init(Cipher.ENCRYPT_MODE, tmp, IvParameterSpec(iv))
val cipherText: ByteArray = cipher.doFinal(input.toByteArray(Charset.forName("UTF-8")))
val ivstring: String = Base64.encodeToString(iv, Base64.NO_WRAP)
val saltystring: String = Base64.encodeToString(salt, Base64.NO_WRAP)
val cipherstring: String = Base64.encodeToString(cipherText, Base64.NO_WRAP)
val returnstring: String = ivstring + "-" + saltystring + "-" + cipherstring
return returnstring
}
fun decryptAESBasic(text: String, password: String): String {
val arr = text.split("-")
val iv = Base64.decode(arr[0].toByteArray(Charset.forName("UTF-8")), Base64.NO_WRAP)
val salt = Base64.decode(arr[1].toByteArray(Charset.forName("UTF-8")), Base64.NO_WRAP)
val data = arr[2].toByteArray(Charset.forName("UTF-8"))
val masterpw = getKey(password)
val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val spec: KeySpec = PBEKeySpec(masterpw.toString().toCharArray(), salt, 100, 256)
val tmp: SecretKey = factory.generateSecret(spec)
val key: ByteArray = tmp.getEncoded()
val cipher = Cipher.getInstance("AES/CTR/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, tmp, IvParameterSpec(iv))
val credential: ByteArray = cipher.doFinal(Base64.decode(data, Base64.NO_WRAP))
return credential.toString(Charset.forName("UTF-8"))
}
fun getKey(masterPass: String): ByteArray {
return masterPass.padEnd(32, '.').toByteArray(Charset.forName("UTF-8"))
}
Again, this code works, but I would like to change it from CTR to GCM but every time that I do I am met with a "mac check in GCM failed" error. Any help explaining how/why this is happening would be tremendously appreciated.
E/AndroidRuntime( 6461): Caused by: javax.crypto.AEADBadTagException: mac check in GCM failed
E/AndroidRuntime( 6461): at java.lang.reflect.Constructor.newInstance0(Native Method)
E/AndroidRuntime( 6461): at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
E/AndroidRuntime( 6461): at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$AEADGenericBlockCipher.doFinal(BaseBlockCipher.java:1485)
E/AndroidRuntime( 6461): at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:1217)
E/AndroidRuntime( 6461): at javax.crypto.Cipher.doFinal(Cipher.java:2055)
E/AndroidRuntime( 6461): at design.codeux.autofill_service.FlutterMyAutofillServiceKt.decryptAESBasic(FlutterMyAutofillService.kt:1003)
Instead of using IvParameterSpec in cipher.init(), use GCMParameterSpec
Related
I am using an SES receipt rule to store KMS encrypted emails in S3 and looking at decrypting in a SpringBoot/Kotlin application. The only way I could achieve this using the AWS Java SDK v2 was decrypting myself using the BouncyCastle crypto lib. If I downgrade to AWS Java SDK v1 I can easily decrypt using AmazonS3EncryptionClient without worrying about the crypto operations but its deprecated.
What is the preferred way to read these KMS encrypted S3 objects from SES using the latest AWS Java SDK?
Below are my various attempts:
AWS SDKv1 - works perfectly but deprecated
val provider = KMSEncryptionMaterialsProvider(kmsArn)
val s3EncryptionClient = AmazonS3EncryptionClient.encryptionBuilder()
.withEncryptionMaterials(provider)
.withCryptoConfiguration(CryptoConfiguration()
.withCryptoMode(CryptoMode.AuthenticatedEncryption)
.withAwsKmsRegion(Region.getRegion(Regions.US_WEST_2)))
.build()
println(s3EncryptionClient.getObjectAsString(bucket, key))
AWS SDKv1 (V2) - fails on the EncryptionContext validation?
val provider = KMSEncryptionMaterialsProvider(kmsArn)
val s3EncryptionClient = AmazonS3EncryptionClientV2Builder.standard()
.withRegion(Regions.US_WEST_2)
.withCryptoConfiguration(CryptoConfigurationV2()
.withCryptoMode(CryptoMode.AuthenticatedEncryption)
.withAwsKmsRegion(com.amazonaws.regions.Region.getRegion(Regions.US_WEST_2)))
.withEncryptionMaterialsProvider(kmsProvider)
.build()
val result = s3EncryptionClient.getObject(bucket, key)
println(String(result.objectContent.readAllBytes()))
AWS SDKv2 + BouncyCastle - works perfectly but I have to do all the decryption myself
fun getAsDecryptedString(bucket: String, key: String): String {
Security.addProvider(BouncyCastleProvider())
val headObjectResponse = s3AsyncClient.headObject(
HeadObjectRequest.builder().bucket(bucket).key(key).build()
).get()
val metadata = headObjectResponse.metadata()
val kmsKeyBase64 = metadata["x-amz-key-v2"]
val iv = metadata["x-amz-iv"]
val algo = checkNotNull(metadata["x-amz-cek-alg"]) { "x-amz-cek-alg metadata is required" }
val encryptionContext = objectMapper.readValue(metadata["x-amz-matdesc"], typeRef)
val kmsKey = SdkBytes.fromByteArray(Base64.getDecoder().decode(kmsKeyBase64))
val kmsDecryptResult = kmsClient.decrypt(
DecryptRequest.builder()
.ciphertextBlob(kmsKey)
.encryptionContext(encryptionContext)
.build()
)
val request = GetObjectRequest.builder().bucket(bucket).key(key).build()
val getObjectResponse = s3AsyncClient.getObject(request, AsyncResponseTransformer.toBytes()).get()
val keyBytes = kmsDecryptResult.plaintext().asByteArray()
val bytesToDecrypt = getObjectResponse.asByteArray()
val ivBytes = Base64.getDecoder().decode(iv)
return decryptWithAES(keyBytes, bytesToDecrypt, ivBytes, algo)
}
private fun decryptWithAES(keyBytes: ByteArray, bytesToDecrypt: ByteArray, ivBytes: ByteArray, algo: String): String {
val secretKey = SecretKeySpec(keyBytes, "AES")
val iv = IvParameterSpec(ivBytes)
synchronized(Cipher::class.java) {
val cipher = Cipher.getInstance(algo, "BC")
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv)
val plainText = ByteArray(cipher.getOutputSize(bytesToDecrypt.size))
var ptLength = cipher.update(bytesToDecrypt, 0, bytesToDecrypt.size, plainText, 0)
ptLength += cipher.doFinal(plainText, ptLength)
val decryptedString = String(plainText)
return decryptedString.trim { it <= ' ' }
}
}
I am trying to encrypt and decrypt the image by AES in Kotlin
Key Generation Function-
override suspend fun key_genration(callback: Enc) {
val keygenerator = KeyGenerator.getInstance("AES")
keygenerator.init(128)
val secretKey: Key = keygenerator.generateKey()
val secretKey1 = secretKey.encoded
val result = Firebase.firestore.collection("profiles").document("YHMauLouORRtrYBV2h4dHJ5A0s72").update("key","$secretKey1")
}
Encryption Function -
override suspend fun encryption(imageView: ImageView, Key: ByteArray, name: String, uid: String) {
val bitmap = (imageView.drawable as BitmapDrawable).bitmap
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos) // bm is the bitmap object
val ba: ByteArray = baos.toByteArray()
val cipher = Cipher.getInstance("AES")
val keySpec = SecretKeySpec(Key, "AES")
Log.d("key11119","$keySpec")
cipher.init(Cipher.ENCRYPT_MODE, keySpec)
val txt = cipher.doFinal(ba)
Log.d("poott0","$txt")
val p = txt.toString(Charsets.ISO_8859_1)
Log.d("poott","$p")
// val encryptText = String(txt, Charset.defaultCharset())
val file = File.createTempFile("$name", ".txt")
file.writeText(p)
//val storageReference = FirebaseStorage.getInstance().getReference("image/$uid/$name")
val storageReference = FirebaseStorage.getInstance()
.getReference("/image/0XhL4jD4XCemk38rcRkIEjJMgjh2/Aadhar/")
storageReference.putFile(Uri.fromFile(file))
}
Decryption Function -
val img = view.findViewById<ImageView>(R.id.imageView2)
Firebase.firestore.collection("profiles").whereEqualTo("UID", "YHMauLouORRtrYBV2h4dHJ5A0s72").get()
.addOnSuccessListener {
val key = it.first().toObject(docretreving::class.java).key
Log.d("key111","$key")
Log.d("pooptt","Success")
val storageRef =
FirebaseStorage.getInstance().reference?.child("/image/0XhL4jD4XCemk38rcRkIEjJMgjh2/Aadhar/")
val localfile = File.createTempFile("temp",".txt")
storageRef.getFile(localfile)
.addOnSuccessListener {
Log.d("pooptt","Success")
val inputStream: InputStream = localfile.inputStream()
val inputString = inputStream.bufferedReader().use { it.readText() }
println(inputString)
Log.d("pooptt", inputString)
val key1 = "mxkLZmSFE1aKWzr6JyybjQ==".toByteArray()
Log.d("key111","$key1")
val keySpec = SecretKeySpec(key1, "AES")
val isro= inputString.toByteArray(Charsets.ISO_8859_1)
val cipher = Cipher.getInstance("AES")
cipher.init(Cipher.DECRYPT_MODE,keySpec)
var decryptedText : ByteArray = cipher.doFinal(isro)
Log.d("key110","$decryptedText")
val baos = ObjectInputStream(ByteArrayInputStream(decryptedText))
val bitmap = baos.readObject() as Bitmap
img.setImageBitmap(bitmap)
}
}
It says invalid Block size in decryption function , I am not sure if I am doing this correct. The key generating function the key and gives to encryption function[For now I was just testing with 1 key converted to base64 ]
Thanks in Advance
You should not put the ciphertext through a buffered reader, which handles text and may change binary data. Files and ciphertext both consist of bytes, you should treat them as such and not convert them to text. If you need text, use base 64.
What I wanted to do is call the Coinbase sandbox API to get all accounts for a profile.
I am following the official documentation https://docs.cloud.coinbase.com/exchange/reference/exchangerestapi_getaccounts
But keep getting this error
Client request(https://api-public.sandbox.exchange.coinbase.com/accounts) invalid: 401 Unauthorized. Text: "{"message":"invalid signature"}"
Am I missing something?
suspend fun getTradingAccounts(): String {
val timestamp = getTimeStamp()
val response: String = client.get("https://api-public.sandbox.exchange.coinbase.com/accounts") {
headers {
append("Accept", "application/json")
append("Content-Type", "application/json")
append(
"cb-access-key",
"MY_KEY..."
)
append("cb-access-passphrase", "MY_PASSPHRASE....")
append("cb-access-sign", signMessage(
timestamp = timestamp,
method = "GET",
path = "https://api-public.sandbox.exchange.coinbase.com/accounts"
))
append("cb-access-timestamp", timestamp)
}
}
return response
}
private fun getTimeStamp(): String {
val time = LocalDateTime.now()
val zoneId = ZoneId.of("Europe/London")
val epoch = time.atZone(zoneId).toEpochSecond()
return epoch.toString()
}
#Throws(NoSuchAlgorithmException::class, InvalidKeyException::class)
private fun signMessage(timestamp: String, method: String, path: String): String {
val prehash = timestamp + method + path
val sha256_HMAC = Mac.getInstance("HmacSHA256")
val secretDecoded: ByteArray = Base64.getDecoder().decode("MY_API_KEY==")
val secret_key = SecretKeySpec(secretDecoded, "HmacSHA256")
sha256_HMAC.init(secret_key)
return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(prehash.toByteArray()))
}
As I see your code for signing messages doesn't use request body JSON string when concatenating components for a message. Here is my version for the function that returns the same string as in NodeJS script for the same inputs:
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
fun main() {
val result = sign(
secret = "TVlfQVBJX0tFWQ==",
timestampMs = System.currentTimeMillis(),
method = "POST",
requestPath = "/orders",
request = Req(
price = "1.0",
size = "1.0",
side = "buy",
product_id = "BTC-USD",
)
)
println(result)
}
#OptIn(ExperimentalSerializationApi::class)
fun sign(timestampMs: Long, method: String, requestPath: String, request: Req, secret: String): String {
val accessTimestamp = timestampMs / 1000.0
val message = "%.3f".format(accessTimestamp) + method + requestPath + Json.encodeToString(request)
val sha256HMAC = Mac.getInstance("HmacSHA256")
val key: ByteArray = Base64.getDecoder().decode(secret)
sha256HMAC.init(SecretKeySpec(key, "HmacSHA256"))
return Base64.getEncoder().encodeToString(
sha256HMAC.doFinal(message.toByteArray())
)
}
#Serializable
data class Req(val price: String, val size: String, val side: String, val product_id: String)
I have used bouncycastle to create a encryption scheme that looks like this:
fun enc(plaintext : ByteArray, key : Key,iv : ByteArray) : ByteArray{
val cipher : Cipher = Cipher.getInstance("AES/GCM/NOPADDING ")
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
return cipher.doFinal(plaintext)
}
fun dec(ciphertext : ByteArray, key: Key, iv : ByteArray) : String {
val cipher : Cipher = Cipher.getInstance("AES/GCM/NOPADDING ")
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
return String(cipher.doFinal(ciphertext))
}
and then I use this drivercode to run things:
Security.addProvider(BouncyCastleFipsProvider())
val key2 : Key = SecretKeySpec(Hex.decode("e6d4d9472cf9b7a92a652fc7e1f3b4124906cff47f42115d77d64709f2177503"), "AES")
val iv : ByteArray = Hex.decode("a2d21879269610eab7c16250b3b4bd81fc41b99738d7f8f2966ecd0bb2e5682a")
val ciphertext = encTWO("12345678123456781234567812345678".toByteArray(), key2, iv)
val decryptedText = decTWO(a, key2,iv)
println(Hex.toHexString(ciphertext))
print(decryptedText)
I need to add a tag (which I understand is a mac to this), preferably of 128 bits added to this scheme, but I really cant figure out how? The documentation is very sparse and I cant find any examples doing it
EDIT:
I now understand that the mac tag is sent, concatenated to the end of the ciphertext, I have these:
fun encGCM(plaintext : ByteArray, key : Key,iv : ByteArray) : ByteArray{
val cipher : Cipher = Cipher.getInstance("AES/GCM/NOPADDING ")
val keyBytes : ByteArray = ByteArray(keylength)
random.nextBytes(keyBytes)
val macspec : GCMParameterSpec = GCMParameterSpec(128, iv )
cipher.init(Cipher.ENCRYPT_MODE, key, macspec)
return cipher.doFinal(plaintext)
}
fun decGCM(ciphertext : ByteArray, key: Key, iv : ByteArray) : String {
val cipher : Cipher = Cipher.getInstance("AES/GCM/NOPADDING ")
val macspec : GCMParameterSpec = GCMParameterSpec(128, iv )
cipher.init(Cipher.DECRYPT_MODE, key, macspec)
return String(cipher.doFinal(ciphertext))
}
I'm trying to get some data from my back with a HTTP request and mapping it as a MutableList of my model.
The code is the exact same in 2 situations but one of them isn't working, and I've got an error for the first time? I've searched for it on Google but all the code examples are really different from mines and so are the solutions.
This is the error that I've got:
D/OkHttp: Callback failure for call to http://10.0.2.2:5000/...
D/OkHttp: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token
D/OkHttp: at [Source: (String)"{"recurrenceId":1,"meetingId":4,"weekDay":21}"; line: 1, column: 1]
D/OkHttp: at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1343)
D/OkHttp: at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1139)
D/OkHttp: at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1093)
D/OkHttp: at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:332)
D/OkHttp: at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:265)
D/OkHttp: at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:245)
D/OkHttp: at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27)
D/OkHttp: at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3023)
D/OkHttp: at fr.intech.neith.api.RecurrenceApi.toDo(RecurrenceApi.kt:34)
at fr.intech.neith.helpers.ApiHelper$GET$1.onResponse(ApiHelper.kt:35)
D/OkHttp: at okhttp3.RealCall$AsyncCall.execute(RealCall.java:174)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
D/OkHttp: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
D/OkHttp: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
This is my data class:
package fr.intech.neith.models
data class Recurrence(val recurrenceId: Int = 0,
val meetingId: Int = 0,
val weekDay: Array<Int> = arrayOf())
And this is my API that makes the GET request:
package fr.intech.neith.api
import...
class RecurrenceApi(context: Context): IOnApiResponse {
var itodo: IToDoRecurrenceList? = null
override fun toDo(body: String) {
val mapper = ObjectMapper()
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
//val reader: ObjectReader = mapper.reader().forType(Meeting.class);
val reader = mapper.readValue<MutableList<Recurrence>>(body)
itodo!!.toExecuteR(reader)
}
var client = OkHttpClient()
var api = ApiHelper(client, context)
var url: String = "http://10.0.2.2:5000" + "/api/recurrence" // 192.168.43.135 4G - 10.0.2.2 ÉMULATEUR
fun getRecurrenceList(meetingId: Int, cb: IToDoRecurrenceList) {
val url2: String = url + "/${meetingId}";
itodo = cb
api.GET(url2, this)
}
}
Is there something wrong here?
Here is a working example, totally similiar to the first because it works the same way:
package fr.intech.neith.models
import java.io.Serializable
import java.util.*
data class Meeting(val hostFirstName: String = "",
val hostLastName: String = "",
val estimatedTime: String = "",
val startDate: Date = Date(),
val meetingId: Int = 0,
val name: String = "",
val hasEnded: Boolean = false,
val hostId: Int = 0,
val recurrence: Array<Int> = arrayOf(),
val index: Int = 0,
val isRecurrent: Boolean = false): Serializable
package fr.intech.neith.api
import android.content.Context
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import fr.intech.neith.IOnApiResponse
import fr.intech.neith.IToDoList
import fr.intech.neith.helpers.*
import fr.intech.neith.models.Meeting
import okhttp3.OkHttpClient
class MeetingApi(context: Context): IOnApiResponse {
var itodo: IToDoList<Meeting>? = null
override fun toDo(body: String) {
val mapper = ObjectMapper()
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
val reader = mapper.readValue<MutableList<Meeting>>(body)
itodo!!.toExecute(reader)
}
var client = OkHttpClient()
var api = ApiHelper(client, context)
var url: String = "http://10.0.2.2:5000" + "/api/meeting" // 192.168.43.135 4G - 10.0.2.2 ÉMULATEUR
fun getMeetingList(userId: Int, cb: IToDoList<Meeting>) {
val url2: String = url + "/${userId}/names"
itodo = cb
api.GET(url2, this)
}
}
Thanks for your answers.
EDIT: This has solved the problem: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
From https://craftsmen.nl/kotlin-create-rest-services-using-jersey-and-jackson/ :
In order to deserialize Kotlin data classes we have to register Jackson’s KotlinModule on an ObjectMapper instance.
https://github.com/FasterXML/jackson-module-kotlin
Module that adds support for serialization/deserialization of Kotlin classes and data classes. Previously a default constructor must have existed on the Kotlin object for Jackson to deserialize into the object. With this module, single constructor classes can be used automatically, and those with secondary constructors or static factories are also supported.