I'm facing with BadPaddingException while I'm trying to decode InputStream. I was able to encode/decode OutputStream/InputStream while I'm using jackson but when I tried to do with Okio it throws BadPaddingException.
getEncodeStream() and getDecodeStream() methods was working fine with Jackson but it seems that its working different with Okio.
Output of encoded file = FxGOXOwOWzBGMa7+u+E3TvNTOjFv/vKKsSt+Q1+QsedtluVa6sULFhOImRO+pYQp43h/HsrssNm0
UpxcC2cvbM4+ix9nH5YUfCK0NJjzT2iR9tJG8tXTrLSCz/B/6WEQ
#Test
fun main() {
val password = "password"
val file1 = File("src/test/resources/okioTest")
if(!file1.exists())
file1.createNewFile()
//create output stream
val outputStream = FileOutputStream(file1)
val encodeStream = getEncodeStream(password, outputStream)
val sink = Okio.buffer(Okio.sink(encodeStream))
sink.write("something to test something to test something to test something to test something to test something to test".toByteArray())
sink.emit()
val inputStream = FileInputStream(file1)
val decodeStream = getDecodeStream(password, inputStream)
val source = Okio.buffer(Okio.source(decodeStream))
val result = source.readUtf8()
Timber.d(result)
}
fun getEncodeStream(keyString: String, stream: OutputStream): OutputStream {
val keySpec = getKey(keyString)
// IMPORTANT TO GET SAME RESULTS ON iOS and ANDROID
val iv = ByteArray(16)
Arrays.fill(iv, 0x00.toByte())
val ivParameterSpec = IvParameterSpec(iv)
// Cipher is not thread safe
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec)
val base64stream = Base64OutputStream(stream, Base64.DEFAULT)
// Log.d("jacek", "Encrypted: $stringToEncode -> $encrypedValue")
return CipherOutputStream(base64stream, cipher)
}
fun getDecodeStream(password: String, stream: InputStream): InputStream {
val key = getKey(password)
val iv = ByteArray(16)
Arrays.fill(iv, 0x00.toByte())
val ivParameterSpec = IvParameterSpec(iv)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec)
val base64stream = Base64InputStream(stream, Base64.DEFAULT)
return CipherInputStream(base64stream, cipher)
}
#Throws(UnsupportedEncodingException::class)
fun getKey(password: String): SecretKeySpec {
// You can change it to 128 if you wish
val keyLength = 256
val keyBytes = ByteArray(keyLength / 8)
// explicitly fill with zeros
Arrays.fill(keyBytes, 0x0.toByte())
// if password is shorter then key length, it will be zero-padded
// to key length
val passwordBytes = password.toByteArray(charset("UTF-8"))
val length = if (passwordBytes.size < keyBytes.size) passwordBytes.size else keyBytes.size
System.arraycopy(passwordBytes, 0, keyBytes, 0, length)
return SecretKeySpec(keyBytes, "AES")
}
Related
I am facing some problem in decryption RSA in android Using Kotlin
I dont know what is going wrong , Need some Help
Error Reads As input should be less than 128bytes and its occurring in decryption Function
Code -
val keyGen = KeyPairGenerator.getInstance("RSA")
keyGen.initialize(1024)
val keyPair = keyGen.generateKeyPair()
val privateKey = keyPair.private
val publicKey = keyPair.public
val encoder: java.util.Base64.Encoder = java.util.Base64.getEncoder()
val m = encoder.encodeToString(privateKey.encoded)
val l = encoder.encodeToString(publicKey.encoded)
Log.d("keys","$m andddddddddddddd $l")
val xyz = "abcdefgh"
fun encryption(data: String): String {
var encoded = ""
var encrypted: ByteArray? = null
val publicBytes: ByteArray? = Base64.decode(l, Base64.DEFAULT)
val keySpec: java.security.spec.X509EncodedKeySpec = java.security.spec.X509EncodedKeySpec(publicBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val pubKey = keyFactory.generatePublic(keySpec)
val cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING")
cipher.init(Cipher.ENCRYPT_MODE, pubKey)
encrypted = cipher.doFinal(data.toByteArray())
Log.d("test","$encrypted")
encoded = Base64.encodeToString(encrypted, Base64.DEFAULT)
Log.d("final", encoded)
return encoded
}
val o = encryption(xyz)
val nom = o.length
Log.d("leng","$nom")
fun decryption(data: String) : String{
var decoded = ""
var decrypted: ByteArray? = null
val privateBytes: ByteArray? = Base64.decode(m, Base64.DEFAULT)
val keySpec:PKCS8EncodedKeySpec = PKCS8EncodedKeySpec(privateBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val prvKey = keyFactory.generatePrivate(keySpec)
val cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING")
cipher.init(Cipher.DECRYPT_MODE, prvKey)
decrypted = cipher.doFinal(o.toByteArray())
decoded = Base64.encodeToString(decrypted, Base64.DEFAULT)
Log.d("finald", "$decoded")
return decoded
}
decryption(o)
The Android docs give the following snippet for how to encrypt a message in AES:
val plaintext: ByteArray = ...
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256)
val key: SecretKey = keygen.generateKey()
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, key)
val ciphertext: ByteArray = cipher.doFinal(plaintext)
val iv: ByteArray = cipher.iv
I get this error when implementing this method:
Unresolved reference: Cipher
So it appears the 'Cipher' object isn't native, however I have no way of knowing how to import it by following the Android docs. How do I set up my project to be able to use 'Cipher'?
javax.crypto.Cipher is part of the JCE and should be available. Does an import javax.crypto.Cipher not work? Then maybe something is wrong with your environment.
I'm not sure if using that Cipher is necessary, and if the solution I'm providing is the best approach, but I was able to use AES for encryption and decryption using the following code for a text input, means a String:
ENCRYPTION
// text
val aesEncrypt: AESEncrypt = AESEncrypt()
val encryptedByteArray = aesEncrypt.encrypt(text)
val baos_text = ByteArrayOutputStream()
val oosText = ObjectOutputStream(baos_text)
oosText.writeObject(encryptedByteArray)
val encryptedText = String(android.util.Base64.encode(baos_text.toByteArray(), android.util.Base64.DEFAULT))
// key
val key = aesEncrypt.mySecretKey
val baos = ByteArrayOutputStream()
val oos = ObjectOutputStream(baos)
oos.writeObject(key)
val keyAsString = String(android.util.Base64.encode(baos.toByteArray(), android.util.Base64.DEFAULT))
// initialisation vector
val iv = aesEncrypt.myInitializationVector
val baosIV = ByteArrayOutputStream()
val oosIV = ObjectOutputStream(baosIV)
oosIV.writeObject(iv)
val initialisationVector = String(android.util.Base64.encode(baosIV.toByteArray(), android.util.Base64.DEFAULT))
DECRYPTION
You must save the key, initialisation vector, and the encrypted text in order to decrypt it back.
val initialisationVector = ... // get from wherever you saved it, local db, firebase...
val bytesIV = android.util.Base64.decode(iv, android.util.Base64.DEFAULT)
val oisIV = ObjectInputStream(ByteArrayInputStream(bytesIV))
val initializationVectorIV = oisIV.readObject() as ByteArray
val encryptedText = ... // get
val bytesText = android.util.Base64.decode(encryptedText, android.util.Base64.DEFAULT)
val oisText = ObjectInputStream(ByteArrayInputStream(bytesText))
val textByteArray = oisText.readObject() as ByteArray
val key = ... // get your key
val bytesKey = android.util.Base64.decode(key, android.util.Base64.DEFAULT)
val oisKey = ObjectInputStream(ByteArrayInputStream(bytesKey))
val secretKeyObj = oisKey.readObject() as SecretKey
val aesDecrypt = AESDecrypt(secretKeyObj,initializationVectorIV)
val decryptedByteArray = aesDecrypt.decrypt(textByteArray)
val textAfterDecryption = decryptedByteArray.toString(charset("UTF-8"))
EDIT
AES helper class:
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
class AESEncrypt {
var mySecretKey: SecretKey? = null
var myInitializationVector: ByteArray? = null
fun encrypt(strToEncrypt: String): ByteArray {
val plainText = strToEncrypt.toByteArray(Charsets.UTF_8)
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256)
val key = keygen.generateKey()
mySecretKey = key
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, key)
val cipherText = cipher.doFinal(plainText)
myInitializationVector = cipher.iv
return cipherText
}
}
AES decrypt helper
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
class AESDecrypt(private val mySecretKey: SecretKey?, private val initializationVector: ByteArray?) {
fun decrypt(dataToDecrypt: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
val ivSpec = IvParameterSpec(initializationVector)
cipher.init(Cipher.DECRYPT_MODE, mySecretKey, ivSpec)
val cipherText = cipher.doFinal(dataToDecrypt)
return cipherText
}
}
Do tell if you still need any help :)
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.
In Java/Android we have used
private const val secretKey = "1f23456d2d014be5"
private const val salt = "a986e0093328765e"
private const val ivKey = "9898989890KJHYTR"
fun passwordEncryptMethod(stringToEncrypt: String): String? {
var encryptedText = ""
try {
val iv: ByteArray = ivKey.toByteArray()
val ivspec = IvParameterSpec(iv)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val spec: KeySpec = PBEKeySpec(
secretKey.toCharArray(),
salt.toByteArray(),
65536,
256
)
val tmp = factory.generateSecret(spec)
val secretKey = SecretKeySpec(tmp.encoded, "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec)
val encryptedByte: ByteArray =
cipher.doFinal(stringToEncrypt.toByteArray(charset("UTF-8")))
encryptedText = Base64.encodeToString(encryptedByte, Base64.NO_WRAP)
return encryptedText
} catch (e: Exception) {
Log.i("Error encrypting:", e.message ?: "")
}
return encryptedText
}
In iOS Swift I have used .
https://gist.github.com/hfossli/7165dc023a10046e2322b0ce74c596f8
Approach 1 using CCKeyDerivationPBKDF & CCCrypt
let digest = "StringToEncrypt".data(using: .utf8)!
let password = "1f23456d2d014be5".data(using: .utf8)!
let salt = "a986e0093328765e".data(using: String.Encoding.utf8)!//AES256.randomSalt()
let iv = "9898989890KJHYTR".data(using: String.Encoding.utf8)!//AES256.randomIv()
let key = try AES256.createKey(password: digest, salt: salt)
var aes = try AES256(key: key, iv: iv)
let encrypted = try aes.encrypt(password)
print( #function, (encrypted.base64EncodedString()))
//Helper function
static func createKey(password: Data, salt: Data) throws -> Data {
let length = kCCKeySizeAES256
var status = Int32(0)
var derivedBytes = [UInt8](repeating: 0, count: length)
password.withUnsafeBytes { (passwordBytes: UnsafePointer<Int8>!) in
salt.withUnsafeBytes { (saltBytes: UnsafePointer<UInt8>!) in
status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), // algorithm
passwordBytes, // password
password.count, // passwordLen
saltBytes, // salt
salt.count, // saltLen
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), // prf
65536, // rounds
&derivedBytes, // derivedKey
length) // derivedKeyLen
}
}
guard status == 0 else {
throw Error.keyGeneration(status: Int(status))
}
return Data(bytes: UnsafePointer<UInt8>(derivedBytes), count: length)
}
mutating func encrypt(_ digest: Data) throws -> Data {
return try crypt(input: digest, operation: CCOperation(kCCEncrypt))
}
mutating func decrypt(_ encrypted: Data) throws -> Data {
return try crypt(input: encrypted, operation: CCOperation(kCCDecrypt))
}
private mutating func crypt(input: Data, operation: CCOperation) throws -> Data {
var outLength = Int(0)
var outBytes = [UInt8](repeating: 0, count: input.count + kCCBlockSizeAES128)
// var status: CCCryptorStatus = CCCryptorStatus(kCCSuccess)
var keyValue = self.key
let status: CCCryptorStatus =
input.withUnsafeBytes {encryptedBytes in
iv.withUnsafeBytes {ivBytes in
keyValue.withUnsafeMutableBytes {keyBytes in
CCCrypt( // Stateless, one-shot encrypt operation
CCOperation(kCCEncrypt), // op: CCOperation
CCAlgorithm(kCCAlgorithmAES), // alg: CCAlgorithm
CCOptions(kCCOptionPKCS7Padding), // options: CCOptions
keyBytes.baseAddress, // key: the "password"
key.count, // keyLength: the "password" size
ivBytes.baseAddress, // iv: Initialization Vector
encryptedBytes.baseAddress, // dataIn: Data to encrypt bytes
input.count, // dataInLength: Data to encrypt size
&outBytes, //bufferBytes.baseAddress! + kCCBlockSizeAES128, // dataOut: encrypted Data buffer
outBytes.count, // dataOutAvailable: encrypted Data buffer size
&outLength // dataOutMoved: the number of bytes written
)
}
}
}
guard status == kCCSuccess else {
throw Error.cryptoFailed(status: status)
}
return Data(bytes: UnsafePointer<UInt8>(outBytes), count: outLength)
}
Approach 2 Based on this link How to use CommonCrypto for PBKDF2 in Swift 2 & 3
But I am getting different base64 encoded string in both platform. Please help.
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)