PHP AES/Rijndael Encryption Confusion

AES is not exactly synonymous with “Rijndael”. AES is a (restricted) variant of Rijndael.

AES has a fixed block size of 128 bits and a key size of 128, 192, or 256 bits, whereas Rijndael is specified with block and key sizes in any multiple of 32 bits, both with a minimum of 128 and a maximum of 256 bits.

PHP provides a general implementation Rijndael algorithm. The PHP mcrypt API is unintentionally misleading because most users would think that specifying MCRYPT_RIJNDAEL_256 means that you’ll get 256-bit encryption. This is NOT the case.  The MCRYPT_RIJNDAEL_256 is actually settinig the block size of the algorithm (not the strength).

  • See this for detailed information about PHP encryption (mcrypt)
  • The AES encryption standard is defined as Rjindael encryption (128-bit, 192-bit, or 256-bit) using a block size of 16 bytes. Chilkat implements the AES encryption standard.
  • When you specify MCRYPT_RIJNDAEL_256 in PHP, you are *NOT* setting the encryption strength to 256-bits. You are setting the block size to 256 bits. This is NOT AES encryption. To properly produce 256-bit AES encryption in PHP, you must provide a 32-byte encryption key (which implicitly sets the encryption strength), but the block size must be set to MCRYPT_RIJNDAEL_128 (16 bytes).

Getting Started with AES Decryption

This is a common question: You receive encrypted data and a key and want to decrypt. The person providing the encrypted data has provided little information, perhaps only that the encryption algorithm is AES. Where to do you begin, and what additional information, if any, do you need?

Answer:

AES encryption comes in 3 key sizes: 128-bit, 192-bit, and 256-bit. Look at the key you received. Which of the following does it look like:

  1. zxcv1234abcdQWER
  2. 7A786376313233346162636451574552
  3. enhjdjEyMzRhYmNkUVdFUg==

The strings above are all the same key encoded differently.

#1 is a us-ascii string that is exactly 16 characters.  This is a clue that the person gave you a 128-bit key (16 bytes * 8 bits/byte = 128) and that the bytes used for the key are the ascii values of the characters in the string.

#2 is a hexidecimal representation of #1.  If you have a hexidecimal representation of the key, you’ll notice that only the characters 0-9 and A-F (or a-f) are used.  Each byte of the key is represented by 2 ascii bytes.  If your hex string is 32 characters, you have a 16-byte key (and therefore 128-bit encryption).

#3 is a base64 encoded representation of #1.  The tell-tale signs of Base64 are:  It is often a string ending in “=” or “==”, and it is not a multiple of 16 characters in length, and it uses characters not valid in a hex string.  A base64 string will be about 1/3rd longer than the binary bytes it represents.  Thus it is longer than our ascii representation, but shorter than the hex representation.  Therfore, if it’s between 16 and 32 bytes, you can guess 128-bit encryption.  if longer than 32-bytes, it’s 256-bit encryption.

So… once you understand the key, you can set the KeyLength and secret key:

cryptObject.KeyLength = 128;

// If the key is represented as an ascii string:
cryptObject.SetEncodedKey(keyStr, "ascii");

// If the key is represented as an hexidecimal string:
cryptObject.SetEncodedKey(keyStr, "hex");

// If the key is represented as an base64 string:
cryptObject.SetEncodedKey(keyStr, "base64");

OK, the KeyLength and the secret key are specified. What’s left?
You need to know the following:

  • CBC or ECB mode?
  • If CBC mode, what is the initialization vector (IV)
  • Padding scheme?
  • Format of your encrypted data?

Chances are more likely that it is CBC mode (which stands for cipher block chaining).  If so, you need an initialization vector.  This will always be 16 bytes long, regardless of the key length.  If no IV is provided, then it’s probable that it is assumed to be all NULL bytes, and this is the default w/ the Chilkat component.

If you have the IV, then examine it just like you did for the key, and call SetEncodedIV just like you called SetEncodedKey, passing the correct encoding (“ascii”, “hex”, or “base64”) for the 2nd argument.

If ECB mode is used, then set the CipherMode property = “ecb”

cryptObject.CipherMode = "ecb";

The PaddingScheme property may be initially left at the default value (which is the most commonly used).  My suggestion is to test with an amount of data that is more than 16 bytes.  The reason is that if everything is correct *except* the PaddingScheme, then your decrypted output will be correct except for the very last 16 bytes.  Once you know that all is correct except for the padding scheme, you can test with different PaddingScheme values.  If you only have a very short amount of data for testing, then it’s not possible to make this distinction.

Finally, look at the encrypted data itself.  Is it hex or base64?  If it is a “string” it must be one or the other.  You’ll want to set the EncodingMode property equal to the encoding of the encrypted data:

