Size of output of encryption with AES-128 CBC Kotlin - android

I am currently implementing AES 128 CBC encryption/decryption functions in Kotlin under Android.
I am using the "AES/CBC/NoPadding" instance and I have a function that handles padding.
I can encrypt and decrypt frames well. However, the size of the encrypted data is never a multiple of 16.
How is this possible?
My code :
private object AES128{
private fun cipher(opmode: Int, secretKey: ByteArray, iv: IvParameterSpec):Cipher{
val c = Cipher.getInstance("AES/CBC/NoPadding")
val sk = SecretKeySpec(secretKey, "AES")
c.init(opmode, sk, iv)
return c
}
fun encrypt(str: String, secretKey: ByteArray, iv: IvParameterSpec):String{
val encrypted = cipher(Cipher.ENCRYPT_MODE, secretKey, iv).doFinal(padFrame(str.toByteArray(Charsets.UTF_8)))
return String(android.util.Base64.encode(encrypted, android.util.Base64.DEFAULT))
}
fun decrypt(str: String, secretKey: ByteArray, iv: IvParameterSpec):String{
val byteStr = android.util.Base64.decode(
str.toByteArray(Charsets.UTF_8),
android.util.Base64.DEFAULT)
return String(cipher(Cipher.DECRYPT_MODE, secretKey, iv).doFinal(byteStr))
}
private fun padFrame(value: ByteArray): ByteArray? {
// MANUAL PADDING
val remain = value.size % 16
if (remain != 0) {
val paddedValue = ByteArray(value.size + 16 - remain)
System.arraycopy(value, 0, paddedValue, 0, value.size)
for (i in 0 until 16 - remain) {
paddedValue[value.size + i] = 0x0F
}
return paddedValue
}
return value
}
}

Related

Why are my ByteArrays different after writing to a Room database?

