phpreact-nativeencryptioncryptojszero-padding

PHP encrypted data needs to be decrypted in ReactNative


I am using following snippet to do AES encryption for CBC 256 mode in PHP.

$iv_real = "ahc/2u6F0Yvww12fyQiZWA==";
$decoded_iv = base64_decode($iv_real);
$plaintext_shared_secret = "9b8a3e600073de05e5d095b5d909043e50f5047ffcd0048c01c65ca690b7b4e981e51b59641d4ffd5a140c27f25a761ab0f99e601b59c5ae3427c751bfae9331";
echo "Shared secret: {$plaintext_shared_secret}\r\n";
$aes_key = hash("sha256", "s@keypact.appa62f1bed41166b2c455d82337222723b0287d920"); 
$encrypted_shared_secret = openssl_encrypt(
    $plaintext_shared_secret, 
    "aes-256-cbc", 
    $aes_key,
    OPENSSL_ZERO_PADDING,
    $decoded_iv //Binary data
);
//Base64 encoded, encryped shared secret
echo "\r\nEncrypted, base64_encoded, shared secret\r\n";
var_dump($encrypted_shared_secret);

Encrypted response I am getting is no where close to what I am getting in React Native. I need to Decrypt same data in ReactNative. I have tried

  1. React-native-simple-crypto
  2. react-native-crypto-js

But none of them seem to be working and giving the decrypted data or even get encrypted data same like I am getting using PHP.

Padding support is not in any react modules.

I had opted for ReactNative Framework imagining that its most advanced framework, but I have wasted many days in figuring this Out, so not sure how to fix this.

Following is React Code

async DecryptSharedSecret() {
//    var plaintext_secret = "9b8a3e600073de05e5d095b5d909043e50f5047ffcd0048c01c65ca690b7b4e981e51b59641d4ffd5a140c27f25a761ab0f99e601b59c5ae3427c751bfae9331";
var strIV = 'ahc/2u6F0Yvww12fyQiZWA==';
var iv_bin = Base64.Decode(strIV, strIV.length);
// Decoded value IV is in Binary when converted to Hex its 6a173fdaee85d18bf0c35d9fc9089958

var shared_secret =
  'n4gIdzwY5UsOpzGtslpRlyNjLwLla7sJWfGkfH0GHadPFjtsOxNGJgqRe9WnjYkbNjCgwvvAvQtYV1kFHTpCIS4zDJa3h/2ADHrDgC7ym3HUyMeVIWgFKRCZYeVKz8SEHmullNxWG6dCjsWEbK6yqVmpNfwJSeh0XHHDFe0/Sak=';
var shared_secret_bin = Base64.Decode(shared_secret, shared_secret.length);
// Decoded Value shared_secret is in Binary when converted to HEx for printing, Value is
// '9f8808773c18e54b0ea731adb25a519723632f02e56bbb0959f1a47c7d061da74f163b6c3b1346260a917bd5a78d891b3630a0c2fbc0bd0b585759051d3a42212e330c96b787fd800c7ac3802ef29b71d4c8c79521680529109961e54acfc4841e6ba594dc561ba7428ec5846caeb2a959a935fc0949e8745c71c315ed3f49a9'

var strKey = 's@keypact.appa62f1bed41166b2c455d82337222723b0287d920';
var keysha256 = await RNSimpleCrypto.SHA.sha256(strKey);
console.log('Key SHA 256 ===== >> ', keysha256);
// var key =
// '074bf4849734a4c3a653fc213f2734d23f5dc63ce9d6244f386276097f032ad7';

let bytes = CryptoJS.AES.decrypt(
  shared_secret_bin,
  CryptoJS.enc.Hex.parse(keysha256),
  {
    iv: iv_bin,
    mode: CryptoJS.mode.CBC,
  },
);
var decrypted = bytes.toString(CryptoJS.enc.Utf8);
console.log('Decrypted Data ', decrypted);

} }


Solution

  • The result of your PHP code is:

    n4gIdzwY5UsOpzGtslpRlyNjLwLla7sJWfGkfH0GHadPFjtsOxNGJgqRe9WnjYkbNjCgwvvAvQtYV1kFHTpCIS4zDJa3h/2ADHrDgC7ym3HUyMeVIWgFKRCZYeVKz8SEHmullNxWG6dCjsWEbK6yqVmpNfwJSeh0XHHDFe0/Sak=
    

    The following CryptoJS code gives the same result:

    // IV
    var iv_real = "ahc/2u6F0Yvww12fyQiZWA==";
    var decoded_iv = CryptoJS.enc.Base64.parse(iv_real);
    
    // Key
    var hash = CryptoJS.SHA256("s@keypact.appa62f1bed41166b2c455d82337222723b0287d920");
    var hashHex32 = hash.toString(CryptoJS.enc.Hex).substring(0,32);
    var aes_key = CryptoJS.enc.Utf8.parse(hashHex32);
    
    // Plaintext
    var plaintext_shared_secret = "9b8a3e600073de05e5d095b5d909043e50f5047ffcd0048c01c65ca690b7b4e981e51b59641d4ffd5a140c27f25a761ab0f99e601b59c5ae3427c751bfae9331";
    
    var encrypted = CryptoJS.AES.encrypt(
        plaintext_shared_secret, 
        aes_key, 
        {
            iv: decoded_iv,
            padding: CryptoJS.pad.NoPadding 
        });
    
    console.log(encrypted.toString().replace(/(.{56})/g,'$1\n'));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

    In the PHP code the SHA256-hash is returned hex encoded and thus consists of 64 characters or 64 bytes. openssl_encrypt uses for AES-256 only the first 32 bytes, so in the CryptoJS code the key must be shortened accordingly. It would make more sense to return the hash as binary data, so it would be 32 bytes long. Furthermore, using the hex encoded data can cause cross-platform problems if the hex-encoded data is uppercase in one environment and lowercase in the other.
    Another point is that padding is disabled in the PHP code (because of the OPENSSL_ZERO_PADDING flag), so it must be disabled in CryptoJS code as well. This means that only plaintexts whose length is an integer multiple of the block size (16 bytes for AES) can be encrypted, which is true for the posted plaintext (with a length of 128 bytes).
    Since the plaintext appears to be hex encoded in the PHP code, an optimization would be to hex decode the plaintext before encryption, which would halve the amount of data.
    Please note that a static IV is insecure. Instead, a randomly generated IV should be used for each encryption.