AES-256-CBC with PKCS#5 Padding in PHP
- Cipher: AES in CBC mode with PKCS#5 padding (equivalent to PKCS#7 for AES)
- Key size: 256 bits
- IV: all zeros, 16 bytes (AES block size)
- Ciphertext encoding: hex
Common pitfalls include using ECB instead of CBC, omitting PKCS#5 padding, mismatching key sizes, or using a nonzero/encorrect IV length. AES-CBC requires a 16-byte IV.
PKCS#5 padding helpers
<?php
function pkcs5_pad(string $data, int $blockSize = 16): string {
$padLen = $blockSize - (strlen($data) % $blockSize);
return $data . str_repeat(chr($padLen), $padLen);
}
function pkcs5_unpad(string $data, int $blockSize = 16): string {
$len = strlen($data);
if ($len === 0 || $len % $blockSize !== 0) {
return '';
}
$padLen = ord($data[$len - 1]);
if ($padLen < 1 || $padLen > $blockSize) {
return '';
}
// Verify padding bytes
for ($i = 1; $i <= $padLen; $i++) {
if (ord($data[$len - $i]) !== $padLen) {
return '';
}
}
return substr($data, 0, $len - $padLen);
}
All-zero IV
<?php
function zero_iv(int $len = 16): string {
return str_repeat("\x00", $len);
}
// Alternative equivalent construction via pack (16 zero bytes):
function zero_iv_pack(): string {
$blk = pack('N', 0); // 4 zero bytes
return str_repeat($blk, 4); // 16 bytes total
}
AES-256-CBC encryption/decryption using OpenSSL (manual PKCS#5)
<?php
function aes256cbc_encrypt(string $plaintext, string $secret): string {
$key = hash('sha256', $secret, true); // 32 bytes
$iv = zero_iv(16); // 16 zero bytes
$padded = pkcs5_pad($plaintext, 16);
$raw = openssl_encrypt(
$padded,
'AES-256-CBC',
$key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
$iv
);
return bin2hex($raw);
}
function aes256cbc_decrypt(string $cipherHex, string $secret): string {
$key = hash('sha256', $secret, true);
$iv = zero_iv(16);
$raw = hex2bin($cipherHex);
$padded = openssl_decrypt(
$raw,
'AES-256-CBC',
$key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
$iv
);
return pkcs5_unpad($padded, 16);
}
Quick self-check
<?php
$pass = 'test-key';
$msg = 'test str';
$cipher = aes256cbc_encrypt($msg, $pass);
$plain = aes256cbc_decrypt($cipher, $pass);
var_dump($msg === $plain); // bool(true)
Legacy mcrypt-compatible variant (for outdated environments)
<?php
function aes256cbc_encrypt_mcrypt(string $plaintext, string $secret): string {
$key = hash('sha256', $secret, true); // 32 bytes
$iv = zero_iv(16);
$enc = mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
$key,
pkcs5_pad($plaintext, 16),
MCRYPT_MODE_CBC,
$iv
);
return bin2hex($enc);
}
function aes256cbc_decrypt_mcrypt(string $cipherHex, string $secret): string {
$key = hash('sha256', $secret, true);
$iv = zero_iv(16);
$dec = mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
$key,
hex2bin($cipherHex),
MCRYPT_MODE_CBC,
$iv
);
return pkcs5_unpad($dec, 16);
}