Android Keystore multipurpose key generation - android

I'm trying to generate a RSA key pair for this pruposes:
val purposes = PURPOSE_DECRYPT or PURPOSE_ENCRYPT or PURPOSE_SIGN or PURPOSE_VERIFY
And this is my key generation code:
val generator = KeyPairGenerator.getInstance(
KEY_ALGORITHM,
ANDROID_KEY_STORE
)
generator?.initialize(
KeyGenParameterSpec.Builder(
alias,
purposes
)
.setDigests(KeyProperties.DIGEST_SHA256)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build()
)
generator?.generateKeyPair()
However when I use this purposes the decrypt does not work and throws an exception:
InvalidKeyException: "keystore operation failed. Incompatible purpose."
But when I try just encryption and decryption, keystore does encrypt and also decrypt perfectly. this is the purposes that I use for:
val purposes = PURPOSE_DECRYPT or PURPOSE_ENCRYPT
These are the methods for encryption and decryption:
private fun decrypt(cipherText: String, alias: String): String? {
return try {
val privateKeyEntry = getGeneratedPrivateKey(alias)
val output = Cipher.getInstance(
"$KEY_ALGORITHM_RSA/$BLOCK_MODE_ECB/$ENCRYPTION_PADDING_RSA_PKCS1"
// ANDROID_OPEN_SSL
)
output.init(Cipher.DECRYPT_MODE, privateKeyEntry?.privateKey)
val inputStream = ByteArrayInputStream(
android.util.Base64.decode(
cipherText,
android.util.Base64.NO_WRAP
)
)
val res = String(CipherInputStream(inputStream, output).readBytes(), Charsets.UTF_8)
res
} catch (e: Exception) {
e.printStackTrace()
null
}
}
private fun encrypt(plainText: String, alias: String): String? {
return try {
val publicKey = getGeneratedPublicKey(alias) ?: setupKeyPair(
alias,
PURPOSE_ENCRYPT or PURPOSE_DECRYPT
)?.public
val cipher = Cipher.getInstance(
"$KEY_ALGORITHM_RSA/$BLOCK_MODE_ECB/$ENCRYPTION_PADDING_RSA_PKCS1"
// ANDROID_OPEN_SSL
)
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val outputStream = ByteArrayOutputStream()
val cipherOutputStream = CipherOutputStream(outputStream, cipher)
cipherOutputStream.write(plainText.toByteArray(charset("UTF-8")))
cipherOutputStream.close()
val encryptedText = outputStream.toByteArray()
outputStream.close()
val res = android.util.Base64.encodeToString(encryptedText, android.util.Base64.NO_WRAP)
res
} catch (e: Exception) {
e.printStackTrace()
null
}
}
So what is the problem? How can I make a multipurpose keypair in Android keystore?

Probably you have your own custom variables for purpose keys
You should use KeyProperties in this line:
val purposes = PURPOSE_DECRYPT or PURPOSE_ENCRYPT or PURPOSE_SIGN or PURPOSE_VERIFY
like this:
val purposes = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY

Related

android.security.KeyStoreException: Incompatible digest in Kotlin

