Search code examples
phpencryptioncryptojsnativescript-angular

CryptoJs AES Decryption Error: Malformed UTF-8 data in Nativescript-Angular


I got an error while decrypting the encrypted data. My goal is to encrypt a pdf file in PHP and decrypt it in nativescript to display the pdf natively in the app. I'm posting this as a last resort because I've read similar posts regarding my issue but none work for me. The data is encrypted in PHP using openssl and will be decrypted in nativescript using cryptoJs for AES decryption and jsrsasign for RSA decryption of the key and the IV. I pretty much appreciate anything that would help me. There might be something wrong with how I did it but I'm not sure what it is. Here's my progress so far.

Encryption in PHP (RSA+AES).

Already tested in decrypting the data successfully in PHP. So it's safe to assume that it can be decrypted.

$data = // Is a pdf file
//get public key
$publicKey = // API CALL to get the public key
// get mime type of file
$dataFormat['docMimeType'] = "application/json";
// compute the hash of the file before encryption
$dataFormat['hash'] = hash('sha256', $data);
// generate random two 16 bytes long encryption key
$secretKey1 = openssl_random_pseudo_bytes(16);
$secretKey2 = openssl_random_pseudo_bytes(16);
// concatenate the two key to make the 32 bytes long AES encryption key
$secretKey = $secretKey1.$secretKey2;