Description
I'm attempting to encrypt a token along with its IV to a pair of ByteArrays, serialize it, then write it to a Room database. The steps are obviously reversed when attempting to decrypt and read it.
When repeating the encryption/serialization/deserialization/decryption steps, but without writing it to a database, the given ByteArray decrypts just fine. Writing it gives me the following error on decryption:
java.io.StreamCorruptedException: invalid stream header
I'm struggling to understand why this happens, and I'd appreciate the help.
Code
ByteArray Functions
#Suppress("UNCHECKED_CAST")
fun <T : Serializable> fromByteArray(byteArray: ByteArray): T {
val inputStream = ByteArrayInputStream(byteArray)
val objectInput = ObjectInputStream(inputStream)
val result = objectInput.readObject() as T
objectInput.close()
inputStream.close()
return result
}
fun Serializable.toByteArray(): ByteArray {
val outputStream = ByteArrayOutputStream()
val objectOutput = ObjectOutputStream(outputStream)
objectOutput.writeObject(this)
objectOutput.flush()
val result = outputStream.toByteArray()
outputStream.close()
objectOutput.close()
return result
}
Encryption Functions
override fun <T : Serializable> encryptData(data: T): Pair<ByteArray, ByteArray> {
var temp = data.toByteArray()
if (temp.size % 16 != 0) {
temp = temp.copyOf(
(temp.size + 16) - (temp.size % 16)
)
}
cipher.init(Cipher.ENCRYPT_MODE, getKey())
val ivBytes = cipher.iv
val encryptedArray = cipher.doFinal(temp)
return Pair(ivBytes, encryptedArray)
}
#Suppress("UNCHECKED_CAST")
override fun <T> decryptData(ivBytes: ByteArray, data: ByteArray): T {
val ivSpec = IvParameterSpec(ivBytes)
cipher.init(Cipher.DECRYPT_MODE, getKey(), ivSpec)
val tempArray: ByteArray = cipher.doFinal(data)
return fromByteArray(tempArray) as T
}
Room Data Class
data class UserData(
val profilePictureId: Long?,
val savedTimestamp: Long = System.currentTimeMillis(),
#PrimaryKey
val username: String = "",
val userToken: Pair<ByteArray, ByteArray>?
)
Database Class
#Database(entities = [UserData::class], version = 1)
#TypeConverters(UserDataConverters::class)
abstract class UserDataDatabase : RoomDatabase() {
abstract val userDataDao: UserDataDao
companion object {
const val DB_NAME = "user_data_db"
}
}
Database DAO
#Dao
interface UserDataDao {
#Query("SELECT * FROM UserData")
fun loadUserData(): Flow<UserData>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun updateUserData(userData: UserData)
}
Database Type Converters
class UserDataConverters {
#TypeConverter
fun fromTokenPair(pair: Pair<ByteArray, ByteArray>): String {
return Json.encodeToString(pair)
}
#TypeConverter
fun toTokenPair(serializedPair: String): Pair<ByteArray, ByteArray> {
return Json.decodeFromString(serializedPair)
}
}
So this isn't actually related to Room. My mistake.
I didn't realise that serializing objects to ByteArrays with ObjectInputStream also writes a header for later serialization with ObjectOutputStream.
When encrypting the serialized data, I was using CDC block mode, which requires padding to a block size divisble by 16. That extra padding caused the aforementioned header to become invalid for accompanying data.
Removing the padding raises issues with detecting when padding stops and content starts (copyOf adds zeroes). With that in mind, and after later finding out that CBC is less secure than GCM (which requires no padding), I changed the block mode to GCM.
See below for resultant code (irrelevent blocks removed):
private val keyGenParameterSpec = KeyGenParameterSpec.Builder(
"TroupetentKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
Also, when decrypting, the previous spec ivParameterSpec() couldn't be used, as GCM requires a tag length. That was also changed:
#Suppress("UNCHECKED_CAST")
override fun <T> decryptData(ivBytes: ByteArray, data: ByteArray): T {
val ivSpec = GCMParameterSpec(128, ivBytes)
cipher.init(Cipher.DECRYPT_MODE, getKey(), ivSpec)
val decryptedArray: ByteArray = cipher.doFinal(data)
return fromByteArray(decryptedArray) as T
}

Encrypting in NodeJs and Decrypting in Android

i am working on symmetric encryption, i have a nodeJs/express https server where i do my aes-256-cbc encryption with the following
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
exports.encrypt = (text,key,iv) => {
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return { iv: iv.toString('hex'), encryptedData: encrypted.toString('hex') };
}
for development and testing purpose i send results as the following :
const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
router.get('/xhtml', function(req, res) {
res.setHeader('Content-Type', 'application/json');
let xml_string =
fs.readFileSync("BookRessources/TheSilverChair/"+req.query.param1,"utf8");
let encryptedData =encryptionSystem.encrypt(xml_string,key,iv)
let keyString =Buffer.from(key, "base64").toString("hex")
res.end(JSON.stringify({xml_string:
encryptedData.encryptedData,iv:encryptedData.iv,key:keyString}));
res.end()
});
In the client side on Andoird using Kotlin, i decrypt using :
// converting Hex String to ByteArray
fun String.decodeHex(): ByteArray {
require(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
// Getting the Key from a String
private fun setKey(myKey: String):SecretKeySpec {
var sha: MessageDigest? = null
try {
var key = myKey.toByteArray(charset("UTF-8"))
sha = MessageDigest.getInstance("SHA-1")
key = sha.digest(key)
key = Arrays.copyOf(key, 32)
var secretKey = SecretKeySpec(key, "AES")
return secretKey
} catch (e: java.lang.Exception) {
throw e
}
}
// Finally decrypting
fun decrypt(cipherText: String, key: String, IV: String): String? {
try {
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
val keySpec = setKey(key)
val iv = IV.decodeHex()
val ivSpec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
val text = cipherText.decodeHex()
val decryptedText = cipher.doFinal(text)
return String(decryptedText)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return null
}
Here is snippets for the key, iv and the encrypted data :
"xml_string": "b558282838947a21ea53595057ecf09e9697f91c41e9217a1672d17f5e2b26c55b833234ce55a1691b2d8d5f58097785550d9378331bc161f09c3fd4044643cdf7df8d778dbae2d593ccca830d5d8abf48431947cfc67cfa009aecb4326cc85772bb29a37d6fd19a9f7099185fcdf6879f1aea97f6b27552ed03399e45381f4122671f94b196ae2444ddb3ad812bc50bad3babdb47343555614db4ca68da7f9ae2b9b9c0dcfec51392a9d261ce6353b1cf78cd14b4688ce199c615b10ddae2da6f27de85fc74752c9437922eadbb6d712eee62fc9dc2fc491155d6a85dcf5a3e556ead1993c108864e6b5b4c5750977744c6d07ed13ce85522f05634e3f579474034b57ce9590e1c617bcedd9a06767ca2d7945dd3b26ce9e901aa8b4b8424ed4cabe93f3273fe4801ab0d499acbc7deb869b909df9e280c7b0bd0df522833eae16ebdea470b531d8fa354e09373078ee351526b585c8450937632e3c77a50543e006891b86282f5ed2bb6cf77ddf68f5470361ec3b99a6ef7a49e103afb370052922cc7f403cddc8b5995f87fc57533ee5d1d66acd5aceec071c21dc3a9e2ad3eda740c9f53712f3857ef4f28d40260949770ee65e25e6468ffe64f0cb7a4a21db4062aed8ceb23d1b2dd96b6182efbe23987567fc88f5992e84d89a269f3a945255e96e0b63f73d036890f341726926775c48ec3409692449f94e6beb4e2136551ab1e199912696c1287d047ad17e2744bcbc0a0b58dc402bfce85668e8095939bba1a722fbb38c8f192e243afcbb7983d4c359050a42861c823e46eb69ad65d99afefa568de8b9a7d0ce5efa3743030a354be5d61add4cc2872cd8cbabc3ab32caf3997ac89c84c454adc751c576d4dba339a7c00947ecdb40ad4dd0dd8009a24070fc9543a4812d2461dc1b010a2545647cafacd71c25beddfa67c59fd7172a4dc8a53011cd47a061db7fa6ab0b3094ff628a4bb6c5e5a6fea2341ab0d89d1cf9bfdc42bcee85ede9db9dcf0225447866645dccfa41e90ba242ceaf2ba709aeef14f59527170ce38ebe859ca835c7b920c275378dcb443ad2660b8652c72b0b4e80771811c1caf61a452226b579a33a417e0bee5aeadabef5de69a545c7e2b3f7d5f8ce0617c76bf98ca0d13cb669fe4e557bb8b76e19f5b56592d394468d8a1f86d09d3823b67ef8fde12740cff026cd612dced07cd4e06ba96ddc9cf9d3ee4ab74080bec144dc915fe61fae7bba78d6d4f6b63fc37c1dbaa542d71e55aee1896da978292449ff12d6393458def3e183ecceb50441bc7deb7106e44b58551175671b5c6535926cf7a4b41cbd3f3ec65a960c50b0cc20eb39a52ed9d189b35f3ea900d25168e4aa48c809e65576d6cb630789d17f36f80bb90acf5f5a7e06f2d5ba7a1897adfe972c7569cd12708556520471ad28b540ff8c1fb3b0b4d903c57b290e98d5a22236172c225737f37aef75789529954dc674208a7e67c9d7c64f0d1a73b8f8db5596a46370c90bc0532e3930d6ef3ca47d71926bcb5453a17774997807ccf68bdd7224176c2a19701ac5e2a6d31c43555f4daf1ce56b09c4d19d0ac8b2932dc9c6fb41a75278646300790a4fb9578925218e98463b8c8d7cd499246c55770b7ac3de5c19950b1b04111e9234ceb40527ac487f7910d6b9bb32fdc7347e02d69382fa81b6d62707af367858d6b116706f2fe632e4b16b49c9d0402ee8ac54b80b44c2ce2988269a0536063940c8ef38180f9b891b611002d7cb717e9ad9ca29caf6b259ee9da252275ef4cab23c64d2e7d52e67f6f5addd20b5079cbd7bf5dacb5f977bf4cb503ee3a1ce32053d588a2fe317e4ed833efade3a14c2e4c6b70175414f4eece4a15c51e34a55fa65d4e51909672b1335a25e6b3484b07085509ed5cf14294205ffec70ad04d9458ddc161b59c439debadc186470d5e76d5b00e5aebb1f4727321568a00618c5b875f74fcbafecbb77b589dab31ff7dab7165cb38d9e32e33a1d3177f259c609277ead087df44463daad729a37068101860aab44a80ca51eb22678a9197439e52b2ac2ca97dec15bc994664574fc560c49b33c32597f63ed96000589b264994096fc8d3fc40369e8c7624c423a307538f770adf589f89e84bf145b2e14d333176adaa4d71d04e5508ed2da8c2b20a22890f55885e93212172facff3812270ccb10cfb2b8789ad367ab23e6ec970e1bdfde32b6989567e078a913b09936e8fea92adfc77841141d53658397f9287498f23e19ffa11005544a7d46adaba0cf09f38767098a08736928013771e64f452c2e630af22d228ebf5d4c1d656773ea699644e3982563c0cfccd6241cb28d9f281b1935e780b13967d7c0e8cc786ecac03b0530e1f7eadb460a3e8e8a4af9f02ba5119e2e18278196857cac056315f0389697d102cb2177394bebbf947eaf0395f5274a3fae7e9b183961ae107d5750630bff9f79057e56657151ef1ab593d88104ef829c68e910fee5cf4e6335f82a94b65863f42f7a5a337206b09482c76937410ed3f87600e71448c6277c4cf546c70beedb3236c0c8c0b85294b4f3f838079bf879b6caf1cfff3faf60f7d27f7911f9c0dff026f2b39793711cc102948b6e64852786fba53216a344010470df5b2b121cd53bd35cbba4dd6d1192bffc7fe926b58e7125f4d3480475c5be34317d2cc98990de8804fc99812891a8f508873a56619c699028b5ab3efa7cb1e50d8ed321db4f7ad38cce53d99f133dbf01ebe496ac0cbaa8446b1329ad365ccbd822b01ae51d7ac1e9118bc24df0fa9aafd34b5999a66648b987ac1ce58ebb5bcb8fdb08b7d551a9e7018e9712e8047e57736545a64647c4748dee8927889448e65230144718418caa1aa063f0b1e75075f32dd0411dba1b2c0e0812f4234ba4bb6ba350042e70780f697be5e2421c6847abb19fc2db0b9b29414554e44e877f2975065e5b0f4759781bb95c9e12717d53fd68b5ba583071a3ad360de16f079ff7986e92412406bb58cbfdb604ffa35c919df1c847203ed16e0497d3a004af71d0c08e818b52260b7355e7b4b30506f47abc69fffce914f43983a8c34d3204cf77e864f1162b85986eda26fb53e6f74ddae922f5426febd627657d713fb9feb038dcf37031c0cba4addd1fbee228bc80192a88996ffd7d7a5cd05004f899a28044436526cf9ca49487e7cb2133f167cf09968a3d922511e241d993b2dd790137fe6dfd2717de73c3fb9d5666e393c6f656c537e398a21f67af3f5a3441651fea7ab1df1e87aba21303ebb5ec75b07e95dd1360e3452a9b166dd4477a5a0903dc890e65b8b64b1c6c49132b1fe5711695bf18263daed2232f2295f0eaed48ef2e2b3cd50dcddf95c5bb42564983336e31d9814c77a1ae05f4916816c791725b3128718740b835f016aa4ac3a716d279cbd712a6c6ff9d6eabfd7576641852f033ffc67b98e3c1761d8b1eaa89bee21cf918c8d102994d2b4c1bb9012ff0859166736c2809ccb1271a273f436f0d8c89c703f9c176dbafdd5b822312977051ff8e50eac9705f0ffda9f14585635242b295930d027833130cb3dcbebf4a7327e257d2c761d087d6e7a23efc76bba954ff9116f9f878257a534ee79b02dc75864d2dd616a2730653c783617734a73952b82a5804e9aa558a027fa4ec612ddbb48e54e95af273203e263570b02d0534b661d0cb366900afb7c173cb8c21b5fa34a8ed60c97c578b486f619deda7ac6c969cfc1633979b8c655270f1af3b97bf6d11f5f6b5a70b1109db7d1ce752bc26d87bb9779fa7961b7f4e35bc78a7b83bda8772f829d1d01bb0e523aafddd2b351676550388997fb9ee8a3ada1b27d0b786e41eb624215366d10f247d07433690373a10f61779d3c5819844f11d9abce4085b168d4063e4a21315586be8ff62c51046f3bd3394e68541977fd3728a5488bee1c8dec461d310c4b6cfb54841c310b9a6cf988fd63941ac21ee672a773fea2e9b8727aa709523898f5611c26dec9fdd30e3a80485e1c0fd7aa67456914e914aefa985005a9166cfd89dd1cc70d99bb0a1ce2f1603eb86905a0ac88256178f1c1f9db83677b1e023dbe5a667bf1a9d715f89b94b9b10fa4edf441f6f09523a3021eef23c56ba76f1840796da6889f886d57efc41a9e8e8d081c82d4252a02cf70afe00ebaa900c21271ae575acdebf130bef5635b3950cd1b72e1abc356431409d5bfa444ff451a314757a380f3acac15bf0fb1643ed5897ff0d1b872e8c503d274d7d9132441f2cbdd2e3f57ab9e5bbf0b9a944f9879bdd99b751cee48534f8c63abcf030194d22658902d968931422718697b696843e6ff343c895379fa0be3cb86598517c4e29f18450b8daf5d1f098061546244eba816b89c8d3f8e29c5baf3cce53f65fa406633e3fd47e11ec850e55f664975f53b9870a39b2716137e6340a61583f34067e7377804fe2b431a3f5dfcbc0a28ec3227679404226477846df5846ea1e9364bf857064e8eb036618ee6cd328d9876251d84ce45808b4bb9dcd8f030fb9bb7d6047360a5cad8d587a755cfbe03cae4c25e86024d315b7ad6c25a739123ea5852a1eb4636e1393dae7360953c62dce2ae0368a58287fd5857642f7e83db50415027a4ad93915db77b9b8eee8e67226a4dcea8d801b1356a42c92421d6864aac7208b75326e39096cab337891b81fb5963be3859796facf65b817ecd498c0fe39963a42e369bcd691ca940a490467acaadcc35dfd670df6db912a467ec9072d2e5044cd40b1ea7a49b938fbdd6f86474072b8351cf77c8fb1761fd3c477d0f2258953ff48000f44f7c326b4a7cc13591822315de5e02262678a59af5aacd326b4ee530132ff3b944b179c41f3417dcdc05c1c999553467594bf7282a5aed5af86d543ba69c1c2048aeda5f17221c4de12c0187c6ba58d8de7ff82d3b025b1ea35d63329056c8b16c44414ba57e84fdaae8594740bcad40d50294a6da760cb283c2224281e38dd8a58ada2e6bca94d655c8504a9aabea153b5d6088851081d33a06a0037b830b900cc4756625b4f55018f34674c00317070b07fe548c2f624a43fdf2c5ec071862871693e32a067dd3ffe8b6be0e9aa9b46e2f5314760679868a667afabc0284cef16637c3550649c027851f3e8bb495c1cd7d387e26834523bf974263a5eccfe334907e6bba48dcb4e6f68c0834ba52e3bc1e3c382cdb994452ea6d3d6ec2232f896d33959490aa2d0083ff7857b56b9d29e94cbf730e4775f022931d936dd478539f95ebee46760a8822cf0a38cb91f1dbfc991cad1305d1c507c89e45ed28ff375d4b6341c991f9ea0e4a5626e2229c4d4677744065fcb7044b5ac5e801571845975959ab162f7f3abf51d62a7421b707595372ea5b2ba387d1ca6aadabf4a630af2a002ededec51b7778c5cd88e1e7808d6063e461ff4623c73dc673ff1055432ccfb3f805ad37a139d022b3f6fa7dd6d6ac621ce2b94a9bfadecf8a1ed9ba12b2804b5beccb5d75cc71ec18f4ee0a69ec0e2eb110df4a46938c28a84cfd4a0dff6d8c14f1936eb750571140fa83fbc71b36d7df9f9789bb1b0684a6cf56eb5fc2195d4b028fd45726f33cd35ec1422d51d35a04ef9c4ead9827b6b067aa15f308a87b65dc7590b952dc447a05a82fc84033f28c713726263b3a1f40ef853393dcb970c4636689ee14cc90c30dbc180f1405507063245465dcdaf92d21411dcaa68b8a676b8da500e8f03581801585347529c1559952da61343c491a0bcf1ecf9d74b0bf001e2dd416bc741b380e668eda4ffb5067eeb8fa2f40bea06e7a64358e74fff8d047e2d78a21a7c6a2ee13c24a71b57444a767503dc8616bcd6bbce07c3b509c10301da8ba9001974434179945960cdbc77de4f8995a072989dfafc04489f72404a53730398aa73775fb62c70f164e671584a0d1c0a4d24c03e9233cb23b5b00d7f51eb13a6ecf0fd15d6ec9db401416eb9b31b8927c1960889f2e3b7c0c60c4ba4b39b4f1ab7dc68f366afd2f0a32c43ea7e22fc7bf48df076c3e2c006306b146c1f0785402602ede9ebba07d924e719ac05a138771b87367b45f3fdce04b0e4c77141dff2b367207afe5693c8840a1d5d08df2c2d464e2fdf60c0cb9d7ba2144c96a26aa72cf06f4cfd148481a153c4cd7f199818275755b59b46ea9bff6ef3a4443267272bd22269c1395784ead16b96074016180216a586c5b3fa19c6923e6df8c11436e84a4bdd5668aa9c5353ff0ceef9b1bdbb5de9ae3b1e066f3d3e2ef077ad8c44057097f175e26ed2353bb2b38dece3b08b602256b2cc06a3a8add3d5450fc0e035e695956097ed431fed6dc61f4df638cbea5c8351bb147a3659d1e96fc42469e09492c93ba5c3cf22474b34b2f8e70b46eef0851e5eb08d91d105f7585034660c11170254aeb38f95c0fa6b57b8269705e823ae0fd45fe31879816ae3ff671e53e82340d91533cf748d7c584533e164457fa34723bdfe658750a2cf697a6c45a6b39e25c87d1a72b9051e6b74539b74a353b0f4fdb1e9985205458ec400f710e531ded94cc565301abfd13810b290132d439e8438c5d936ae7d2a654270a35d76e9ebd8d2fcc28bfb7e81232e5425b87399dd96d581127140854e292de72d3b082db418c55bffa58ac633137e2a37bc2ce06083452ef6157af55dcb43ca90ed4b88c6f03ff812cbb66421c993cfd19e58c7b76398c3bdb86b46dacdc83e1582c4f66fea1f33e51e896d975bce9195cc1c74700d2bc15acb2eeb45e2602b22ef2ed34d685c4e5fb61b92292d51c0531a8f1d5a0ee00c9eec7703e280fe94353bdf1e206e119fe0afa5a528b2839b223408e45acc3f631ab9d49d8711cde4a2b5c9b4e7c3cefef8a101dfec433fa77ddd5183598f42dc9e96256b455e7aa2c1df07fb3d1ae7cb4b8418e2f65da69f076f90284db1fad3182abda21ac43f4ce6890c7cbb68f2aefe9f3928123ff2c20b1c08ec1c0a4fd3f548235fbe85e81f966938eb61926d5296549ca264e7b113986ee90f9c7a35d61f80d0331bd5ffff8543271f534702b25f08937135329e8ccd01b830456e2979f0471fd8df1f8574d4fc61983f433af2e3bf0cc944d418c45d3c68b01873370b63b7d817b10f0c455fc369d912168fef986430ff2a39c8f21851eca8ea77dee24dbaec44125fa5a78d1139a6ba02325c5fae4864b63da6fad75381f87eaef8655d923445877f6a9d87d80287ecde5e28e99e9ad70d951633055e66dbbb5451437f71b646243a9863fc7c5f6f45d319c46f2bca026a82ae9d30029a7630c4c95ad850bfd6a3f569d99a8fb332a8d68ff07648a895e0a454df38be6c9bfc680245334facd2897b1f01a326548c6397829afa979676434254185766372879f6baf29f9e7abee97402510f51ee5cfce98b9c8ee565533b96ae8f1e7f6a2898d8a97cca6a3a00b74023a91195e476a35e2d64b81b9f89512cbb2cc84088e48f8e754407b9c25b2b378a00a13195df9443a5f3ad0a379e436a712a8bfffc3dec824516062819f84edf3ddd3de4f1413567c72229ec9570d507a3dbbc870f1f9ca0df39170dea6f7665fa6aba58ec95f9b3f9a5f43755378ccf07d0a3b5938174e166a5cdaa02d5a43991d035423c628ba9bf509b5baff896f940af8bca1b82eaf2f196dc9e626ec31748de1285b77e4548cd25a48e4ef8e5694855e1ee529f7969b074840a54c3bbcb188bbba9950306974560f462c4e939c5b8d7684a8ac5ca641910682c455e5733c2d46b8c466aa31edcb1d1e0ec39e8324f2faf238828ec3a883fe45d13bb2272a67dd0f83eddad30be032bbc2b1a96bd26b8f9bd8a7e932a3d4665d8cd6b7e0ad9a78bf8ed4e6d2b530fa950b71807bbdde2508b1373879b972fb31732bfdd2d28459c4f76a84ade85dc2bcc84d29e9880aeed4654ec1fcb7200121c8786828dcb7efc26626d6bd52493600f8be8c02cdf6e23f7ef91860573d3c62e638112761d2988af90985d6b18b32742fefef47b46c452b0354a99a3c692f16961dfc5674bd8f975c8991b93684ed335cd034f0f039e98da6632dcd0c3eef4b7082756fcb76832ecdf242e80332df5aad9f4ef6b6ec58cade495710c7c6f1aeedf0cd66426cc2bed3e85eeaad60da1a83de5269f2aa31101bc9c5e820aca2ac2bdcc6baec9b5ea02aa175a767ded8752752b5b57fa955ce364f4454988be873d1f1432f9981d67a7c4da81da54fed4052e68081fda4748513041b274adc99db81ca95128c712ae484391a46e1139c32f5fa6c4211576aa2faf1d937068567a4c915fe75b405b5bf6b1f21ae007a7755a6f6edbd1d871b87a16ccbafa26397f346daa092b7359cb9573a1c3ee839207f298818cb0a27aaee7db16350c6bc8a2108f884fb1b3d62a8df7e03ae11f910cf6e53fc7c1f9c22145165afdd86bd1846abd0e6bb46b4a0f1b6af9bf953da77c6ec383a36269b213131e7e906723e6c8a2de23c2b01d82fe30f3f932c14b29bbc3f3bcd9b725981ee24cc5a04e519083f72ae47698f54cd5a915740a18b8119e86b4a1db92795d526a86c284de06160087a247f361db04bc370486ddad06f641e60b67c6a2ad6b2bbad58f9921c82622c716ec2ab55c2d77998382bba3b69464e47228763302d86a2b9274412ab5b6a99dadf04c7e1615916cd9e30457c69780b6aaae520119832142c79005101bc1471b52bba6d3b5112effe02e5b140210f57133bb7808c5f44d07987817ee4edfb14d8785179f3acf2430f8e8033bd5056b98e3aa75910cda3c54368d2d36f45224bfb40658ad878fe9b0b225e3ba646ce8c86ab807e074644ffb22c34373ee29d46b9c0deadfba426e41b10ea018d06aaf0c5446472551bcdf8780252783b0aff190d3cbbe7f108b8eb6aba331b0482ae9f018b37e1ce137acfdc15aca77af61e1644d73549de905ef903edff2fdb67218f74df09550a41a97ab7fe2fa5ffeb463e9bd0cbb3ef961714cf5a3cf081db82fa152db73fbf1aeb7095a06c2cf7ee4a625396dbae8864b7a63aca9c5b2e5d132633d9e681b7cb2f93b7f2e2b42e5e5f347cb14bc48265f5595e7e20855bb4318785f6bc5be93e42b99cfbf3dd0b1fcfa716f9f07da275599f319ebabb907de4942461c676fb15ac3c0054a97ab37234b928e0651c980671530a80e21c4fecd92ac4378a7958a0cb8cd9f6fb8d79993527ce7791671ff05b1b4fd63182b77adf49e2c205e2d5fc6b8dd5163dbbbfbc5aff4f150d6066ecc59a22162f67ad98c8deaff53ba53039f647bd07f4460c71b601ac6f54bdece7be34befba57119197a0aa0fc167e14fc753e0c350a671bfd469542ee419654ff966523438ee28f8b2d09744a088550078e5be63d529b44a4995bc8ab03b39d6cdf476c36dc4a4f969c3d125f27146a446a5d2cc12dbcdb62ff30415eba05cdaf2865bb67eaa9b9061b0468fbdddcbe9aa227e23ea8c45eab131e69ef1f00880696bd5e40bca4693d1ee1bb864e393cf28de49fc3b45e8393d156c03da0b9f2360f27f8a9963a4602a09f5d55107fb2e0b062142e992f92ddee870293bf0faf3a4ddcdfbee653a0cf2d53a53f66f46130b341f8afab5d615710b5b5d8128269a4eec3d30aee252ffbbc2052ea1ff6ce60f79f8194d295fc79d4fabae62485716475b10f0495ec3b8c639d560ce8b99b29005940b0477ecf3d15a67e4b1458dfd99d6cf673a3584aad2d2930c13b3fc48a0528098d751dbb2cafe5cd63d709a59e342480102fd4d5d4e026834b961d6f8baf317e00eec31136f38ee7cfa4601816773288ac3506d0f4d7b301a9cf2d5ae7f4ab114f34a721894f8860701b6f046f42d4e0cf24be79f09a1c2dcd430e8d4f7a87411c8f25e54b6fa8ff0087bf873c8f63aa134ea3b42e9a90c5b3103d423c564216a4b9d7f0d8024d0c53be35289304cde6c40f3d2871dfb852eb0cf9f04792e38620462419d6a2cafba2a12dd6b10a06cdf0b321c1f233489881121aeff4a6dedc06e8cec26e35d4e1585b05b7b5af371766596057ea31b5303d9c90fd25739224b6025be938e475c6d56f31f1d391e21b0127bd4b023f633fb25e3431f50a0963f1a9e3499c607d2825495cccec8767d6090890a8cf4c7752fb9d8e25961de95d68a1f548bc628c2b3a761d351f162f4cc99fc3c4bc58688daef804787ad45b8fff2a7975c76f28b57003220ed237d3d2a43ebe72d5bbd914a4452ccbff1a4f3c97ab8ff63e69983acea9da31c5c1dd25866fa533d390af9c7d3e5d5084c7e0b359508d2fce6bbbfc9e36f8be49c66e4bf86a79053aa224b8178d59eefc714bf5eeb423aa0645a0eb0c859c4a0a289ebd4773b46367cbc0c829f998c8f4b89db29a3454c97a438d591a9b5fd87fc126fbf279a0f41fe491920ae895bf04b447ee0d8397ad08b3e7e31c8bf31ffa05e396aa41ac63c03b6e4e5acf0cde5f53ca6ec116f5114305283975eb222bee93bd8aa173d346a1f827b798afd8f7df8609090ccacacb81b9cd351a017d988ecda59d503b216edaabb5ec895941707ee300de6311e1d62c8cfe9ce6f5276a521908c604470ffc0c7548a79ebedd315f8772eef87e0cb154f5c82c99c584ea28039a0cebaeb1318ef99b3d086c9ea69fdc398cecc346a0f000858f19fe18afbd232c6753c0430e51cfc65681724c1ea3ac80c93636e644b996594f4a98b5eb596e3d69e5ccd87d25cd287f3f55f0e12b9a38340db62cad4fa3e8ddcd940868576270137f96d3884837bcd8e2246046d64e35a2ee9c0fca0b7ade9e0b0c6f5cc1e31e9f38b1993882f8c8263b834f51e5a64da820f08baf324b661714425f26febe2bf53e3738707fc9c38a91b77acfecc7779c7744ae072ce34ba2ac8cc7399454cc7ebb279d0b952f5aad516011831774c4803f608329aae63349a59c19475b540f3e41156d56bcb1eb6afaeab1e062a68151708599ca6b9d076de284c8175a945f43b0a748d3a4220482befd2a688a5f8d278f176a800fcf82488c31b1ddee28af207ad18c188d169177b111a49280e9811cc52f07f9864e658c3abb0e3df463b44a262bfd21c75029a73b643194acfa2abaa5085b68e99a06a4fb71682f29755e8f9d91af12b150dfb7d9bc71ea01a94d72f664a756d1540025462a156618a71fa58cc256c2bcf0fa9954be618bb456ea9ad9bcfc09ed6a383aa5677a7f9b2e81669271e979bc6584330ed49f0eeb0a6169d17a0588e5e79ab463bde03539a3897a462cdfd8cdf56b41d3747f0d8ecb407e506b73860c60ef8b6b678984b79cf9189c28ab1e66cfa77b7021195d22fe4263e725a80b5578574951841ea855b5f8cfa04b434ea5b83b136715271f57645cb442d0f4ff2c3881037f63778debab67234d677e435ddfd390592a474eaa95fe06a721808619f2db3cd1616d8785c3b3cf3dc1c52be073a241ef0f3b8a3ccba624bb22ee92b75e2056f212658b4537891724f5828362eb6a47c5ff23d969bda71ea1999ebdf12ec440f9c8e02a61c8a60521a3677f4de1526913de8e266a83457f76bcd600c9c315186745c0b61ece77dece104178ad1686a42f4b5639d59840708ae064cf4d993fca2e092957b6f03a116bb01ac9e403e185308d2566012669cea2017bf3996971c05d26b561995ab57abaeebfe16169f6e4212780377b165c113a3a453f092260577bd9c61bf11d57b3a2c049b05ae37a03d4eed5a8c258d30b5affc36e88bf68bb89cb46a4ca57180279f745b4fde9be5728c559e0f6d2a25945b310655263fba0d3ba723c45a9607f2daac915de3d493cc51ef3f10352a32d753b57d89bf7089f8bd5015446bee237f0e0fb33b9278e27b65fbbd6f95be5f29c0d6dc0261b34f775dac394d5fcf12c4ed9fbfd8ac24220388e2e2aa91b394fe33e15c54fe71276d40a75ec7f696903931c2f41e4df6799c0cb4d3854e1acac3d65e45ecf6e43152cd369ba0913e07ac83b5dd6846a6feeafc1467793908f47b248dc4f3dd6d7e641941d26f50edd1eaf7a1f6f8eef0cf3afacb8c27228ad2de55bb50b018c4a616418a74cb3a768dc4423e9de1131dfd32d6bddc0f4a5d388dd335e149b8026f4026bdb976bd2886df421e2c5c1638d3219fb5ee767b950480752b63d4e6e6d3dad1388fb8c2f2458ffc09b1290dd76af4fd84a3c8e993060569f3c0c18d16271818ab155117e14ae35eea3c1801e5267e248d328a6999942926b8d10751c0d413f2a02894b154027494340c60d2bd84aa41e8a6e2224504b53127c6131c4c5babcbe8cbe28f55aa7374ddd397ab72dce3f8dac5d387af286909f915cd9544ba3a1dfcc0cea980aef0c340be2fc95f5e3163a598707947dc24bfabc62f44ffa2fe475d402d065ab8b21f94543f1f45ef49ebb5b2a2a186f12945b373de95d4034cd63eda0662e4405c50159f4fe3b0f97e72abbe18e710d7a34b6e8b8a0a2ffd861cc62032239b69abb4243b48f8d9f03aecc1713a5cc603157f77baf1fc7c0a17b639fa7ac09fc4780fd99dce54222c4243b438b2df4c6571fb028559f8dda2e85b451a0842a9b76e074b8805e0bfb18164b18f6999b9212160c013349b3b7df2eaf9d7e4e57c053f097b66455e941951d4437e3591185d81f69ee26af88a6f4c87f71aef01394813b35f59f23733abf858c24cf8dcc6ca1653dc759b691c65b5b1017d1424e014879977e2407963632e7c016a5c1eadd64430370d4e1e519b94b4801e0465f9452774e1cebd842cd965d3ea6f60b09f3349870405e6b1b18b5abccb04f95c75ef0b3915010a2ae1d7be4c9d14b73ee987cd06f283464291c5cad6450c4d0325ede71edf7c5b6a59b89d6f6f6722e7c72704e7dc817d9ba5c5319d49a0e9cad1e16172b3e34d0801a91c644698aec0d0232df7c1c3a0ef08ffdde0e9c9285c93fae69b6dce801195e4288ca2f040efb3634ee6c49e379a3e492b453bacb84328f8cac21fb59dd0e946e54c0ac25fe883e9b1412e330f580e9d425bda2ef94cca8154cc28323bfbae2450c31868f3ea114f95c544788270714fe8d954fb00e2e2ed97d2400ecb681823e68f8226366ec0648a96cbb696a3c248b1eb4d6b98ccf9484d2b180cf4ac2a4051ed33f2d0b6dc2a80efc73eea7ec28a97f9470994076f0e322288e6842c3ad44b160d5aa1d1f266f406d5b477c26a53f55f3d81ada919b181ef1e5645974d971c0c225262b27a1ead1558843957c662eceaaf0f1e5d9dccff188ea6228636284cddf17dc14d2b9bf9d9d52de357ae6521055b45f9b845245ca9166b66f5d197cf5f17310ad15d9bb03249dc17c184b6e6b6a96b158850809535ac6ec730ea788afa3d21d16e358d9d5b494b2710e8b9c239759943fcc6855f28267b0945a9f74829f9c73074572f94d96c3363cd241b65568c2a5107c9f0fbf45676509a365d2580aac33827f03239b91526d785830e794afef8c50c45f3a5b08446264bd652ba9384142127793fd3304cd918323e97e328c35318dc45666bff5268d0c89fcf83de830f9e75ba4374d772bf5163afc76258386c37621a49f9427b4de1b1bbc33c76b0702929236ce27406ae5d0908dd23612cbd5ef2a58532bdc69fe3a932857d7662a737ca4f341ae45685e3df55e1f71be743222f5f882201dd27b9fdbc198508c7719fce60f582c34f16487f68fbce5b9fe33bedb86fdb7b233de7934d1f81a4041de830c3ca8dfcfbf2251539f21d48386ddcc1d2de34a5ed4e80ecd2799d14468c4460807c07e9f9edf200f98c55523b63338dc9c4a1cc6d01bdf1cfe1a07e4315adf34f95d9110d70a984a937a0e9861d0f73418c0eba88df5605a54879382858e55cd4df995f6333659722f46ec8266baa65b20eb5eeaf0bd0fd7bb26c1ffd6850d0e2aa40462677d829e798b29a83de87ee4c90655ceb7c249c237e02a3fb1444d25a9403b75f5003f6ce4d9b9b39880744dbcafbb410922d47f8981057ddb48cc7791ef11e87cea39297b8057ac230c8fc433611b8975556432d46a534bff9eacff6b42a0eb17d933f76d1c069150dbb5a33d6e4aef414c824fb0f06a8f7abfde2e3400972064b3cb7ccebec5b7a6545fcdc62ad07f0fea11d59da1418e0c07b0d9fb111e058330fc91cad9ac1a3911f0308dcc6a7238b91de66a25680bebc23ccf7f92286f75ef005276f159ae4c21a1a7f6347b94f714b37fa673d78849c0b42853a8dc562fbd68cb73eb0dff62e74dabe06f7b44b64ec77baeb9a364a4f42e7b36179c266e621e8f9982e22e9645a4eebd1fa3c4ba5edccd949782bd67bd4aa6b2fb45b9d20e0ac5d0343cbbcf9f532b86dd0d2002dd2a397d3e067f537411703085fff3d25f850b802ddb7c82a8741c530ec3680b2e3d40eca2e0cfd0d0913c978e3d1136ea2ec3d2615431281c0ff81fcd000fc6aa3675ace0a773ed2010b2ab20fabcdabad124102b6f6b58cd92d264e7b1f410298948fed4200fd8d4f846bad5a6869e07660ceb1c357b68f6cba96d34e1c501fbb4ea9cd764e0ecbcff20e4dff51ef87cdf2bb636257a8dbe6e243a06f71ed8e048608f2bf536eb2094990b12b7facd5268c8c78e99abd5100314e413315d19e1b5c3cd914a4c1e07f474f3a759a892530d12f33737c60c35a6a7ff05b85e1122a6496d1d144bdb2e0f656bcd9b52c373f3aa91d1cb06f4f22853da7d4244ba7361f6a88a50ea1bb1de17cb7e4dab451f6065d78516de9056af535248381825d1ed119319ea305668f8ea73b3a162c4532d0dec8e2e6a185e5ca08bb61ee6975d06b8283a02a2472cb44f67e8c7bb18ec5317677fecf6731c6498192dd28105f4b0a21287d81e6f823d3ecc737688a6bce5c5fbba93adbf3a49928a4cc5724b98fc0c79ef5de5e994f6171e977e3d6d43a66712afa1b46375b0689be1e974f1e3a7e4696879e9c7a6e5db5c7c44ad4319f88e12e49727082d8a3ed25c5ae5b27fc9267fcee3733bb301b7b8728f9ce5f5ba91b1bcebd65157d6970f8a07850c78a940466a51ae31a1e9521f4aa26669af14be91b3c6c78d4fb0d89388cbbeb225a757778ff043edebb4d378cbf435f7bcbaee28fd63e1b96e8d454c71c9602982e3dccedaa3f2e4855de688850e36afef0882def68e9c6a518a4d2a43d77ad3a778c1d6f051be2889441fabce6f60c6076745ee74b2a827670078f33506112b0700b707a16d32c1cbea3d5ab3021ad8379703984224241f5b61b9221cf4c35361c1dc5d7e0ed504dce0ebe04bc7a899b79e06f6584e0f1f6e40a535d7b33aad620d6ebe517122a01c7e69fe6cc8f0c203516bc5761cfb57516a37f66b894f64438903540f7808994d5f2b3814826f185d96b8b12df134d313457bb02889622e0d0ed75cf0cbc7ee62dd1a24e8fae18b30bd4309a1c06191139e2bf8490f1287a521df945df19e5f48ddf440f0d99159177e784af1ce4208a93afe307cb7ad58cf671050d4b8324781869795205759e9210897d9185eeaf55e07ddcb1b5d3a279a77a1f2fc7a47efdec9063ba9951277d5e8bf9e6d26de5bf41be350c32c43860209f6daa92847a4288e60eed029728fac833577b5af905e78dc6c4a449b07c7a2a3937f5c305250d35cf94b8175f0acf286959088f88daaa978be8e974345027a0b0908be397ea945374f177b580260828542ccb78328024f6afca6cbc5ff0c89e2a13d06c93ebceb5b173aeb5d59f2d2cebea3e8572c6743fdef96eb056ddfe57f3550e2add365e6e223ddb40b147018bcd582d585cdb136b02bde41b258044fe2332a7898cc1b44444eebf2cec76fe494d2a96275a950b8dca42568bd9b54d3385877d9ecf1d4ae978c05ba1db802be3ee64c8c34cf940d0629118220abe3d1a3ca189ae4eb44a080f81d564336c71a365134bf64932eda38ac624b8f3234b61bd995fd3998f937304e9b4753e7c9bfd643c890b0359cbbfb497e5cbe291010d75d32e170ba676740aca0b2bdbb298c0707924f1ca93a2ddcc66d41f4f283ecb2953ec7e784a17d66e329869cff3f6604aa00e38211af1ef2cd98c88175dcc78e1cc43c4b1a02775000641b723d0f42e8f10e47eac75bb856049beb70bc5d7563bceb7dfc4806eaf95c483990181bb43a67d9175749e139413dc446977377d4345cd469f7903dba4befb4786fa2d4cc2f882f40f4cdf3570f32b72fff170b3852460a7e71ecc37182ab98c5dcaf4cd3aa0ddcc986abd8d0dd7f08db90a7ad4f497f484bf6ed7288dc2b929aeb4cae910a448638f1670908fba915d4ece079421e6a8d39a2a43873dbcc577bd257c2488344ab19de7b0f37f3662b2e65ae685b201c01753880559611448191041b6ccae985ed58fa0c1b5df4bddd62b9ae97bb6f15b6d77cc4ac95ccdb8bd648202164e63e549d067e28313a103e53a762ca52b3b55fcbbad1da1a4a00520de9931dff4a7750b507f31deb1fa0e528d85fde3c2f6c35aa888b0fc52f24f028a7ada347b97c3df0820caf039e45f3f3e01816caeb2187914fa08e75314a74f2e8bca93dce7a0b833ce4b5d047291054d54c00ef44e3c8a9b2ac26be217ab86f9cf5e2889af24c4761c5aa2a203bc37b41da24e18b9ffdc2cad0e3ec9c5a2d6c7525633aca5818d482b6378568e791d67301280fc7befa609425166a8917de88c5712f23967601219559d0b1748514c0bf3abeb6583b05a44676f8f3d584bde5e452a7eafe4ff56bb3815fd9b68ce2e4bd8dea419569b98924bfa9bdb16114d0b5a0c92741e6b2cf9ec1fc97b97640b5b40090f77e795feab78c15d0d0cdeff736c6fd1f9b83e3eef3b96eb476eba2c872782a4af6ee8477a5ffd7ed99245fb5b936ea5168d9e3d4e9d327e3b97f3a26587f1329f73ec3f59a7b6a22e116641d26e5e5299dec6d8dd7fc673f88754cd042c0e3cad8349e9dc3f1861eefd425f9206772d38e66a28056541da48e5f930d053358fe8921ce13a7b1725d65ed7b642ec7d8366ee9cf210e43d6e104afca934393f3d18e41a94c4707abd38673065628a0467a2bfd201a1c4f2c7ea8a61ce0882856a50e1a6be7dfd3925221f71029d8f3297dbecc7929a8515fc75cddd8fa067d8c5e60920fb9a18c841813f2c12d60401cb28325f960ac87b6d8cf189fbbfa6ebcd74750e71d8cf19c020fb40a51b5672c1292664f3a31abb4bae8cdc8ea5b8ae586be3d961b30e4b2c5bb4d52ae8903133db96d264ae6db03f2acb81f5e87fec04a42d8000d43c1d087227e5db6dfd351b3ca90e3292801c3f3058c70d536df6935be143009b5a559acf163eb3f5d86e870098cdb395d00c247fb7b0cf757142f6aa6ed54302e9c9ee088f35a40be49fe754695e30fd470e19b61cf25b4fa3e7ed4fde7d84c8199707accd7fd2a8ece077c26b2a1bb96595199827521a775618d7ef86ef0a8b70b55c44984eca2f96f7386d5898002ac95d019048ae2f1a9b0debbb9732514c121d2099d440bee55e945cd74cf085154fda6aa65d760a673315123a0d2a72b3777d8ebfce4608b6b4b812a04520abeae5a18a7cfa1055c0852435e6646e5f827fc7e3a7cd3c99f9d7e8baadf573428b9780ce0fcd709ea4e9e1406f1785bc10b984b80a0d8e46b1c55d1bf37987fe49f7dae4a9ca2e4c2186b1d31f4b869e29e6251b8ef743ab3b3ec2934584b95690bc76a839e5a1f324daaedaa3866a44ff8eea51214c66c5f6b8e281805535df924e241c8c05a209c6543c187cb00a9989963ab46a83ce0f7d3be888837153843dd709b39f515e5fb97ba3c9784bcbed9f9e0bfa3f799ce910d7b0ad07b6ffaaeabb34becaf00ab00b1854400193953b74c228664eee7fc6712aaf0ff927168565faff3628d0abf324ff40c7ec39085f76122175d74e38e5d07b04b1dba30d3dca315130f5b5700df5116e13c76ee65e4912e7559b53e07b8dda6b1ec6cf9373fb14cc50ffb648c81d776acd03b028e6161abe227ca9b41e65dfde2ed75ed82cf1ef439d6fefd210a6de2dc29083227932e0531fa150db0e434da413a43142ed244afc1e6e26eb271d54326d1c736538d68363a9782d8e3f16a0b36e0a7c107eb0e233eb1db54ce54a55cd97cc4742e64b95fdabd34f6c53758f672787943e50a95d7738dae260d04c06fb21752f9e6bf2b16ee3f3a9cab13026141021f8008cf389d5425fe86e7d93ad45c736043577f97636d827810f93bd2c5ff77b96c22ab4b4c335d80fab8e40fe85693057414e8e8a1f5b893c2ae5154550840a904a134c9cd77b8501bc59fa79c2fc19ddd3d",
"iv": "bce8e1e081ee4def6001ddaccd3afecc",
"key": "8e1e7e7cc9ff2457deda3fb012b1ec65a042cd08e1264f83a706bc5fdf12a562"
The decrypted text is not clear, it gives me something like this
���\��n%�Вl��v+\"�I���l�+��R�_�}��`}��g���ӄ�_Pr�������D���A�[�*�
thanks for helping, mention me if something is unclear.
the problem was the conversion of hex String to bytes, the iv string, the encrypted CipherText as well as the key string weren't converted properly to bytes.
In addition to the PKCS5Padding that must be applied in the Kotlin code.
here is the functions used to get the original key and iv of encryption :
fun toByte(hexString: String): ByteArray? {
val len = hexString.length / 2
val result = ByteArray(len)
for (i in 0 until len) result[i] =
Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).toByte()
return result
}
fun setKey(myKey: String):SecretKeySpec {
try {
var key = toByte(myKey)
key = Arrays.copyOf(key, 32)
var secretKey = SecretKeySpec(key, "AES")
return secretKey
} catch (e: java.lang.Exception) {
throw e
}
}
//finally Decryption
fun decrypt(cipherText: String, key: String, IV: String): String? {
try {
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
val keySpec = setKey(key)
val iv = toByte(IV)
val ivSpec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
val text = toByte(cipherText)
val decryptedText = cipher.doFinal(text)
return String(decryptedText)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return null
}
For the NodeJs server, everything stay the same.

AndroidX DataStore - AES/CBC/PKCS7 - javax.crypto.IllegalBlockSizeException

I read Mark Allison's blog post about combining the new Android DataStore with encryption with the use of the Android Keystore.
I'm using the same exact SecretKey properties (AES/CBC/PKCS7) and Encrypt/Decrypt found in his blog.
class AesCipherProvider(
private val keyName: String,
private val keyStore: KeyStore,
private val keyStoreName: String
) : CipherProvider {
override val encryptCipher: Cipher
get() = Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.ENCRYPT_MODE, getOrCreateKey())
}
override fun decryptCipher(iv: ByteArray): Cipher =
Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.DECRYPT_MODE, getOrCreateKey(), IvParameterSpec(iv))
}
private fun getOrCreateKey(): SecretKey =
(keyStore.getEntry(keyName, null) as? KeyStore.SecretKeyEntry)?.secretKey
?: generateKey()
private fun generateKey(): SecretKey =
KeyGenerator.getInstance(ALGORITHM, keyStoreName)
.apply { init(keyGenParams) }
.generateKey()
private val keyGenParams =
KeyGenParameterSpec.Builder(
keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
setBlockModes(BLOCK_MODE)
setEncryptionPaddings(PADDING)
setUserAuthenticationRequired(false)
setRandomizedEncryptionRequired(true)
}.build()
private companion object {
const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING"
}
}
class CryptoImpl constructor(private val cipherProvider: CipherProvider) : Crypto {
override fun encrypt(rawBytes: ByteArray, outputStream: OutputStream) {
val cipher = cipherProvider.encryptCipher
val encryptedBytes = cipher.doFinal(rawBytes)
with(outputStream) {
write(cipher.iv.size)
write(cipher.iv)
write(encryptedBytes.size)
write(encryptedBytes)
}
}
override fun decrypt(inputStream: InputStream): ByteArray {
val ivSize = inputStream.read()
val iv = ByteArray(ivSize)
inputStream.read(iv)
val encryptedDataSize = inputStream.read()
val encryptedData = ByteArray(encryptedDataSize)
inputStream.read(encryptedData)
val cipher = cipherProvider.decryptCipher(iv)
return cipher.doFinal(encryptedData)
}
}
I'm using following super simple ProtocolBuffer with only one String field.
syntax = "proto3";
option java_package = "my.package.model";
message SimpleData {
string text = 1;
}
I'm using following code to test this implementation.
class SecureSimpleDataSerializer(private val crypto: Crypto) :
Serializer<SimpleData> {
override fun readFrom(input: InputStream): SimpleData {
return if (input.available() != 0) {
try {
SimpleData.ADAPTER.decode(crypto.decrypt(input))
} catch (exception: IOException) {
throw CorruptionException("Cannot read proto", exception)
}
} else {
SimpleData("")
}
}
override fun writeTo(t: SimpleData, output: OutputStream) {
crypto.encrypt(SimpleData.ADAPTER.encode(t), output)
}
override val defaultValue: SimpleData = SimpleData()
}
private val simpleDataStore = createDataStore(
fileName = "SimpleDataStoreTest.pb",
serializer = SecureSimpleDataSerializer(
CryptoImpl(
AesCipherProvider(
"SimpleDataKey",
KeyStore.getInstance("AndroidKeyStore").apply { load(null) },
"AndroidKeyStore"
)
)
)
)
When I try to serialize and deserialize a simple String it works like intended.
simpleDataStore.updateData { it.copy(text = "simple-string") }
println(simpleDataStore.data.first())
// "simple-string"
However when I try the same with a longer String (note smaller than the max size for Proto's).
The save works, but upon killing the app and relaunching the app to retrieve the value it crashes.
simpleDataStore.updateData { it.copy(text = "eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQeyJhdWQiOiJ2cnRudS1zaXRlIiwic3ViIjoiNmRlNjg1MjctNGVjMi00MmUwLTg0YmEtNGU5ZjE3ZTQ4MmY2IiwiaXNzIjoiaHR0cHM6XC9cL2xvZ2luLnZydC5iZSIsInNjb3BlcyI6ImFkZHJlc3Msb3BlbmlkLHByb2ZpbGUsbGVnYWN5aWQsbWlkLGVtYWlsIiwiZXhwIjoxNjEwMjc4OTQ0LCJpYXQiOjE2MTAyNzUzNDQsImp0aSI6Ijc0MDk3MzFiLTg5OGUtNGVmNS1iNWMwLTEzODM2ZWZjN2ZjOCJ9kSkuI9Z0XLLBtfC0SpHA4wV0299ZOd6Xj99hNkemim7fRP1ooCD8YkqbM0hhBKiiYbvhqmfc1NSKYHAehA7Z9c6XluPTIpZkljHIBH7BLd0IGznraUEOMYDh0I2aQKZxxvwV6RlWetdCBUf3KtQuDO7snywbE5jmhzq75Y") }
println(simpleDataStore.data.first())
Process: com.stylingandroid.datastore, PID: 13706
javax.crypto.IllegalBlockSizeException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:513)
at javax.crypto.Cipher.doFinal(Cipher.java:2055)
at com.stylingandroid.datastore.security.CryptoImpl.decrypt(Crypto.kt:33)
at com.stylingandroid.datastore.ui.MainActivity$SecureSimpleDataSerializer.readFrom(MainActivity.kt:32)
at com.stylingandroid.datastore.ui.MainActivity$SecureSimpleDataSerializer.readFrom(MainActivity.kt:26)
at androidx.datastore.core.SingleProcessDataStore.readData(SingleProcessDataStore.kt:249)
at androidx.datastore.core.SingleProcessDataStore.readDataOrHandleCorruption(SingleProcessDataStore.kt:227)
at androidx.datastore.core.SingleProcessDataStore.readAndInitOnce(SingleProcessDataStore.kt:190)
at androidx.datastore.core.SingleProcessDataStore$actor$1.invokeSuspend(SingleProcessDataStore.kt:154)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: android.security.KeyStoreException: Invalid input length
at android.security.KeyStore.getKeyStoreException(KeyStore.java:1301)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:176)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506)
at javax.crypto.Cipher.doFinal(Cipher.java:2055) 
at com.stylingandroid.datastore.security.CryptoImpl.decrypt(Crypto.kt:33) 
at com.stylingandroid.datastore.ui.MainActivity$SecureSimpleDataSerializer.readFrom(MainActivity.kt:32) 
at com.stylingandroid.datastore.ui.MainActivity$SecureSimpleDataSerializer.readFrom(MainActivity.kt:26) 
at androidx.datastore.core.SingleProcessDataStore.readData(SingleProcessDataStore.kt:249) 
at androidx.datastore.core.SingleProcessDataStore.readDataOrHandleCorruption(SingleProcessDataStore.kt:227) 
at androidx.datastore.core.SingleProcessDataStore.readAndInitOnce(SingleProcessDataStore.kt:190) 
at androidx.datastore.core.SingleProcessDataStore$actor$1.invokeSuspend(SingleProcessDataStore.kt:154) 
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) 
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) 
2021-01-10 14:08:09.907 13706-13706/com.stylingandroid.datastore I/Process: Sending signal. PID: 13706 SIG: 9
Does anybody know?
Is it specific to the String's length in combination with the chosen encryption algorithm?
Is the decryption function wrong?
Thanks in advance.
The problem is reproducible on my machine. It occurs when the encrypted data encryptedBytes in CryptoImpl.encrypt has a length of more than 255 bytes. The reason is that starting with 256 bytes encryptedBytes.size cannot be stored on one byte, while the methods int InputStream.read() or void OutputStream.write(int) read or write only one byte.
Therefore, if the size of the ciphertext is to be written, a sufficiently large bytes buffer must be used in CryptoImpl.encrypt, e.g. 4 bytes:
with(outputStream) {
write(cipher.iv.size)
write(cipher.iv)
write(ByteBuffer.allocate(4).putInt(encryptedBytes.size).array()) // Convert Int to 4 bytes buffer
write(encryptedBytes)
}
and for reading in CryptoImpl.decrypt:
val ivSize = inputStream.read()
val iv = ByteArray(ivSize)
inputStream.read(iv)
val encryptedDataSizeBytes = ByteArray(4)
inputStream.read(encryptedDataSizeBytes)
val encryptedDataSize = ByteBuffer.wrap(encryptedDataSizeBytes).int // Convert 4 bytes buffer to Int
val encryptedData = ByteArray(encryptedDataSize)
inputStream.read(encryptedData)
However, writing the sizes is actually not necessary. The size of the IV is known, it corresponds to the block size, i.e. 16 bytes for AES, so that the criterion for the separation of IV and ciphertext is defined. Thus, the data can be written in CryptoImpl.encrypt as follows:
with(outputStream) {
write(cipher.iv) // Write 16 bytes IV
write(encryptedBytes) // Write ciphertext
}
And for reading in CryptoImpl.decrypt:
val iv = ByteArray(16)
inputStream.read(iv) // Read IV (first 16 bytes)
val encryptedData = inputStream.readBytes() // Read ciphertext (remaining data)

IllegalBlockSizeException "Invalid input length" when decrypt data

I am developing an android app.
I want to encrypt/decrypt some sensitive data (jwt token) into SharedPreference.
So I wrote the below code.
fun initKeyStore() {
val alias = "${packageName}.rsakeypairs"
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
if (keyStore.containsAlias(alias)) {
} else {
SLog.d(LogTag.SECURE, "[cipher] No keypair for $alias, creating a new one")
with(KeyPairGenerator.getInstance(KEY_ALGORITHM_RSA, "AndroidKeyStore"), {
val spec = KeyGenParameterSpec.Builder(alias,
PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
.setAlgorithmParameterSpec(RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
.setBlockModes(BLOCK_MODE_CBC)
.setEncryptionPaddings(ENCRYPTION_PADDING_RSA_PKCS1)
.setDigests(DIGEST_SHA512, DIGEST_SHA384, DIGEST_SHA256)
.setUserAuthenticationRequired(false)
.build()
initialize(spec)
generateKeyPair()
})
}
keyEntry = keyStore.getEntry(alias, null)
}
fun String.encrypt(): String? {
cipher.init(Cipher.ENCRYPT_MODE, (keyEntry as KeyStore.PrivateKeyEntry).certificate.publicKey)
val bytes = this.toByteArray(Charsets.UTF_8)
val encryptedBytes = cipher.doFinal(bytes)
val base64EncryptedBytes = Base64.encode(encryptedBytes, Base64.DEFAULT)
return String(base64EncryptedBytes)
}
fun String.decrypt(): String {
cipher.init(Cipher.DECRYPT_MODE, (keyEntry as KeyStore.PrivateKeyEntry).privateKey)
val base64EncryptedBytes = this.toByteArray(Charsets.UTF_8)
val encryptedBytes = Base64.decode(base64EncryptedBytes, Base64.DEFAULT)
val decryptedBytes = cipher.doFinal(encryptedBytes)
return String(decryptedBytes)
}
But when the app tries to decrypt the encrypted data, Exception occurred.
javax.crypto.IllegalBlockSizeException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:513)
at javax.crypto.Cipher.doFinal(Cipher.java:2055)
...
Caused by: android.security.KeyStoreException: Invalid input length
at android.security.KeyStore.getKeyStoreException(KeyStore.java:1539)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.update(KeyStoreCryptoOperationChunkedStreamer.java:132)
The length of the JWT token that I am using is very long. (More than 800)
If I try to encrypt/decrypt short text, it works fine...
How can I encrypt/decrypt the long text?
In order to encrypt a long text, you either increase the key size (which is probably a bad idea, since it will take much more time to generate this key), or you split the text into chunks, encrypt those chunks one by one, and save them as a string array.
The maximum limit of Assymmetric Encryption is 245 character.
it can be fixed with the chunks of the Long String
object SecurePreferencesHelper {
private const val chunkSize = 240
private fun getNumberOfChunksKey(key: String) = "${key}_numberOfChunks"
fun setLongStringValue(key: String, value: String) {
val chunks = value.chunked(chunkSize)
SecurePreferences.setValue(getNumberOfChunksKey(key), chunks.size)
chunks.forEachIndexed { index, chunk ->
SecurePreferences.setValue("$key$index", chunk)
}
}
fun getLongStringValue(key: String): String? {
val numberOfChunks = SecurePreferences.getIntValue(getNumberOfChunksKey(key), 0)
if (numberOfChunks == 0) {
return null
}
return (0 until numberOfChunks)
.map { index ->
val string = SecurePreferences.getStringValue("$key$index", null) ?: run {
return null
}
string
}.reduce { accumulator, chunk -> accumulator + chunk }
}
fun removeLongStringValue(key: String) {
val numberOfChunks = SecurePreferences.getIntValue(getNumberOfChunksKey(key), 0)
(0 until numberOfChunks).map { SecurePreferences.removeValue("$key$it") }
SecurePreferences.removeValue(getNumberOfChunksKey(key))
}
fun containsLongStringValue(key: String): Boolean {
return SecurePreferences.contains(getNumberOfChunksKey(key))
}
}
For reference pls refer link
click here

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

Categories

Resources