接口联调里最麻烦的情况之一,就是对方还在用 DES,而你这边项目已经跑在 PHP 7 甚至更高版本,很多旧文章给出的 mcrypt_decrypt、mcrypt_encrypt 方案根本不能继续用。本文把一套可在 PHP 7 中运行的 DES 加解密代码重新梳理出来,同时说明它为什么能兼容 Java、C#,以及模式、IV、Base64 这些最容易在联调时出错的点该怎么判断。
如果你现在正遇到“同一份密钥在别的语言能解,在 PHP 里结果不对”的问题,可以直接先看下文的 PHP 实现和跨语言对照,再按模式、IV 是否全 0、是否 Base64 包装这几个条件逐项核对,通常就能定位差异。
PHP 7 以后为什么要改用 OpenSSL
很多旧项目里的 DES 实现建立在 mcrypt 扩展上,但这条路在 PHP 7 里已经不适合继续走了。原文里提到的问题非常典型:搜到的大量示例都只适用于 PHP 7 以下版本,而到了 PHP 7.1 以后,相关函数已经被弃用,PHP 7.2 起更是被移除。
Warning
This function has been DEPRECATED as of PHP 7.1.0 and REMOVED as of PHP 7.2.0. Relying on this function is highly discouraged.
这也是为什么现在做 DES、3DES 之类的兼容处理时,优先应该看 openssl_encrypt 和 openssl_decrypt。它们可以直接指定算法模式,例如 DES-ECB、DES-CBC,比继续围绕旧扩展修补更稳妥。
一份可直接使用的 PHP 7 DES 加解密实现
下面这段代码保留了原始实现,核心思路很直接:加密时使用 openssl_encrypt 输出原始二进制,再做一次 base64_encode;解密时先 base64_decode,再走 openssl_decrypt。这样可以和很多 Java、C# 接口常见的“密文以 Base64 传输”保持一致。

<?php
/**
* Des加解密,兼容java、C#
*/
class Des
{
/**
* 加密
* @author TechLee
* @param string $input
* @param string $key
* @param string $method
* @param string $iv
* @return string
*/
public static function encrypt($input, $key, $method = 'DES-ECB', $iv = null)
{
$iv = $iv ? $iv : self::createIv();
return base64_encode(openssl_encrypt($input, $method, $key, OPENSSL_RAW_DATA, $method == 'DES-ECB' ? '' : $iv));
}
/**
* 解密
* @author TechLee
* @param string $input
* @param string $key
* @param string $method
* @param string $iv
* @return string
*/
public static function decrypt($input, $key, $method = 'DES-ECB', $iv = null)
{
$iv = $iv ? $iv : self::createIv();
return openssl_decrypt(base64_decode($input), $method, $key, OPENSSL_RAW_DATA, $method == 'DES-ECB' ? '' : $iv);
}
/**
* 这个玩意相当于java里的
* byte[] iv = { 0, 0, 0, 0, 0,0, 0, 0 }
* 也相当于C#里的
* IV = new byte[8];
* @author TechLee
* @return [type] [description]
*/
public static function createIv()
{
return self::hexToStr("0000000000000000");
}
public static function hexToStr($hex)
{
$string = '';
for ($i = 0; $i < strlen($hex) - 1; $i += 2) {
$string .= chr(hexdec($hex[$i] . $hex[$i + 1]));
}
return $string;
}
}
这段实现里有几个关键点值得直接拎出来看:
1. 默认模式是 DES-ECB
方法签名中把 $method 默认设成了 DES-ECB。如果你的对接方没有特别说明模式,这里不能想当然。因为一旦对方实际使用的是 DES-CBC,哪怕密钥完全相同,结果也不会一致。
2. 只有 DES-ECB 不需要 IV
代码里专门做了判断:当模式是 DES-ECB 时,传给 OpenSSL 的 IV 为空字符串;其他模式则使用传入的 IV,或者默认走 createIv()。这一步决定了 ECB 和 CBC 等模式能否正确对齐。