cryptObject.EncodingMode = "hex";

Assuming the decrypted result is a string, you’ll call DecryptStringENC.  The “ENC” in the function name indicates that the input is an encoded string and that the encoding is specified by the EncodingMode property.  It returns a string — your decrypted data.

string decryptedStr = cryptObject.DecryptStringENC(encryptedStr);

Matching MySQL’s AES_ENCRYPT Functions

The following example programs demonstrate how to match MySQL’s AES_ENCRYPT function in different programming languages:

ASP: Match MySQL AES_ENCRYPT Function

SQL Server: Match MySQL AES_ENCRYPT Function

C#: Match MySQL AES_ENCRYPT Function

C++: Match MySQL AES_ENCRYPT Function

MFC: Match MySQL AES_ENCRYPT Function

C: Match MySQL AES_ENCRYPT Function

Delphi: Match MySQL AES_ENCRYPT Function

Visual FoxPro: Match MySQL AES_ENCRYPT Function

Java: Match MySQL AES_ENCRYPT Function

Perl: Match MySQL AES_ENCRYPT Function

PHP: Match MySQL AES_ENCRYPT Function

Python: Match MySQL AES_ENCRYPT Function

Ruby: Match MySQL AES_ENCRYPT Function

VB.NET: Match MySQL AES_ENCRYPT Function

Visual Basic: Match MySQL AES_ENCRYPT Function

VBScript: Match MySQL AES_ENCRYPT Function

C# Encrypting/Decrypting with Stream

This C# example demonstrates how to use Chilkat.Crypt2 to encrypt and decrypting using the .NET FileStream class:

    Chilkat.Crypt2 crypt = new Chilkat.Crypt2();
    bool success = crypt.UnlockComponent("Anything for 30-day trial");
    if (!success)
    {
        MessageBox.Show(crypt.LastErrorText);
        return;
    }

    crypt.CryptAlgorithm = "aes";
    crypt.CipherMode = "cbc";
    crypt.KeyLength = 128;

    crypt.SetEncodedIV("0000000000000000", "hex");
    crypt.SetEncodedKey("abcdefghijklmnop", "ascii");

    // Open input and output files as file streams...
    FileStream fsIn = File.OpenRead("hamlet.xml");
    FileStream fsOut = File.Create("encrypted.dat");

    crypt.FirstChunk = true;
    crypt.LastChunk = false;
    byte [] encryptedChunk;

    // Encrypt the stream in 1024 byte chunks.
    byte[] b = new byte[1024];

    int n;
    while ((n = fsIn.Read(b, 0, b.Length)) > 0)
    {
        if (n < b.Length)
        {
            // Don't encrypt the full 1024 bytes, only the amount read...
            byte[] tmp = new byte[n];
            int i;
            for (i = 0; i < n; i++) tmp[i] = b[i];
            encryptedChunk = crypt.EncryptBytes(tmp);
        }
        else
        {
            encryptedChunk = crypt.EncryptBytes(b);
        }

        fsOut.Write(encryptedChunk, 0, encryptedChunk.Length);
        crypt.FirstChunk = false;
    }
    fsIn.Close();

    // Flush any remaining encrypted data.
    crypt.LastChunk = true;
    byte[] empty = { };
    encryptedChunk = crypt.EncryptBytes(empty);
    if (encryptedChunk.Length > 0)
    {
        fsOut.Write(encryptedChunk, 0, encryptedChunk.Length);
    }
    fsOut.Close();


    // Now decrypt in chunks.  The decryptor must know when the last
    // block is being processed so it may unpad it correctly.

    System.IO.FileInfo fi = new System.IO.FileInfo("encrypted.dat");
    long fileSize = fi.Length;
    int numChunks = (int)fileSize / b.Length;

    fsIn = File.OpenRead("encrypted.dat");
    fsOut = File.Create("decrypted.xml");

    crypt.FirstChunk = true;
    crypt.LastChunk = false;
    byte[] decryptedChunk;

    int idx;
    for (idx = 0; idx <= numChunks; idx++)
    {
        n = fsIn.Read(b, 0, b.Length);

        // Is this the last chunk?
        if (idx == numChunks)
        {
            // Decrypt only the amount read...
            byte[] lastBlock = new byte[n];
            int i;
            for (i = 0; i < n; i++) lastBlock[i] = b[i];
            crypt.LastChunk = true;
            decryptedChunk = crypt.DecryptBytes(lastBlock);
        }
        else
        {
            decryptedChunk = crypt.DecryptBytes(b);
        }

        fsOut.Write(decryptedChunk, 0, decryptedChunk.Length);
        crypt.FirstChunk = false;
    }
    fsIn.Close();
    fsOut.Close();

    MessageBox.Show("Finished!");