Encrypting/Decrypting File With Mcrypt

Encrypting / Decrypting file with Mcrypt

Since mcrypt is abandonware and no longer recommended to be used, here's an example using openssl.

class AES256Encryption
{
public const BLOCK_SIZE = 8;
public const IV_LENGTH = 16;
public const CIPHER = 'AES256';

public static function generateIv(bool $allowLessSecure = false): string
{
$success = false;
$random = openssl_random_pseudo_bytes(openssl_cipher_iv_length(static::CIPHER));
if (!$success) {
if (function_exists('sodium_randombytes_random16')) {
$random = sodium_randombytes_random16();
} else {
try {
$random = random_bytes(static::IV_LENGTH);
}
catch (Exception $e) {
if ($allowLessSecure) {
$permitted_chars = implode(
'',
array_merge(
range('A', 'z'),
range(0, 9),
str_split('~!@#$%&*()-=+{};:"<>,.?/\'')
)
);
$random = '';
for ($i = 0; $i < static::IV_LENGTH; $i++) {
$random .= $permitted_chars[mt_rand(0, (static::IV_LENGTH) - 1)];
}
}
else {
throw new RuntimeException('Unable to generate initialization vector (IV)');
}
}
}
}
return $random;
}

protected static function getPaddedText(string $plainText): string
{
$stringLength = strlen($plainText);
if ($stringLength % static::BLOCK_SIZE) {
$plainText = str_pad($plainText, $stringLength + static::BLOCK_SIZE - $stringLength % static::BLOCK_SIZE, "\0");
}
return $plainText;
}

public static function encrypt(string $plainText, string $key, string $iv): string
{
$plainText = static::getPaddedText($plainText);
return base64_encode(openssl_encrypt($plainText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv));
}

public static function decrypt(string $encryptedText, string $key, string $iv): string
{
return openssl_decrypt(base64_decode($encryptedText), static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
}
}

$text = '8SViI0Gz4r-p7A15YxkwjOBFuW*@NTtbm{U]D&E=~6yLM+adX'P;h3$,KJ%/eo>}<Rs:2#gZ.9fqn"Cv_^[(H\c!)?`Ql';
$key = 'secretkey';
$iv = AES256Encryption::generateIv();
$encryptedText = AES256Encryption::encrypt($text, $key, $iv);
$decryptedText = AES256Encryption::decrypt($encryptedText, $key, $iv);

printf('Original Text: %s%s', $text, PHP_EOL);
printf('Encrypted: %s%s', $encryptedText, PHP_EOL);
printf('Decrypted: %s%s', $decryptedText, PHP_EOL);

Output:

// Long string with lots of different characters
Original Text: 8SViI0Gz4r-p7A15YxkwjOBFuW*@NTtbm{U]D&E=~6yLM+adX'P;h3$,KJ%/eo>}<Rs:2#gZ.9fqn"Cv_^[(H\c!)?`Ql
Encrypted : rsiF4PMCMyvAp+CTuJrxJYGoV4BSy8Fy+q+FL8m64+Mt5V3o0HS0elRkWXsy+//hPjzNhjmVktxVvMY55Negt4DyLcf2QpH05wUX+adJDe634J/9fWd+nlEFoDutXuhY+/Kep9zUZFDmLmszJaBHWQ==
Decrypted : 8SViI0Gz4r-p7A15YxkwjOBFuW*@NTtbm{U]D&E=~6yLM+adX'P;h3$,KJ%/eo>}<Rs:2#gZ.9fqn"Cv_^[(H\c!)?`Ql

Old Answer

Try this PHP5 class for encryption using mcrypt. In this case it's using AES encryption. You'll want to change the key for each site you use it on. If you don't use it at least it may guide you on writing your own version of it.

<?php

class Encryption
{
const CIPHER = MCRYPT_RIJNDAEL_128; // Rijndael-128 is AES
const MODE = MCRYPT_MODE_CBC;

/* Cryptographic key of length 16, 24 or 32. NOT a password! */
private $key;
public function __construct($key) {
$this->key = $key;
}

public function encrypt($plaintext) {
$ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
$ciphertext = mcrypt_encrypt(self::CIPHER, $this->key, $plaintext, self::MODE, $iv);
return base64_encode($iv.$ciphertext);
}

public function decrypt($ciphertext) {
$ciphertext = base64_decode($ciphertext);
$ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
if (strlen($ciphertext) < $ivSize) {
throw new Exception('Missing initialization vector');
}

$iv = substr($ciphertext, 0, $ivSize);
$ciphertext = substr($ciphertext, $ivSize);
$plaintext = mcrypt_decrypt(self::CIPHER, $this->key, $ciphertext, self::MODE, $iv);
return rtrim($plaintext, "\0");
}
}