3. 默认 IV 是 8 字节全 0
createIv() 返回的是 0000000000000000 对应的字节串,也就是 8 字节全 0。原文已经说明,这个写法对应 Java 中的:

byte[] iv = { 0, 0, 0, 0, 0,0, 0, 0 }
也对应 C# 中的:
IV = new byte[8];
这正是很多系统联调时的默认约定。如果对方文档里写了“IV 为空”“IV 为 00000000”“向量为 8 个 0”,通常都需要确认它到底是空字符串、十六进制文本,还是 8 字节的零值数组,三者不能混用。
4. 密文是 Base64 文本,不是十六进制字符串
无论加密还是解密,代码都把 Base64 作为传输层格式处理。这意味着如果你拿到的密文看起来像一串十六进制内容,而不是类似 QmFzZTY0... 这种 Base64 文本,那么就要先确认对接协议是不是同一种输出格式。
DES 模式怎么选,IV 又该怎么对齐
原文列出了一批 OpenSSL 可用的 DES 相关模式,并特别强调:只有 DES-ECB 不需要 IV。这一点在排查“解不开”时非常重要,因为不少问题不是代码写错,而是模式和向量没对上。
DES
DES-CBC
DES-CFB
DES-CFB1
DES-CFB8
DES-ECB
DES-EDE
DES-EDE-CBC
DES-EDE-CFB
DES-EDE-ECB
DES-EDE-OFB
DES-EDE3
DES-EDE3-CBC
DES-EDE3-CFB
DES-EDE3-CFB1
DES-EDE3-CFB8
DES-EDE3-ECB
DES-EDE3-OFB
DES-OFB
DES3
DESX
DESX-CBC
如果文章标题里提到“支持 ECB、CBC 等模式”,真正要理解的不是“把算法名写进去就行”,而是下面这几层要完全一致:
模式名必须一致
Java 里常见写法是 DES/CBC/PKCS5Padding,在 PHP OpenSSL 里通常对应到 DES-CBC。如果你把 Java 的 CBC 代码翻成了 PHP 的 DES-ECB,结果必然不一致。
IV 的值和长度必须一致
DES 的 IV 通常是 8 字节。原始实现使用的是全 0 IV,这和示例里的 Java、C# 保持一致。如果对方传来的是其他 IV,必须原样使用,不能继续套默认值。
填充方式也要一致
虽然示例代码没有单独展开填充参数,但 Java 代码里已经明确写出 PKCS5Padding。在常见 OpenSSL 实现中,使用对应 DES 模式时往往可以和这类填充对齐;一旦对方改成 NoPadding,结果又会发生变化。联调时如果模式、IV 都没问题,接下来就该看填充。
为什么这份实现能兼容 Java、C#
这份 PHP 代码之所以能和 Java、C# 互通,靠的不是“同样叫 DES”这么简单,而是几项细节对齐了:同样的密钥、同样的模式、同样的 IV,以及同样的密文编码方式。
原文附了 C# 和 Java 的解密示例,正好可以作为对照。
C# 解密示例
public static string DESDecrypt(string input, string key)
{
System.Security.Cryptography.DESCryptoServiceProvider des = new System.Security.Cryptography.DESCryptoServiceProvider();
des.Key = Encoding.UTF8.GetBytes(key);
des.IV = new byte[8];
System.Security.Cryptography.ICryptoTransform cTransform = des.CreateDecryptor(des.Key, des.IV);
System.IO.MemoryStream mStream = new System.IO.MemoryStream();
System.Security.Cryptography.CryptoStream cStream = new System.Security.Cryptography.CryptoStream(mStream, cTransform, System.Security.Cryptography.CryptoStreamMode.Write);
byte[] byt = Convert.FromBase64String(input);
cStream.Write(byt, 0, byt.Length);
cStream.FlushFinalBlock();
cStream.Close();
return Encoding.UTF8.GetString(mStream.ToArray());
}
这里可以直接看到两个关键信号:一是 des.IV = new byte[8];,说明它使用的是 8 字节全 0 IV;二是 Convert.FromBase64String(input),说明输入密文经过了 Base64 编码。这两点都和上面的 PHP 代码完全对齐。
Java 解密示例
public static String decode(byte[] DESkey, String data) throws Exception {
DESKeySpec keySpec = new DESKeySpec(DESkey);// 设置密钥参数
byte[] DESIV = { 0, 0, 0, 0, 0,0, 0, 0 };// 设置向量
AlgorithmParameterSpec iv = new IvParameterSpec(DESIV);// 加密算法的参数接口,IvParameterSpec是它的一个实现
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");// 获得密钥工厂
SecretKey key = keyFactory.generateSecret(keySpec);// 得到密钥对象
Cipher deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
deCipher.init(Cipher.DECRYPT_MODE, key, iv);
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] pasByte = deCipher.doFinal(base64Decoder.decodeBuffer(data));
return new String(pasByte, "UTF-8");
}
Java 这段更直观地说明了兼容条件:它明确使用 DES/CBC/PKCS5Padding,向量是 8 个 0,密文同样先经过 Base64 解码。也就是说,如果 PHP 端要和这段 Java 代码互通,调用时就不该继续用默认的 DES-ECB,而应该改成 DES-CBC,并保证 IV 同样为 8 字节全 0 或者与对方约定值一致。
换句话说,这份 PHP 工具类并不是“天然兼容所有 Java/C# DES 代码”,而是提供了一个可调模式和 IV 的实现。真正决定能否互通的,仍然是双方参数是否严格一致。
联调时最容易出错的几个地方
基于原文这组示例,实际对接时最值得优先检查的是下面几项。
把 ECB 和 CBC 搞混
PHP 示例默认是 DES-ECB,但 Java 示例用的是 DES/CBC/PKCS5Padding。如果你直接拿默认参数去解 Java 侧的密文,大概率会失败。这不是代码库的问题,而是调用参数没有改。
IV 看起来一样,实际类型不一样
很多文档把 IV 写成“0000000000000000”,有人会把它当字符串传进去,有人会把它当十六进制转字节,也有人直接用 8 字节零数组。原文里的 hexToStr("0000000000000000") 实际返回的是字节串,不是字符 0 拼起来的文本,这个区别必须看清。
忘了 Base64 这一层
示例代码的输入输出都不是裸二进制。加密后用 base64_encode,解密前先 base64_decode。如果你拿对方返回的字符串直接送进 openssl_decrypt,没有先做 Base64 解码,结果自然不对。
以为“支持很多模式”就可以随便切
OpenSSL 确实支持多种 DES 相关模式,但接口联调不是本地测试,双方只要有一个参数不同,最终结果就不会一致。模式列表的意义更多是告诉你 PHP 侧有能力对齐对方,而不是可以任意替换。
实际排查时,建议按这个顺序核对:先看对方到底是 DES-ECB 还是 DES-CBC;再看 IV 是否为全 0 以及是否为 8 字节;然后确认密文是否经过 Base64;最后再检查密钥字节和填充方式。顺着这个顺序看,通常比盲目换代码更有效。
这套 PHP 7 写法适合什么场景
如果你的目标是把旧版 DES 接口迁到 PHP 7 环境,并继续兼容 Java、C# 服务端或客户端,这套基于 openssl_encrypt 和 openssl_decrypt 的写法是可以直接使用的。它解决的关键问题不是“发明了新算法”,而是用 PHP 7 可用的方式,把模式、IV 和 Base64 这些跨语言联调条件统一起来。
需要注意的是,默认参数只是示例起点,不是最终标准答案。尤其在和 Java 的 DES/CBC/PKCS5Padding 互通时,应显式传入 DES-CBC,并确认 IV 与对端一致。只要把这些条件对齐,PHP 7 下处理 DES 的兼容需求通常就能落地。