i want to get value hashed in kotlin with private key RSA, but i have error 'android.security.KeyStoreException: Incompatible digest'.
this my code
fun generatePairKey() {
val start: Calendar = GregorianCalendar()
val end: Calendar = GregorianCalendar()
end.add(Calendar.YEAR, 1)
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore"
)
kpg.initialize(
KeyGenParameterSpec.Builder(
KopraMobile().BIOMETRIC_ALIAS_KEY_PAIR,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setKeySize(2048)
.setUserAuthenticationRequired(false)
.setDigests(
KeyProperties.DIGEST_SHA256,
KeyProperties.DIGEST_SHA1
)
.setCertificateNotBefore(start.time)
.setCertificateNotAfter(end.time)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build()
)
kpg.generateKeyPair()
}
fun getPrivateyPair(): PrivateKey {
val keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val entry: KeyStore.Entry =
keyStore.getEntry(KopraMobile().BIOMETRIC_ALIAS_KEY_PAIR, null)
return (entry as KeyStore.PrivateKeyEntry).privateKey
}
fun getPublicKeyPair(): PublicKey {
val keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
return keyStore.getCertificate(KopraMobile().BIOMETRIC_ALIAS_KEY_PAIR).getPublicKey()
}
fun getValAuthorization(_context: Context): String {
var encoded = ""
try {
val randomString = getRandomString(10)
val time = System.currentTimeMillis()
val random = (randomString + "" + time)
val rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
rsaCipher.init(Cipher.ENCRYPT_MODE, getPrivateyPair())
val byteArray = random.toByteArray(Charsets.UTF_8)
val cipherText = rsaCipher.doFinal(byteArray)
val randomHash = Base64.encodeToString(cipherText, Base64.NO_WRAP)
encoded = "Basic " + Base64.encodeToString(
("$random:$randomHash").toByteArray(),
Base64.NO_WRAP
)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return encoded
}
fun getRandomString(length: Int): String {
val charset = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz0123456789"
return (1..length)
.map { charset.random() }
.joinToString("")
}
i get error
android.security.KeyStoreException: Incompatible digest
when try to call getValAuthorization() on the part
rsaCipher.init(Cipher.ENCRYPT_MODE, getPrivateyPair())
how to solved it? thanks

AES Encrypt/Decrypt SQL DB issue on decryption with Initialization Vector

I am trying to store encrypted data in a SQL database using AES with Initialization Vector (IV). I am able to do this with the following class:
class Encrypted (wordE : String) {
val keyGenerator: KeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,"AndroidKeyStore")
val keyGenParameterSpec = KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
fun genKey(){
keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()
}
fun getKey(): SecretKey {
genKey()
val keystore = KeyStore.getInstance("AndroidKeyStore")
keystore.load(null)
val secretKeyEntry = keystore.getEntry("MyKeyAlias", null) as KeyStore.SecretKeyEntry
return secretKeyEntry.secretKey
}
fun encryptData(data: String): Pair<ByteArray, ByteArray> {
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
var temp = data
while (temp.toByteArray(Charsets.UTF_8).size % 16 != 0)
temp += "\u0020"
cipher.init(Cipher.ENCRYPT_MODE, getKey())
val ivBytes = cipher.iv
val encryptedBytes = cipher.doFinal(temp.toByteArray(Charsets.UTF_8))
return Pair(ivBytes, encryptedBytes)
}
val pair = encryptData(wordE)
val encrypted = pair.second.toString(Charsets.UTF_8)
val iv = pair.first
}
but I have some problems to decrypt the data from the database.
For decryption I have implemented another class:
class Decrypted (dataD: String, ivD :String) {
fun getKey(): SecretKey {
val keystore = KeyStore.getInstance("AndroidKeyStore")
keystore.load(null)
val secretKeyEntry = keystore.getEntry("MyKeyAlias", null) as KeyStore.SecretKeyEntry
return secretKeyEntry.secretKey
}
fun decryptData(data: String, iv : String) : String {
val spec = IvParameterSpec(iv.toByteArray())
val decipher = Cipher.getInstance("AES/CBC/NoPadding")
decipher.init(Cipher.DECRYPT_MODE, getKey(), spec)
val encryptedData: ByteArray = data.toByteArray()
return decipher.doFinal(encryptedData).toString().trim()
}
val decryptedData = decryptData(dataD, ivD)
}
I understood that IV must be the same as the one generated during encryption, so I also stored IV in the database as .
From my logcat I get an "InvocationTargetException" to "Invalid IV" message.
I can see that when I call the Decrypted class enter for decryption:
dataEncrypted: /�,#�j3�RqLrY�
iv_stored: [B#5a36422

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.

Incompatible padding mode when decyrpt usiing Cipher with instance "RSA/ECB/PKCS1Padding"

I had this problem when trying to decrypt an encrypted string. I had read a lot and also search for answers from Stack Overflow, but nothing works. You can see code I attach, it's not complex but the problem makes me confused. I'm using Kotlin, and yes, it's for Android.
private fun generateKeyPair() {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null)
if (keyStore.containsAlias(keyAlias)) return
val keyGenerator =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE)
val builder = KeyGenParameterSpec.Builder(
keyAlias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setKeySize(4096)
.setDigests(KeyProperties.DIGEST_SHA256)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setRandomizedEncryptionRequired(true)
.setUserAuthenticationRequired(false)
keyGenerator.initialize(builder.build(), SecureRandom())
keyGenerator.generateKeyPair()
}
fun encryptApplicationKey(
data: String
) {
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey())
val encryptResult = cipher.doFinal(data.toByteArray())
val encryptResultString = Base64.encodeToString(encryptResult, Base64.DEFAULT)
text_view_info.text = encryptResultString
}
fun decryptApplicationKey(data: String) {
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey()) //it throw Caused by: android.security.KeyStoreException: Incompatible padding mode
val encryptedData = Base64.decode(data, Base64.DEFAULT)
val decryptResult = cipher.doFinal(encryptedData)
val decryptResultString = String(decryptResult)
text_view_info.text = decryptResultString
}
fun getPublicKey(): PublicKey {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null)
val publicKey = keyStore.getCertificate(keyAlias).publicKey
val unrestrictedPublicKey: PublicKey =
KeyFactory.getInstance(publicKey.algorithm).generatePublic(
X509EncodedKeySpec(publicKey.encoded)
)
return unrestrictedPublicKey
}
fun getPrivateKey(): PrivateKey {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null)
return keyStore.getKey(keyAlias, null) as PrivateKey
}
When I debugged the getPrivateKey() function, it's return AndroidKeyStoreRSAPrivateKey object. I don't know if the problem related to the usage of PKCS1Padding, but when I'm using OAEP padding, there's no crash when run it.
Does the device affect the result? I have commented where the crash happens.