// RSA Encryption of the keys and IV
// encrypt the aes encryption key with the public key
openssl_public_encrypt($secretKey1, $dataFormat['key1'], $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
openssl_public_encrypt($secretKey2, $dataFormat['key2'], $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
// Encoding the encrypted keys to base64
$dataFormat['key1'] = base64_encode($dataFormat['key1']);
$dataFormat['key2'] = base64_encode($dataFormat['key2']);

// AES Encryption
// generate random iv
$iv = openssl_random_pseudo_bytes(16);
// encrypt iv with the public key
openssl_public_encrypt($iv, $dataFormat['iv'], $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
$dataFormat['iv'] = base64_encode($dataFormat['iv']);
// Manually doing the padding - Zero padding
$pad = 32 - (strlen($data) % 32);
$clear = $data . str_repeat(chr(0), $pad);

// encrypt the data with the aes encryption key
$dataFormat['doc'] = openssl_encrypt($clear, 'AES-256-CBC', $secretKey, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv);
$dataFormat['doc'] = base64_encode($dataFormat['doc']);

// save file to the cloud which will then be fetch by the app in nativescript for decryption
// save cipher
file_put_contents($cipherfile, json_encode($dataFormat));
// $cipherfile is the .enc file that will be uploaded to the cloud

Sample pkcs#8 private key in pem format

-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCuLqpkKI65J7wd
e+UfjDf3B9iCwJbqZ5B4b68q+e6yZbYapi8KZT3E71KNxZXe0sMuBXM4XhlmjSMr
a5PM8FiXxQ5u1+1BAuRjyiWF13IVYPctedel4h9nPONdQ9xi7tTR4Ez+YtpJ3xbi
IGuvMQ8VblUCEIJskMhljei9hT2ioJ/xZ5IGwrntA2QUtxXUBaKv0hNlJSXTlfsW
9lzGfBl7USRD9fjb+6PJntFurkb3PEgskI4y667Ias5IYKhjxl2xfXjaZ1WzB/Xt
7RGsucLEJwevx8W4xYiOyzluM2Bh4vPLBHjpFTd0zcXdQCXoO0jXAHbdey1jX9M+
AQ2JdhFMfUd2neTgTDuB7G3zL4F+nn33P1k931AVPmCDasGQdoGJpSjo4meowLLP
1S7bQlxBUede8f9Jb0vKVd1Rr7WTVLnj8vOJR1H9f4K93hxbepaGXQMQM051snKf
nvDSE1I6tLDFHf2Jzo7rG6DDKAckcwlNJaPgLzjyMu7jaqs5x877r7L9mTPlgFYZ
QLpSy0eHtxNkpJL6hhSeovGgk3VeHsJjDwGxdqhqy4xGgH8m5jHcb4L4RFkyo4rh
MRDNLGVFvR0gMD0XVXPLKc6lCni+kndyJalAGAxUCKhUj9jd8JEjh46QU0WilOdK
2iIgA0gCSQg/3IwgHBKCTwuaWCXmnQIDAQABAoICADTmd1lz9+TD7J4Hws6SB20/
NMKBO1R1n6r0QH8M6THFMkllr2O428mz5Z6pI5tI535SqWu1KIG827GUOo9Db6So
dHm1mpjkB/lGq566kCtuB6QIHTaENoVkOKlastRZNMmJFdksrJ9C4fBLaGkMvUgE
+kxmNzZBwpsVPlpQ3SdHIUxv6CmlelJBdWPVNwvtqjs70igl4Bcitd+DcynVBugs
/JanXa1x/4x8b57tzx/HbyQZwxYz/iFgTsYslPe4hzwgn1O1jpGRGV/pYDTVNhHc
yLjaM3Kqi47Sqd6OO/d+tgwyj4Y6q10OIcnQcbLWToj92xvpDR/hOobaSwI0mYvg
rgZ3siie4zZgQLem3L0E4wECqSQcGft1STIs2LtjfZz4gabSLLXs2XGhOEy8vqo4
mbhzKubpv/MGBS9CELyqRaPabkLgwclWXxr1K79NDLeiYWnMgJbVftc8fUA28Xw0
vbBQHkLwR039ZEO/1rXk94frlzzLWWCpgfflqgjtG50bURVEvLHKTgV4pKbgPdSq
K/bguv6csFTitlGuzFJFee+p/aGc4Ank35/qG3W+fhPRybnA8OQgD9ESDIL/14Iz
/LR1wQPCVIPqIyffRETkVS4sNPvOzvbb7jf5m/DaNZcAicZHUFdahdCVjNT6GRYH
KfLNzZ3IbHfn044j1ZgBAoIBAQDnxHJYv024YVY9x889lkYqpbGC3Sr8ONNM7AhE
3dmyekmjvswEAybi+VvVXFGBa1E13hECI44ZU6gOhPxPfA0jgB01GkbpP0oJATXu
2u3hxHKxlusgI60u3y4GDJMs2lT1W7WUtTE4Kdf2azoOcxjYEmBZlnvcVekNNmrA
8pRurf9BxgrgZt+1YKjIVgHiYDNJyc5fU00FhAonZdXwlQqYQAWwzT9yeQx+TTt0
JmDO1UTohHn4PkgisrCX94GI/q5Kj6lyadi4uySq3+Hq9PsomqcUp9GKCkLV1VSH
Vj1ET+mvkq+ryBlw694XmwfhBWfo2o34cro6hC18FJKi8Js1AoIBAQDAZOE2bMVk
x8muw3dluIv7QLyb1vHkwv4/mDExRfl9U8wRe9G0+9zoOku7yORw8uf9h3BX8Z8V
Q35lFCZc7qI+XdA0+Pw02wIsubGnjiEi9JbEnjNNEM+jZmsSdFbsahG0tbdIEPMn
Iz62QrD4T9sYUopj7z0w2jz4AHqfaDN0pWHLK7S0dD6ADe5ToaWho/ldo7VjEFrh
6n0wfsU4TcqnRzzNkiKnJdCoFQA42z7Cu9eHb/CT/nPAYdjokGG0WJJcV3BUmOJg
Ua0ZLryBuaBRp9+gmtixQ9isLHezeDQkw3lacYAgM+F9VJgI8LvaDbbFbSe1LJfP
UJc9AQl3AyLJAoIBAQClSTILHJM+1Rv9/0tKrqrnqzcHDtei/JuklgfnpRel8xQl
VOKGUgEfiZn2dVojI/fMpMwYbGbgpQqLIEgYH233TVyHDHBZ06rm9RgTecqAYesx
v2F7kDXn4X4fDkS/jg1c1cSPgxDQ7vZ3ZE0JFQg4wBI6kdlPb20+4PoNFHLK5AfQ
Tn5fPr53ybFU/hFl8hlhnCwzS2L4dgwPwM5sPr5Jig8E4btH1hAU2tt1oBEWMH5u
HFwnr7MeG7VZC0gz/P0ra3z6fYXzaHNj8Tpap8+eS3ywfGYxPdQgT31y7wvOdfdA
ISprJc8O+wduaj+aOJK8O3FYJ+mr5avSe5F/5nupAoIBAFTv9HF3ocm1Xje2ec7B
o6L/2ISgZ1hMMbzXrtgHAXcv+Ia7eECDdOpdWjQEI74trvfBxHo+5LGZg4KSW8Fp
ZN1Nw26vgYDAM3yVWnYTjFOHUcxp31IlmtWTkK+sBvgGd9yk4M6DFSXxLG2Akep+
LsbVId2usAASSRpf88LOWPElVkBBAl0seK9F9zKayanL33pUnO6oOMdGI5C2VtFd
npstGw3Sd1P9oNmjs95mBJYqIaPcaoSbsHR9fipmG4xoEe2MeL4QZNiLFnVoqbZH
whxqlZKs6f+QaWbRluL3rWWRmtCwDRWqceFH6HsB9Jz2svsEDqSwlDDlbm9zNr+A
9ekCggEASDc9pzs3bjDTCqOB1MhbDdDBj+9skp9saiOJmCyuhtoNerx8nEGRB3QM
mHRafusbE9gMwZ8zvnxz3SOxw/76xG8IMxVpVB0zyNSFYeB78jU7HsbORzDUDCae
0afeSikfTCz1WU9VsWsspUfsA3meApENK5eu9xFfVkqCXj/l/1pElrAFUK1uXGlN
P6XeOiE7rZr+tkwD8phPB0+5sUfaMH4+sB84rCU1vizinrxntslCay0WjGyCY8sc
ea0/rJmd9TmBU6JIi5oLyZuNqxYBn8qD0jvkg7g6AhH7/TcpLMDAINeh4Hkj84Lx
LFzA9Qx4EmEK73S8UTxk/9eDLFMhfA==
-----END PRIVATE KEY-----

Sample output (to work with): The content of the file will look something like this.

{
  "docMimeType": "application/json",
  "hash": "253cd9d4f849084f276c649a5155512c5b0bb68c8f7cbde33cc529f68c672168",
  "key1": "dxUYOfiIEchLeha/Hu3dUd6MzucwABf8/Q9eUPgJAhEhmjrsf1MXnXOtcPCNGE/8bXEU6Iv3XIcLjImhaFBF8aYoDbS6GjnhPMeGpRQN8+raXrfm/5J9rlRjUImSyZUUnwgJ7vkHP22F9I8rrZywA5QgHiK97vyp7y2cv1H9y2tT5kukrDxhvWWBHoii+V0pA0SCEviPrlWjopyL/SpiaUfoG/L97Rdg7k181c1pHR5S+dWRAy190aU/IFo7VZXQ6FuqWd0TyoOEeBoAwrxTXG+03OXs7qqZwlFdHtCZ6ehrUl8Kkcbbv7O2PBOFsHnla5c+7aSDQ69xCHXmzoxB+dTbu5OY0E8TFWLD6V8QVe2Y5q79Nybf9r56YZDX6sqKK5uZqJDQB9wQUWHhM5hE7mzP7Ll1IVC4sAml+k2qrwJqOaDDN0eUidR+X31LXDEO9+y3g6NrlsNTROttiF0jC1oKyN5y+OOwxx0OEntXYUsM1Z8iQ9QQBHL418EF/FoEwmTVcg/7Lg20Qdgp21mv0ihMmYssB0EmopzH9yQ6d4eEp4Wh5krO2Ph73zJx6RvjVu1JfkoMmZ8fxwlHs+Xe09WE828NCWTW2WheP63V+fQZ07nm9c/BcITCmiQ9376wYh3+Hj+o6/N+et1nf0vO9zbooUI+V3Hd/m0wUnBxm+s=",
  "key2": "stkiCTyRmKfU7OhGfsqQyvi2UZ2syRjAdD4Sk0N/x+JMYX2Dr5rlwV+EH5TG388uvoPH7NSalPrprF+Nlm+YEVhYS0E5SBXJiIEFixT5AIQ9rhAHD/WFfxjvHainhiHhoLcX5rQ6C0zGCNUZaH0X4I4v8r575rbklTqSgCs3ueGDX9mAQKj2q5eJMIfRh+mAwQq7lhTk4TbGxL6LAyWxYL+Zw4R5ljUl6uK0o5Vwpn7I2xdAIQs6J7jW6jDfMT+5mbk06nrba09ysKdJILe8je97SyBRR5y+OHfJF5EArTSKwnLrAp/DXE40DP8BHOheL/PNxoKzIEkOX0Cu3aAXmHR6SLJxtwKGEHn5tzUvBzx54h2K5iEh2C8Wvkqqi7m+DcmxnrjYKEjmP7jzUvSMIob7EBcJ+wHvE9s5aPCK1MbKqK1UbHHkHRHsk8tgZsv/PC8A0+wdlMKI30GL1B9wcck7Fottgnc1eGzdFjbnEirLHeihaJPJOjkjiz1RtkOQHgd6l9bzUTMUnCq0melgSlut68akgvGM7kTtHhEY27sHj8UGF5zjTS+KPYzpi17Wp/9TgWpETxXsewvtBP9oCl/L+ZhhUrRihZOctSgTzFTZpIyrlx9ZDg0A0MU/Q2AyhBU0r9dzowyApDUu/TWPdj2Z9p/DX7c0i5y4IK7TOX0=",
  "iv": "Y2RdcAtnid52Ueu2MDdh2bY1aJBysk2UTstPdCAILU+RqcZCtgp+f5kLlRweG7Kgst88oZrJy6B/33F2WMqQ1qx1AqK+WthG+gVg15YC50OGaKGNuBAFSdFCJli2MLIE4s8ItXH2VmgxkGGE3ETuz3b7fheUJOUpUKcc3Kz0HmZiyo5eXsB4lNSXsaTP5Z8kaQcHDfacu9UORF9RUM2tfUH4IzAkU3ENPEM4cTP27HFRsFAeuXAcNAAYUQws9HVQFtg9pXAyuN0GUzD4GksL/3M0Y8CvITewLQDRHSBSljspPtF+lFqS3ZQTxAbtqxjEf6/Q1dCP1CIeiXEi2it8GEKZzWhH7385qW383JIBk1b9HjT7+26w2s7AqpLSevK5iOis+4sHzqEix9MHsOUp4OEcQ+BAYFkYiFlmPeo4tNgg9sRuIrgQwaEvCgHG2IoboURqCRs6iRnS4j8dHyvG5acavSEm0ozouqt4KBVoZt/chq8hpxH123kRIeuZH1qTsmzfSInAJLKpW2ky+nLifjOrSCA1D7ZDpfykACa1PpP+I6LgjMkWoQ/zkd70hz28fD+tpDWBJPngDcxnCz8UbZRma9psVEsw/4IOWQr3yy+V4Y9+xllLzZLy1YbEcdewsh/FXjLkhWqic3uPPyNlLWqzpHqBqTvgpPwU31zyJks=",
  "doc": // data is too large to be included in the post. If you want to see the full version of the sample output I created a gist in github https://gist.github.com/ketuvin/cba7e7215dbdd67ea8b10050ecbbcd3f
}

In nativescript this file will then be fetched and will be processed from JSON to array. And will then be decrypted.

Decryption in Nativescript

import * as cryptoJs from "crypto-js"
import { KJUR, KEYUTIL } from "jsrsasign"

/**
  @params encryptedFile - the file to be decrypted
  @params pem - the pkcs#8 private key (cipher=aes-256-cbc)
*/
decryptFile(encryptedFile, pem) {
    // load key
    let privateKey = KEYUTIL.getKey(pem);

    // Base64 Decode using cryptoJs
    let key1 = cryptoJs.enc.Base64.parse(encryptedFile['key1']).toString();
    let key2 = cryptoJs.enc.Base64.parse(encryptedFile['key2']).toString();
    let iv = cryptoJs.enc.Base64.parse(encryptedFile['iv']).toString();
    let doc = cryptoJs.enc.Base64.parse(encryptedFile['doc']).toString();

    console.log("Base64 Decoded Key1: "+key1);
    console.log("Base64 Decoded Key2: "+key2);
    console.log("Base64 Decoded IV: "+iv);

    // RSA Decryption of the keys and IV using jsrsasign
    let decryptedKey1 = KJUR.crypto.Cipher.decrypt(key1, privateKey, "RSAOAEP");
    let decryptedKey2 = KJUR.crypto.Cipher.decrypt(key2, privateKey, "RSAOAEP");
    let decryptedIV = KJUR.crypto.Cipher.decrypt(iv, privateKey, "RSAOAEP");

    console.log("RSA Decrypted Key1: "+decryptedKey1);
    console.log("RSA Decrypted Key2: "+decryptedKey2);
    console.log("RSA Decrypted IV: "+decryptedIV);

    // Concatenate the keys to make up the AES key
    let decryptedKey = decryptedKey1 + decryptedKey2;

    console.log("AES KEY (Key1+Key2): "+decryptedKey);

    // Convert to wordarray
    let parsedkey = cryptoJs.enc.Utf8.parse(decryptedKey);
    let parsedIV = cryptoJs.enc.Utf8.parse(decryptedIV);
    
    // AES Decryption using crypto-js
    let decrypted = cryptoJs.AES.decrypt(doc, parsedkey, {
        iv: parsedIV,
        mode: cryptoJs.mode.CBC,
        padding: cryptoJs.pad.ZeroPadding
    }).toString(cryptoJs.enc.Utf8);  // Error: Malformed Utf-8 data

    return decrypted;
}

In console, See actual log here

Base64 Decoded Key1: 0a5b90147517916c338fb8aa359d3f53a542eed1d482e42dd93eea0735a5d5a64dc219cdc703ba0a88cbf10b7feae82d2d4fe5a389f296cba8452b5e3371166c903aae2f64223f561821bb61f7be1ea41d96ac64d0e47da601041aef88ab9ded35c40240f7615e001f624b22966107539f98c499607e4b2760a162a963a854dffbfe955afec959b069101a49d1f765e729ff7035f0beb8ce06a1b3776a62f8c7db5b3efcfb1b0c1231f654e6aa075667621e99abbb6ba78a6fd81061c59de1024da81d2a8cc42dc84fa5af61c3d5b1a009cca2b4543e66974dce60f6631c490564be8494d0324fb1d7a8e7067feba5dd38653c936895247557e380e31e118af7a1886063e867acf58a3f63ab98bfa619b6a18518be68675a072d5a5217bc7ca4c5dba601ab7560e42dce1aadeca5a7fe0eba50f5d1c997eaf25d8dbbfded2cbea6402b121df8691f1673c243d1a8195e0af2a3b938fa271ecda93984ba9eef24912169ccabfd7a8f9fd2c4aa710e174f10d04914d7107c2a4fa54b463d002cc2347d4429604b2e1bb536a74f754f1f931362c90168e841ee05717e23323d627a938b582af62fb64c2d55442932517194eedf89d265523fe6f6ccdcd9c90fe5c76f9b3220c4a4fecfb4e84075167092b3971d52cfafba904f5b00ea14878df6b47bfbae29a97d1a4fa7caa995baddb6280f4939091fe2531d39b80759971d55ee
VM38 bundle.ad8287f8f3cbaa7f8ee5.hot-update.js:109 Base64 Decoded Key2: 2ba192411fb0a2ceeca359d389b4cacfa0f87f7a3a0c7168ad1e2e92d4952b4ab6465b7ad8975e2b735bd6c9489d4c34462df636ef3b4d7fdc6ed56cdb9cd3bab96539a420ca630721cb378df95371f1f7b10cf2dc9f7f149816b920be2a2dd0006052176ec10fee093672526e0b3eb8488a5ad1dee25a34e754c5c4b0a99478173501609bcb35dd5e99355b660485eee8f3fed8b3fcbc4873e760a303682ccd0f0aceea1c9da419bfec5d78619cbe5d22bfd34cc055e3ce7c3c596029b9c393b2039823e29b465ca4581e80015052c4ac4c63d9ffdafa995c0b1c2bc0cb10f194a99989467a7958956d29cea1efa271a6239110316fa9ea9f4bfe2f8a509db86e68245ae930f27b327e49f799757fc868985cfb93a24e7686b2d55966365ba506acd9e63e64b63ccf98024fcd472b2d69cee7a20904ba07556ac8ba6f05d8ec60c18e958dfb100b29d6409efb164caec3d35dfaf39f83ef59c3db1fa21fe684c6c13a403c52cec4b593b153e6ad79607f7f5e4dbfcb4c64ec058846278e06d0cdf72afee431a10bb12a2955ca78ea105da0607942d8af226e64fd2803310090f7e69c96fd634b82e946213bf073952bef8dcbdab11be4b197c144bacd10f6231c043795f855fd2042e2370bc64cdbefdbc9180e48b3d8aff957e8c998b92b8c5c2e8a2cc25f79c40f7633842b839313dbf6547b34d4d8f7974c09f54f9a20d8
VM38 bundle.ad8287f8f3cbaa7f8ee5.hot-update.js:110 Base64 Decoded IV: 4bba6438c3360e5d18080afc3edde5f3bcb81f3690cb152758b15ebf5377b6f4fe6b8008fce35fb7f966251b7405916626c3fe2caa61c9e183f87ced6b3f5cee8e336c32d23f0646c18c38aa2ce98dcfb9bfb3f32a02bdf9f3d16e36ef501d564316300203dc42b416c512530161952ddeef7d016b7af9b5c757ed6a43646efe34615db7674ec9446d1bf3aa8e8832a81b601bde8b2281c6fbe441d7bb2e8c3a7f26204e51078857b13d31b117df82850e20c6bf38110b7a52d62f78fd571204a50ae9ac3c11300eef05b48d43d7bd64532e50e9b03add4c826f9bb2804f8c5362f5f6ec5526219c2519f9e04742b92b52db45d7d717fd0fe43a2c31f59634a958811e07e095c3f9e4f8562ae999ef0761411d7de71751a80e0332d6aaa298d090c23790ee1439e8f5b2b9d8d80bf5834306a9be47a476f31dd9ec71c9e254eb48dce2139cc4170ca42f385ef408a2e8425a60b443ef41d8725a0399a81cfa53117c931c7c066f25614182117b18b9a1e5302a0bcc866560f2309d5c9d2dedcbed2782b1a3535d0b2532d0d7f7ff9d89296054fa12a00b54401fc4b098f2011506f25165618eae95bfcd83d097f1ca897e83528a22dfaa11c7978daa9fbca33b246f395417376162967275ecbdad7007c93b9b3e40377814c632926a01ff93b62dd583cb9ae4b5e5cb2fa338de4e3acaeb22be7622bfb6bbfbbf56dafb07e093
VM38 bundle.ad8287f8f3cbaa7f8ee5.hot-update.js:115 RSA Decrypted Key1: g.œ'Æ‹wvüXã2¸¶
VM38 bundle.ad8287f8f3cbaa7f8ee5.hot-update.js:116 RSA Decrypted Key2: lÖÑCò®|f.üØÁ}]
VM38 bundle.ad8287f8f3cbaa7f8ee5.hot-update.js:117 RSA Decrypted IV: KN¿N8ª1Ê°YëLr”e
VM38 bundle.ad8287f8f3cbaa7f8ee5.hot-update.js:119 AES KEY (Key1+Key2): g.œ'Æ‹wvüXã2¸¶lÖÑCò®|f.üØÁ}]
VM9 vendor.js:20948 ERROR Error: Malformed UTF-8 data


Solution

  • I solved the problem! (Technically though I was asking the wrong question). I realized it was because I parse the secretkey and IV using UTF-8 encoding when in fact I should have use Latin1. I checked the encoding of the data and it was ISO-8859-1 in which I recently learned that is also known as Latin1. So I change this chunk of code

    let parsedkey = cryptoJs.enc.Utf8.parse(decryptedKey);
    let parsedIV = cryptoJs.enc.Utf8.parse(decryptedIV);
    

    to

    let parsedkey = cryptoJs.enc.Latin1.parse(decryptedKey);
    let parsedIV = cryptoJs.enc.Latin1.parse(decryptedIV);
    

    and my aes decryption is now like this

    let decrypted = cryptoJs.AES.decrypt(doc, parsedkey, {
        iv: parsedIV,
        mode: cryptoJs.mode.CBC,
        padding: cryptoJs.pad.ZeroPadding
    }).toString(cryptoJs.enc.Latin1);
    

    Now I can process the pdf and display it natively in my app. Hope this helps with others who have a similar situation as mine.