AES256 encryption in swift string not matching - android

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.

Related

Encrypting in NodeJs and Decrypting in Android

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

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

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

IllegalBlockSizeException "Invalid input length" when decrypt data

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

Async loading on recyclerview viewholder not working properly

I'm developing an app that shows sensible information so I have to
encrypt all informations stored on Room Database.
After a lot of research, I choosed to encypt using AES, generating a random key
and storing on KeyStore (minSdk: 24 btw).
private fun encript(plain: String): String? {
return try {
generateKey()
encData(plain)
} catch (e: Throwable) {
if (!BuildConfig.DEBUG){
Crashlytics.logException(e)
}
null
}
}
private fun encData(plain: String): String? {
val sKey = getSecretKey()
iv = ByteArray(12)
secRng = SecureRandom()
secRng.nextBytes(iv)
val cipher = Cipher.getInstance(AES_MODE)
val parameterSpec = GCMParameterSpec(128, iv)
cipher.init(Cipher.ENCRYPT_MODE, sKey, parameterSpec)
cipText = cipher.doFinal(plain.toByteArray())
return encBuffer()
}
private fun encBuffer(): String? {
val byteBuffer = ByteBuffer.allocate(4 + iv.size + cipText.size)
byteBuffer.putInt(iv.size)
byteBuffer.put(iv)
byteBuffer.put(cipText)
val cipherMessage = byteBuffer.array()
//clean()
return Base64.encodeToString(cipherMessage, Base64.DEFAULT)
}
So i must show all these informations on a list, so Im decrypting all information
on viewholder. the problem is that is too slow when it shows a lot of items,
so i decided to try an async descrypt inside the viewholder and for my surprise
i got a lot of "Unitialized keystore" exception, letting y data encrypted,
it's weird because when i scroll down and up, some viewholders decrypt successfull
and others not, it's pretty random, an it seems that viewholder try to decrypt more
than once. PS: I can't cache decrypted data on SharedPrerences for security reasons
fun decription(encStr: String): String? {
return try {
dec(encStr)
} catch (e: Throwable) {
Log.d("cripto", "Here, Trowing Unitialized Keystore")
if (!BuildConfig.DEBUG){
Crashlytics.logException(e)
}
null
}
}
private fun dec(encStr: String): String {
val byteBuffer = ByteBuffer.wrap(Base64.decode(encStr, Base64.DEFAULT))
val ivLength = byteBuffer.int
if (ivLength < 12 || ivLength >= 16) { // check input parameter
throw IllegalArgumentException("invalid iv length")
}
val iv = ByteArray(ivLength)
byteBuffer.get(iv)
val cipherText = ByteArray(byteBuffer.remaining())
byteBuffer.get(cipherText)
return callCip(cipherText, iv)
}
private fun callCip(cipText: ByteArray, iv: ByteArray): String {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), GCMParameterSpec(128, iv))
val plainText = cipher.doFinal(cipText)
return String(plainText)
}
And my viewholder code:
doAsync {
var name = ""
var lname = ""
var patRecord = ""
if (value?.patient != null){
name = PatSecHelper.nToPat(value.patient?.name?.trim() ?: "")
lname = PatSecHelper.nToPat(value.patient?.lastName?.trim() ?: "")
patRecord = PatSecHelper.nToPat(value.patient?.patientRecord?.trim() ?: "")
}
onComplete {
if (value?.patient == null){
view.textViewPatientId.visibility = View.GONE
}else{
if (name == "" || lname == "") {
view.textViewPatient.text = "Error..."
}else{
view.textViewPatient.text = "$name $lname"
}
if (patRecord == ""){
view.textViewPatientId.text = "Error..."
}else{
view.textViewPatientId.text = patRecord
}
}
}
}
**EDIT:
Here is the code that i'm using to genarete and get keys
private fun generateKey(){
keyStore = KeyStore.getInstance(AndroidKeyStore)
keyStore.load(null)
if (!keyStore.containsAlias(KEY_ALIAS)) {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore)
keyGenerator.init(
KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false)
.build())
keyGenerator.generateKey()
}
}
#Throws(Exception::class)
private fun getSecretKey(): java.security.Key {
keyStore = KeyStore.getInstance(AndroidKeyStore)
keyStore.load(null)
return keyStore.getKey(KEY_ALIAS, null)
}
Here are the problems I see:
I am not sure what the implementation is of generateKey() but if it does what it says on the tin then don't generate your key every time you encrypt. As I believe you are doing here:
private fun encript(plain: String): String? {
return try {
generateKey() // <- why are you doing this every time? are you storing a unique key with everything you encrypt?
encData(plain)
} catch (e: Throwable) {
if (!BuildConfig.DEBUG) {
Crashlytics.logException(e)
}
null
}
}
This will mean you have a new key every time you encrypt something? Rather, this should be initialized and ready before you try to encrypt/decrypt.
And, if this is the only place you do generate your key, then how do you know that when you call this:
private fun callCip(cipText: ByteArray, iv: ByteArray): String {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), GCMParameterSpec(128, iv)) // <- Are you sure you have called encrypt before callin this?
val plainText = cipher.doFinal(cipText)
return String(plainText)
}
That you have a key generated to be fetched? Pulling out generateKey() and making sure you have called it before you do any encrypt/decrypt actions should fix your problem I think.

BadPaddingException: pad block corrupted while trying to decode

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")
}

Categories

Resources