php7使用openssl_encrypt函数进行AES加密
Posted 骷大人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了php7使用openssl_encrypt函数进行AES加密相关的知识,希望对你有一定的参考价值。
前言
手上有个api对接需求,要用到AES加密,要用到openssl_encrypt函数,记录一下,鉴权要求大概如下。
- 将明文先base64加密,后取前16位
- 判断字符串的字节型数据长度是否为16倍整,如不是则进行补充(PKCS#7标准)
- 对字符串进行AES加密后base64加密,其中
vi=A-16-Byte-String
介绍
openssl_encrypt($data, $method, $password, $options, $iv)
参数说明:
- $data 加密明文
- $method 加密方法 这里使用
AES-256-CBC
- $passwd 加密密钥
- $options 数据格式选项(可选)
OPENSSL_RAW_DATA
, OPENSSL_ZERO_PADDING,OPENSSL_NO_PADDING - $iv 密初始化向量(可选)
实现
将AES加密,解密(用不到),长度裁剪等功能先封装成函数
/**
* 对数据进行AES加密
* @param $data 明文
* @param $privatekey 秘钥
* @param $iv 密初始化向量
* @return 加密后的字符串
*/
public static function encrypt($data, $private, $iv)
$method='AES-256-CBC';
return base64_encode(openssl_encrypt($data, $method, $private, OPENSSL_RAW_DATA, $iv));
/**
* 对数据进行AES解密
* @param $data 密文
* @param $privatekey 秘钥
* @param $iv 密初始化向量
* @return 解密后的字符串
*/
public static function decrypt($data, $private, $iv)
$method='AES-256-CBC';
return openssl_decrypt(base64_decode($data), $method, $private, OPENSSL_RAW_DATA, $iv);
/**
* 对需要加密的明文进行填充补位
* @param $text 需要进行填充补位操作的明文
* @param $block_size 需要的位数
* @return 补齐明文字符串
*/
public static function pad($text,$block_size=16)
$text_length = strlen($text);
//计算需要填充的位数
$amount_to_pad = $block_size - ($text_length % $block_size);
if ($amount_to_pad == 0)
$amount_to_pad = $block_size;
//获得补位所用的字符
$pad_chr = chr($amount_to_pad);
$tmp = "";
for ($index = 0; $index < $amount_to_pad; $index++)
$tmp .= $pad_chr;
return $text . $tmp;
实现
$data='hello world';
$private='dfgdfhdhdfh';
$iv='A-16-Byte-String';
//截取前16位
$data= substr($data, 16);
//判断字符串的字节型数据长度是否为16倍整,不是进行补充
$data= self::pad($data);
//调加密方法
$AES_en_str = self::encrypt($data, $private, $iv);
2022-11-23补充:看网上资料,当openssl_encrypt的options=OPENSSL_RAW_DATA,会自动对字符串进行16位的PKCS#7标准填充。
解密用 PHP openssl_encrypt 加密的 C# 中的字符串
【中文标题】解密用 PHP openssl_encrypt 加密的 C# 中的字符串【英文标题】:Decrypt string in C# that was encrypted with PHP openssl_encrypt 【发布时间】:2013-11-12 05:06:47 【问题描述】:我有一个客户使用以下代码在 PHP 中加密一个字符串:
$password = 'Ty63rs4aVqcnh2vUqRJTbNT26caRZJ';
$method = 'AES-256-CBC';
texteACrypter = 'Whether you think you can, or you think you can\'t--you\'re right. - Henry Ford';
$encrypted = openssl_encrypt($texteACrypter, $method, $password);
导致此加密输出:MzVWX4tH4yZWc/w75zUagUMEsP34ywSYISsIIS9fj0W3Q/lR0hBrHmdvMOt106PlKhN/1zXFBPbyKmI6nWC5BN54GuGFSjkxfuansJkfoi0=
当我尝试在 C# 中解密该字符串时,它给了我一堆像这样的垃圾:Z�o�'*2��I4y�J6S��
��xz���9^�ED�fF
���گs�)�Q���i��$)�
我尝试更改填充,使用 AesManaged 而不是 RijndaelManaged,更改密钥大小,使用不同的密钥等。所有这些都会导致不同的垃圾字符串或各种异常。我必须在这里遗漏一些非常基本的东西,但我不确定此时还可以尝试什么。
这是我的解密代码(我无耻地从另一个 *** 问题中复制:openssl using only .NET classes)
class Program
//https://***.com/questions/5452422/openssl-using-only-net-classes
static void Main(string[] args)
var secret = "Ty63rs4aVqcnh2vUqRJTbNT26caRZJ";
var encrypted = "MzVWX4tH4yZWc/w75zUagUMEsP34ywSYISsIIS9fj0W3Q/lR0hBrHmdvMOt106PlKhN/1zXFBPbyKmI6nWC5BN54GuGFSjkxfuansJkfoi0=";
var yeah = OpenSSLDecrypt(encrypted, secret);
Console.WriteLine(yeah);
Console.ReadKey();
public static string OpenSSLDecrypt(string encrypted, string passphrase)
// base 64 decode
byte[] encryptedBytesWithSalt = Convert.FromBase64String(encrypted);
// extract salt (first 8 bytes of encrypted)
byte[] salt = new byte[8];
byte[] encryptedBytes = new byte[encryptedBytesWithSalt.Length - salt.Length - 8];
Buffer.BlockCopy(encryptedBytesWithSalt, 8, salt, 0, salt.Length);
Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length + 8, encryptedBytes, 0, encryptedBytes.Length);
// get key and iv
byte[] key, iv;
DeriveKeyAndIV(passphrase, salt, out key, out iv);
return DecryptStringFromBytesAes(encryptedBytes, key, iv);
private static void DeriveKeyAndIV(string passphrase, byte[] salt, out byte[] key, out byte[] iv)
// generate key and iv
List<byte> concatenatedHashes = new List<byte>(48);
byte[] password = Encoding.UTF8.GetBytes(passphrase);
byte[] currentHash = new byte[0];
MD5 md5 = MD5.Create();
bool enoughBytesForKey = false;
// See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM
while (!enoughBytesForKey)
int preHashLength = currentHash.Length + password.Length + salt.Length;
byte[] preHash = new byte[preHashLength];
Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
Buffer.BlockCopy(password, 0, preHash, currentHash.Length, password.Length);
Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + password.Length, salt.Length);
currentHash = md5.ComputeHash(preHash);
concatenatedHashes.AddRange(currentHash);
if (concatenatedHashes.Count >= 48)
enoughBytesForKey = true;
key = new byte[32];
iv = new byte[16];
concatenatedHashes.CopyTo(0, key, 0, 32);
concatenatedHashes.CopyTo(32, iv, 0, 16);
md5.Clear();
static string DecryptStringFromBytesAes(byte[] cipherText, byte[] key, byte[] iv)
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("key");
if (iv == null || iv.Length <= 0)
throw new ArgumentNullException("iv");
// Declare the RijndaelManaged object
// used to decrypt the data.
RijndaelManaged aesAlg = null;
// Declare the string used to hold
// the decrypted text.
string plaintext;
// Create a RijndaelManaged object
// with the specified key and IV.
aesAlg = new RijndaelManaged Mode = CipherMode.CBC, Padding = PaddingMode.None, KeySize = 256, BlockSize = 128, Key = key, IV = iv ;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
srDecrypt.Close();
return plaintext;
【问题讨论】:
【参考方案1】:这很有趣,需要跳入 PHP 源代码并获得一些有趣的结果。首先,PHP 甚至不使用密钥派生算法 just takes the bytes of the passphrase and pads it out with zero's 到所需的长度。这意味着不需要整个 DeriveKeyAndIV 方法。
由于上述原因,这意味着正在使用的 IV 是一个包含零的 16 字节数组。
最后,您的代码唯一的其他问题是您从中复制它的源在他们的加密实现中使用了盐,然后必须将其删除,PHP 也没有您这样做,因此删除盐字节是不正确的。
所以所有这些放在一起意味着您需要将OpenSSLDecrypt
方法更改为此。
public static string OpenSSLDecrypt(string encrypted, string passphrase)
//get the key bytes (not sure if UTF8 or ASCII should be used here doesn't matter if no extended chars in passphrase)
var key = Encoding.UTF8.GetBytes(passphrase);
//pad key out to 32 bytes (256bits) if its too short
if (key.Length < 32)
var paddedkey = new byte[32];
Buffer.BlockCopy(key, 0, paddedkey, 0, key.Length);
key = paddedkey;
//setup an empty iv
var iv = new byte[16];
//get the encrypted data and decrypt
byte[] encryptedBytes = Convert.FromBase64String(encrypted);
return DecryptStringFromBytesAes(encryptedBytes, key, iv);
最后,结果字符串的末尾有一些额外的字符,即一组 3 个ETX char,但这些应该很容易过滤掉。我实际上无法弄清楚这些是从哪里来的。
感谢@neubert 指出填充是标准PKCS 填充的一部分,如果您希望框架将其删除,只需在实例化RijndaelManaged
对象时将其指定为填充模式。
new RijndaelManaged Padding = PaddingMode.PKCS7 ;
【讨论】:
ETX 是 chr(3)。最后出现的 3 个 ETX 字符正是 PKCS 填充将导致的结果:en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 谢谢你。我无法弄清楚为什么 .NET 和 PHP 使用 16 字节密钥获得不同的 AES-256 密文。我(错误地)假设 .NET 和 PHP 会使用相同的 KDF,但从没想过 PHP 只会对密钥进行零填充。 我的数据被部分解密了?如何解决? @AlexandrSargsyan 我会检查您使用的 PHP 版本如何派生密钥,因为这可能已更改。 @DavidEwen 我修好了。问题出在 iv 键中。 PHP 用 iv 加密它。在 C# 中,您必须使用 iv 而不是空字节以上是关于php7使用openssl_encrypt函数进行AES加密的主要内容,如果未能解决你的问题,请参考以下文章