简体   繁体   中英

c# MCRYPT_RIJNDAEL_256 Encryption Decryption Class in php

I am trying to convert c# application into php but I stuck at a place where C# provides Security class for Encryption and decryption based on RIJNDAEL algo. I am trying to convert into php.

Note: I am Using php 7.2 so mcrypt is deprecated for this version.

C# code

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace pharmarackencryption
{
    class Program
    {
        private const string initVector = "aw90rela942f65u2";

        // This constant is used to determine the keysize of the encryption algorithm.
        private const int keysize = 256;

        public static string Encrypt(string plainText, string passPhrase = "testing")
        {
            byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null);
            byte[] keyBytes = password.GetBytes(keysize / 8);
            RijndaelManaged symmetricKey = new RijndaelManaged();
            symmetricKey.Mode = CipherMode.CBC;
            ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
            MemoryStream memoryStream = new MemoryStream();
            CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
            cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
            cryptoStream.FlushFinalBlock();
            byte[] cipherTextBytes = memoryStream.ToArray();
            memoryStream.Close();
            cryptoStream.Close();
            return Convert.ToBase64String(cipherTextBytes);
        }

        public static string Decrypt(string cipherText, string passPhrase = "testing")
        {
            byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
            byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
            PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null);
            byte[] keyBytes = password.GetBytes(keysize / 8);
            RijndaelManaged symmetricKey = new RijndaelManaged();
            symmetricKey.Mode = CipherMode.CBC;
            ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
            MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
            CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
            byte[] plainTextBytes = new byte[cipherTextBytes.Length];
            int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
            memoryStream.Close();
            cryptoStream.Close();
            return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
        }

        static void Main(string[] args)
        {
            Program p = new Program();
            string enc_password = Encrypt("437217");
            string dec_password = Decrypt("C9xJGa03dRQx9ePm0nLnHg==");
            Console.WriteLine(enc_password);
            Console.WriteLine(dec_password);
        }
    }
}

Encryption : C9xJGa03dRQx9ePm0nLnHg==

I found some what similar code in php like

PHP code:

<?php 
    // key/iv in ASCII binary data, $str base64
    function decrypt_stuff($key, $str, $iv) {
        // $plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($str), MCRYPT_MODE_CBC, $iv);
        $plaintext_dec = openssl_decrypt(base64_decode($str), "aes-256-cbc", $key,  OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv);
        return $plaintext_dec;
    }

    // key/iv in ascii binary data, $str ascii
    function encrypt_stuff($key, $str, $iv) {
        // $ciphertext = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $str, MCRYPT_MODE_CBC, $iv));
        if (($l = (strlen($str) & 15)) > 0) { $str .= str_repeat(chr(0), 16 - $l); }
        $ciphertext = base64_encode(openssl_encrypt($str, "aes-256-cbc", $key,  OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv));
        return $ciphertext;
    }

    echo encrypt_stuff("testing","437217","aw90rela942f65u2");
    //Result : LTbhEHjFgfa5PDJQXJEdKQ==

Both are going same thing but still result is different

Your PHP code produces different results because you're not using the same key. In your C# code you're using PasswordDeriveBytes to create the key, which is Microsoft's implementation of PBKDF1.

PHP doesn't support PBKDF1, but we could write a function that is compatible with C#. The code below is inspired from this great answer by Maarten Bodewes, translated to PHP.

function passwordDeriveBytes($password, $salt, $iterations = 100, $len = 32) {
    $key = $password . $salt;
    for($i = 0; $i < $iterations; $i++) {
        $key = sha1($key, true);
    }
    if (strlen($key) < $len) {
        $hx = passwordDeriveBytes($password, $salt, $iterations - 1, 20);
        $counter = 0;
        while (strlen($key) < $len) {
            $counter += 1;
            $key .= sha1($counter . $hx, true);
        }
    }
    return substr($key, 0, $len);
}

Also, you're bese64 encoding and padding your data manually. You're preforming zero byte padding, but in your C# code you're using PKCS7 (the default and preferred) padding. It's best to let openssl pad and encode your data.