Android Keystore decrypting encrypted data gives incorrect result

I'm trying to encrypt a arbitrary String using a KeyPair generated by an instance of java.security.KeyPairGenerator. Unfortunately after encrypting and decrypting the String with the generated KeyPair the result is incorrect.
here is how I go about doing this:
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
fun encryptUsingKey(publicKey: PublicKey, bytes: ByteArray): ByteArray {
val inCipher = Cipher.getInstance("RSA/NONE/NoPadding")
inCipher.init(Cipher.ENCRYPT_MODE, publicKey)
return inCipher.doFinal(bytes)
}
fun decryptUsingKey(privateKey: PrivateKey, bytes: ByteArray): ByteArray {
val inCipher = Cipher.getInstance("RSA/NONE/NoPadding")
inCipher.init(Cipher.DECRYPT_MODE, privateKey)
return inCipher.doFinal(bytes)
}
fun getKey(): KeyStore.Entry {
val containsAlias = ks.containsAlias(alias)
if (!containsAlias) {
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec =
KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT
)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setRandomizedEncryptionRequired(false)
.build()
kpg.initialize(parameterSpec)
val kp = kpg.generateKeyPair()
}
return ks.getEntry(alias, null)
}
My encryption/decryption test looks like this:
fun testEncryptionDecryption() {
val entry = getKey()
if (entry is KeyStore.PrivateKeyEntry) {
val privateKey = entry.privateKey
val certificate = entry.certificate
val publicKey = certificate.publicKey
val testKey = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"
val encrypted = service.encryptUsingKey(publicKey, Base64.decodeFromString(testKey))
val decrypted = service.decryptUsingKey(privateKey, encrypted)
assertEquals(testKey, Base64.encodeToString(decrypted))
}
}
Unfortunately the result looks like this:
org.junit.ComparisonFailure: expected:<[0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF]> but was:<[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANNdt-Oeu_PQAQgxBdNdt-Oeu_PQAQgxBdNdt-Oeu_PQAQgxBdNdt-Oeu_PQAQgxBQ]>
Can someone enlighten me to what's going on here? Where do all these A's come from? Am I using the keys incorrectly?
As suspected it was incorrect configuration. The following works:
val inCipher = Cipher.getInstance("RSA/ECB/OAEPPadding")
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec =
KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT
)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setDigests(KeyProperties.DIGEST_SHA1)
.build()
kpg.initialize(parameterSpec)

Categories

Resources