Usage:

$key = /* CRYPTOGRAPHIC!!! key */;
$crypt = new Encryption($key);
$encrypted_string = $crypt->encrypt('this is a test');
$decrypted_string = $crypt->decrypt($encrypted_string); // this is a test

Notes:

  • This class is not safe for use with binary data (which may end in NUL bytes)
  • This class does not provide authenticated encryption.

Decrypting an mcrypt file in .Net (C#)

MCrypt file format

Observations were made using mcrypt-2.6.7-win32, encrypting the following file with the command mcrpyt.exe --no-openpgp -V test_in.txt

test_in.txt unencrypted is 25 bytes in length, and the above command encrypts as follows, resulting in the file test_out.txt.nc which is 125 bytes in length.

+-------------+----------------------+----------------+---------------------------------------------+
| File Offset | Field Length (bytes) | Field Content | Description |
+-------------+----------------------+----------------+---------------------------------------------+
| 0 | 1 | 0x0 | Zero byte |
+-------------+----------------------+----------------+---------------------------------------------+
| 1 | 1 | 0x6d | m |
+-------------+----------------------+----------------+---------------------------------------------+
| 2 | 1 | 0x3 | Version |
+-------------+----------------------+----------------+---------------------------------------------+
| 3 | 1 | 0x40 | Flags - bit 7 set = salt, bit 8 set = no IV |
+-------------+----------------------+----------------+---------------------------------------------+
| 4 | 13 | rijndael-128 | Algorithm name |
+-------------+----------------------+----------------+---------------------------------------------+
| 17 | 2 | 32 | Key Size |
+-------------+----------------------+----------------+---------------------------------------------+
| 19 | 4 | cbc | Algorithm mode |
+-------------+----------------------+----------------+---------------------------------------------+
| 23 | 12 | mcrypt-sha1 | Key generator algorithm |
+-------------+----------------------+----------------+---------------------------------------------+
| 35 | 1 | 21 | Salt length + 1 |
+-------------+----------------------+----------------+---------------------------------------------+
| 36 | 20 | Salt data | Salt |
+-------------+----------------------+----------------+---------------------------------------------+
| 56 | 5 | sha1 | Check sum algorithm |
+-------------+----------------------+----------------+---------------------------------------------+
| 61 | 16 | IV data | Initialisation vector |
+-------------+----------------------+----------------+---------------------------------------------+
| 77 | 48 | Encrypted data | 25 original data + 20 check sum + 3 padding |
+-------------+----------------------+----------------+---------------------------------------------+
| TOTAL | 125 | | |
+-------------+----------------------+----------------+---------------------------------------------+

Observing the output in different scenarios, the following block/key/IV sizes are used:

+--------------+--------------------+------------+------------------+
| Algorithm | Block Size (bytes) | IV (bytes) | Key Size (bytes) |
+--------------+--------------------+------------+------------------+
| rijndael-128 | 16 | 16 | 32 |
+--------------+--------------------+------------+------------------+
| rijndael-256 | 32 | 32 | 32 |
+--------------+--------------------+------------+------------------+

The check sum is done on the original data before encryption, and appended to the end of the original data. The default check sum algorithm used is SHA-1 which results in a 20 byte hash. So, the original data of 25 bytes becomes 45 bytes. With a block size of 128 bits (16 bytes), that results in 3 bytes of padding to reach the block size of 48 bytes. With a block size of 256 bits (32 bytes), there would be 19 bytes of padding to get to 64 bytes. Zero bytes are used for padding, which is significant during decryption as these are not automatically removed since the size of the original data is not known.

Reading the header

Here is a code sample of reading the header and encrypted data at the tail of the file. Not all helper functions are included for brevity.

public void ReadHeader(Stream stream)
{
byte[] buffer = new byte[512];
stream.Read(buffer, 0, 3);
if (buffer[0] != 0x0) throw new FormatException($"First byte is not 0x0, invalid MCrypt file");
if ((char)buffer[1] != 'm') throw new FormatException($"Second byte is not null, invalid MCrypt file");
if (buffer[2] != 0x3) throw new FormatException($"Third byte is not 0x3, invalid MCrypt file");

byte flags = (byte)stream.ReadByte();
KeyGeneratorUsesSalt = (flags & (1 << 6)) != 0;
HasInitialisationVector = (flags & (1 << 7)) != 1;
AlgorithmName = ReadNullTerminatedString(stream);
stream.Read(buffer, 0, 2);
KeySize = BitConverter.ToUInt16(buffer, 0);
BlockSize = GetBlockSize(AlgorithmName);

var cipherModeAsString = ReadNullTerminatedString(stream);
CipherMode cipherMode;
if (Enum.TryParse<CipherMode>(cipherModeAsString, out cipherMode))
CipherMode = cipherMode;

KeyGeneratorName = ReadNullTerminatedString(stream);

if (KeyGeneratorUsesSalt)
{
var saltSize = ((byte)stream.ReadByte()) - 1;
Salt = new byte[saltSize];
stream.Read(Salt, 0, saltSize);
}

CheckSumAlgorithmName = ReadNullTerminatedString(stream);

if (HasInitialisationVector)
{
InitialisationVector = new byte[BlockSize / 8];
stream.Read(InitialisationVector, 0, BlockSize / 8);
}

int read = 0;
byte[] remainingData = null;
using (MemoryStream mem = new MemoryStream())
{
while ((read = stream.Read(buffer, 0, buffer.Length)) != 0)
{
mem.Write(buffer, 0, read);
remainingData = mem.ToArray();
}
}

EncryptedData = remainingData;
}

Key Generation

The key generator algorithm is specified in the header and by default in MCrypt format is mcrypt-sha1. Looking into the mcrypt source, that key is generated using the mhash library. It combines the passphrase with the salt to produce a key of the required number of bytes for the algorithm (32 bytes in both the cases I looked at). I translated the function _mhash_gen_key_mcrypt from the mhash library into C# as below - perhaps it's already in the .NET framework somewhere, but if so I couldn't find it.

public byte[] GenerateKeyMcryptSha1(string passPhrase, byte[] salt, int keySize)
{
byte[] key = new byte[KeySize], digest = null;
int hashSize = 20;
byte[] password = Encoding.ASCII.GetBytes(passPhrase);
int keyBytes = 0;

while (true)
{
byte[] inputData = null;
using (MemoryStream stream = new MemoryStream())
{
if (Salt != null)
stream.Write(salt, 0, salt.Length);
stream.Write(password, 0, password.Length);
if (keyBytes > 0)
stream.Write(key, 0, keyBytes);
inputData = stream.ToArray();
}

using (var sha1 = new SHA1Managed())
digest = sha1.ComputeHash(inputData);

if (keySize > hashSize)
{
Buffer.BlockCopy(digest, 0, key, keyBytes, hashSize);
keySize -= hashSize;
keyBytes += hashSize;
}
else
{
Buffer.BlockCopy(digest, 0, key, keyBytes, keySize);
break;
}
}

return key;
}

Decryption

We can use standard .NET crypto classes to do most of the decryption, passing in 32-byte key we generated by hashing the passphrase and salt and where we're using the 128-bit or 256-bit flavour based on the algorithm name from the header. We assign the initialisation vector (IV) we read from the header via rijndael.IV = InitialisationVector;.

/// <summary>
/// Decrypt using Rijndael
/// </summary>
/// <param name="key">Key to use for decryption that was generated from passphrase + salt</param>
/// <param name="keySize">Algo key size, e.g. 128 bit, 256 bit</param>
/// <returns>Unencrypted data</returns>
private byte[] DecryptRijndael(byte[] key, int keySize)
{
using (RijndaelManaged rijndael = GetRijndael(key, keySize))
{
rijndael.IV = InitialisationVector;
using (MemoryStream unencryptedStream = new MemoryStream())
using (MemoryStream encryptedStream = new MemoryStream(EncryptedData))
{
using (var cs = new CryptoStream(encryptedStream, rijndael.CreateDecryptor(), CryptoStreamMode.Read))
cs.CopyTo(unencryptedStream);

byte[] unencryptedData = RemovePaddingAndCheckSum(unencryptedStream.ToArray(), GetCheckSumLen());
return unencryptedData;
}
}
}

/// <summary>
/// Set algorithm mode/settings
/// </summary>
/// <param name="key">Key to use for decryption that was generated from passphrase + salt</param>
/// <param name="keySize">Algo key size, e.g. 128 bit, 256 bit</param>
/// <returns>Instance ready to decrypt</returns>
private RijndaelManaged GetRijndael(byte[] key, int keySize)
{
var rijndael = new RijndaelManaged()
{
Mode = CipherMode, // e.g. CBC
KeySize = keySize, // e.g. 256 bits
Key = key, // e.g. 32-byte sha-1 hash of passphrase + salt
BlockSize = BlockSize, // e.g. 256 bits
Padding = PaddingMode.Zeros
};

return rijndael;
}

Since the padding style is zero-bytes, these are not removed during decryption as we don't know the size of the original data at that point, so the decrypted data will always be a multiple of the block size no matter the size of the original data. It's also going to have the checksum appended to the end. We could simply remove all zero bytes from the tail of the decrypted block, but we'd risk corrupting the check sum and original data if that really ended on a zero byte.

So instead we could work backwards one byte at a time from the tail and use the check sum to validate when we have the correct original data.

/// <summary>
/// Remove zero padding by starting at the end of the data block assuming
/// no padding, and using the check sum appended to the end of the data to
/// verify the original data, incrementing padding until we match the
/// check sum or conclude data is corrupt
/// </summary>
/// <param name="data">Decrypted data block, including zero padding and checksum at end</param>
/// <param name="checkSumLen">Length of the checksum appended to the end of the data</param>
/// <returns>Unencrypted original data without padding and without check sum</returns>
private byte[] RemovePaddingAndCheckSum(byte[] data, int checkSumLen)
{
byte[] checkSum = new byte[checkSumLen];
int padding = 0;

while ((data.Length - checkSumLen - padding) > 0)
{
int checkSumStart = data.Length - checkSumLen - padding;
Buffer.BlockCopy(data, checkSumStart, checkSum, 0, checkSumLen);
int dataLength = data.Length - checkSumLen - padding;
byte[] dataClean = new byte[dataLength];
Buffer.BlockCopy(data, 0 , dataClean, 0, dataLength);

if (VerifyCheckSum(dataClean, checkSum))
return dataClean;

padding++;
}

throw new InvalidDataException("Unable to decrypt, check sum does not match");
}

The SHA1 20 byte check sum can be validated against the data simply as follows:

private bool VerifySha1Hash(byte[] data, byte[] checkSum)
{
using (SHA1Managed sha1 = new SHA1Managed())
{
var checkSumRedone = sha1.ComputeHash(data);
return checkSumRedone.SequenceEqual(checkSum);
}
}

And that's it, with the 128-bit after 3 attempts we should get the right check sum and corresponding original data, which we then return to the caller as the unencrypted original data.

php mcrypt - decrypting and encrypting files?

mcrypt isn't designed for handling PGP-encrypted files. Best to use GnuPG for them.

Decrypt mcrypt with openssl

If you encrypt in mcrypt without adding PKCS7 manually, mcrypt will happily pad your plaintext with NUL bytes.

OpenSSL will do PKCS7 padding for you whenever using aes-X-cbc. The unfortunate consequence of this is that if you have AES-CBC(NULL_PADDED(plaintext)) and try to decrypt it, openssl_decrypt will attempt to remove the padding and fail.

Compare http://3v4l.org/bdQe9 vs http://3v4l.org/jr68f and http://3v4l.org/K6ZEU

The OpenSSL extension does not currently offer you a way to say "This string is not padded, please don't strip the padding for me" and then remove the NUL bytes on your own. You must encrypt with PKCS7 padding in order for decryption to succeed.

Although this is a limitation of OpenSSL, it bears emphasizing that the only reason you're running into it is because mcrypt is terrible.

PHP: Encrypt and Decrypt with MCRYPT

Cannot use object of type stdClass as array in
C:\xampp\htdocs\MIAManagerNEWChris - Copy\php\getLogin.php on line 63

The error here identifies what went wrong. All of your encrypt/decrypt code is functioning correctly, but you're just misusing the output from json_decode(). In the subsequent code after json_decode(), you're accessing array elements but the original code is actually returning an object stdClass that looks like:

class stdClass#1 (2) {
public $userUsername =>
string(9) "testing55"
public $userPassword =>
string(7) "1234567"
}

So you merely need to either switch to object properties like $userLoginCredDecoded->userUsername instead of [] array syntax, or more easily pass TRUE as the second argument to json_decode() to force it to return an associative array.

json_decode($decrypted, TRUE);


Related Topics



Leave a reply



Submit