function encrypt_stuff($key, $str, $iv) {
    return openssl_encrypt($str, "aes-256-cbc", $key, 0, $iv);
}

function decrypt_stuff($key, $str, $iv) {
    return openssl_decrypt($str, "aes-256-cbc", $key, 0, $iv);
}

Using the key derived from passwordDeriveBytes , this PHP code produces the same results as your C# code.

$key = passwordDeriveBytes("testing", null);
$enc = encrypt_stuff($key,"437217","aw90rela942f65u2");
echo $enc;
//C9xJGa03dRQx9ePm0nLnHg==

However, I don't recommend using this code for the following reasons.

  • It's best to use PBKDF2 for your key. You can use Rfc2898DeriveBytes in C#:

     Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, iterations); byte[] key = kdf.GetBytes(32); 

    and hash_pbkdf2 in PHP:

     $key = hash_pbkdf2("sha1", $password, $salt, $iterations, 32, true); 

    The salt should be at least 8 bytes long, and the number of iterations should be at least 10,000.

  • You're not using a salt. You should use a random salt for each password, it makes your keys stronger.

  • You're using a static IV. The IV should be unique and unpredictable. You can create a random IV with RNGCryptoServiceProvider :

     byte[] iv = new byte[16]; RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(iv); 

    and openssl_random_pseudo_bytes :

     $iv = openssl_random_pseudo_bytes(16); 

    You could use this code for the salt too.

  • You're not using authenticated encryption. If you're using PHP 7 you could use GCM, but unfortunately .NET doesn't provide any AEAD algorithms. However, you could use bouncycastle , if you choose to use authenticated encryption.


But your encryption code should produce different results every time you use it, because you should be using a random IV. You can decrypt the ciphertext correctly if you have the IV. The IV doesn't have to be secret; you could store it next to the ciphertext.

If you were using mcrypt with MCRYPT_RIJNDAEL_256 , you could still make your PHP code compatible with C#, as RijndaelManaged supports 256 bit block size.

symmetricKey.BlockSize = 256;

However you shouldn't use mcrypt because it's not maintained, and it's deprecated in PHP 7.

I am not familiar with the C# end of this question. However I will point out some information that may be relevant to your problem.

  • The descriptions in the RIJNDAEL functions refer to the block size, not the key size. For example MCRYPT_RIJNDAEL_256 states that you are using a block size of 256. It is not specifying a key size.
  • For the openssl AES functions you are specifying a key size. As an example aes-256-cbc specifies you are using a 256 bit key.
  • The block size for all the AES functions is a 128 bit. So you can use MCRYPT_RIJNDAEL_128 which matches the AES block size. You can use a 256 bit key with both the MCRYPT_RIJNDAEL_128 and the aes-256-cbc as they are both using the 128 bit block size.
  • So for these reasons aes-256-cbc can not be used with MCRYPT_RIJNDAEL_256 . They simply are not the same thing.

  • This one is redundant. If you are using the aes-256-cbc you need to make sure that you are using a 256 bit key. Obviously make sure that your are using the same key to encrypt and decrypt. Make sure your IV is the correct IV. I would make them both static for testing. Once you get it working tinker around with adding the IV to the cipher string on encrypt and separating the IV from the cipher text on decrypt

  • Use openssl_random_pseudo_bytes() to generate your 256 bit(32 byte)key. You can also use openssl_random_pseudo_bytes() to generate your IV.

On a side note. I would highly recommend using the LibSodium library. It is now native on the latest versions of PHP and has a C# library as well. You can find it on Github easy enough.

Once you get this going I would look at learning how to Authenticate/Verify your encryption. Here is a good starting link for that. Authentication Read

Hope that helps.

It is not recommended to store passwords at the database using an reversible algo, passwords should be stored using expensive hashes. You should consider switching your password storage to a hash like Argon2.

Check this: http://php.net/manual/en/function.password-hash.php

mcrypt has been moved. not removed. you may try installing this

sudo apt-get -y install gcc make autoconf libc-dev pkg-config
sudo apt-get -y install php7.2-dev
sudo apt-get -y install libmcrypt-dev
sudo pecl install mcrypt-1.0.1

PS: not tested

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM