何时加密需要填充?

我在这里问了一个问题, 为什么AES java解密会返回额外的字符? 关于解密加密数据时获取额外字符的问题。 感谢用户“Ebbe M. Pedersen”的评论,我现在明白问题是在PHP和Android Java代码中都没有使用相同的填充机制。 所以我将Java代码更改为

Java代码

public class encryption { private String iv = "fedcba9876543210";//Dummy iv (CHANGE IT!) private IvParameterSpec ivspec; private SecretKeySpec keyspec; private Cipher cipher; private String SecretKey = "0123456789abcdef";//Dummy secretKey (CHANGE IT!) public encryption() { ivspec = new IvParameterSpec(iv.getBytes()); keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES"); try { cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");//"AES/CBC/NoPadding" } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchPaddingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public byte[] encrypt(String text) throws Exception { if(text == null || text.length() == 0) throw new Exception("Empty string"); byte[] encrypted = null; try { cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec); encrypted = cipher.doFinal(padString(text).getBytes()); } catch (Exception e) { throw new Exception("[encrypt] " + e.getMessage()); } return encrypted; } public byte[] decrypt(String code) throws Exception { if(code == null || code.length() == 0) throw new Exception("Empty string"); byte[] decrypted = null; try { cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec); decrypted = cipher.doFinal(hexToBytes(code)); } catch (Exception e) { throw new Exception("[decrypt] " + e.getMessage()); } return decrypted; } public static String bytesToHex(byte[] data) { if (data==null) { return null; } int len = data.length; String str = ""; for (int i=0; i<len; i++) { if ((data[i]&0xFF)<16) str = str + "0" + java.lang.Integer.toHexString(data[i]&0xFF); else str = str + java.lang.Integer.toHexString(data[i]&0xFF); } return str; } public static byte[] hexToBytes(String str) { if (str==null) { return null; } else if (str.length() < 2) { return null; } else { int len = str.length() / 2; byte[] buffer = new byte[len]; for (int i=0; i<len; i++) { buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16); } return buffer; } } private static String padString(String source) { char paddingChar = ' '; int size = 16; int x = source.length() % size; int padLength = size - x; for (int i = 0; i < padLength; i++) { source += paddingChar; } return source; } } 

然后我在PHP mcrypt类中添加了相同的PKCS5padding函数:

PHP mcrypt类

 class MCrypt { private $iv = 'fedcba9876543210'; #Same as in JAVA private $key = '0123456789abcdef'; #Same as in JAVA function MCrypt() { } function encrypt($str) { //$key = $this->hex2bin($key); $iv = $this->iv; $td = mcrypt_module_open('rijndael-128', '', 'cbc', $iv); mcrypt_generic_init($td, $this->key, $iv); $encrypted = mcrypt_generic($td, $str); mcrypt_generic_deinit($td); mcrypt_module_close($td); return bin2hex($encrypted); } function decrypt($code) { //$key = $this->hex2bin($key); $code = $this->hex2bin($code); $iv = $this->iv; $td = mcrypt_module_open('rijndael-128', '', 'cbc', $iv); mcrypt_generic_init($td, $this->key, $iv); $decrypted = mdecrypt_generic($td, $code); mcrypt_generic_deinit($td); mcrypt_module_close($td); return utf8_encode(trim($decrypted)); } protected function hex2bin($hexdata) { $bindata = ''; for ($i = 0; $i  strlen($text)) return false; if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false; return substr($text, 0, -1 * $pad); }} 

现在当前的问题是发送/接收UTF-8字符,而不是如何解码/编码UTF-8字符。 当我发送包含例如超过3个或少于3个字符的阿拉伯语/波斯语时,它什么都不返回。 例如:如果我发送单词“خوب”(正好有3个字符),我会得到“خوب”这是正确的; 但如果我发送مچکرم(有5个字符),我什么也得不到。

我发现问题是我在解密php代码中的数据后没有使用unpadding函数,所以我修复了:

PHP代码

 decrypt($data); $data=$etool->pkcs5_unpad($data);// pkcs5_pad($data,$block_size); $data=$etool->encrypt($data); $array=array('data'=>$data); echo json_encode($array); 

这是获取它的Java代码

 JSONObject j=new JSONObject(sb.toString());//sb is string builder result=j.get("data").toString(); result= new String(etool.decrypt( result ),"UTF-8"); result = new String(result.getBytes("ISO-8859-1")); Log.d("success remote ",result); 

现在问题逆转了!! 我可以得到包含多于或少于3个波斯语/阿拉伯字符的单词,但不能包含包含3个字符的单词。

我想我应该检查一下“需要解压吗?” 但如果是这样呢?

 function pkcs5_pad ($text, $blocksize) { $pad = $blocksize - (strlen($text) % $blocksize); return $text . str_repeat(chr($pad), $pad); } function pkcs5_unpad($text) { $pad = ord($text{strlen($text)-1}); if ($pad > strlen($text)) return false; if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false; return substr($text, 0, -1 * $pad); } 

此代码使用PHP的字符串函数,默认情况下对原始二进制文件进行操作并忽略编码。

通过utf8_encode()混合解密的消息实际上没有意义。 该函数将ISO-8559-1代码点(即0x000xFF )重新映射到UTF-8代码点(即0x000x7f ,然后是0xc2800xc2bf0xc3800xc3bf )。

由于PHP默认使用原始二进制文件,因此您根本不需要应用此转换。

注意:我默认说。 PHP中有一个非常愚蠢的function叫做函数重载 ,它由mbstring.func_overload PHP.ini指令控制。 如果您正在使用此function,则需要重写需要测量和/或切片字符串的一段加密代码,以便使用strlen()substr()等。

Defuse Security发布并维护一个安全的经过身份validation的加密PHP库 ,其中包含对这些函数的替代,这些函数可以抵御函数重载 。


安全通知

第一: 您的加密代码被破坏了 。 换句话说: 不安全

第二: 如果你能提供帮助,请避免使用mcrypt 。

如果您只需要通过网络加密数据,只需使用TLS即可 。 试图在这里重新发明轮子只会导致灾难。

但是,如果(例如)您需要在TLS之上进行点对点加密(例如,因此您的服务器永远不会看到数据),请不要自己滚动。 选择一个安全的PHP加密库 。 如果您需要跨平台工作的,请使用libsodium 。

问题是在php代码中没有正确使用un-padding函数。 实际上,当加密java中的数据时,有时因为文本不适合它使用填充的块 ,如果文本适合,它不会这样做。 在PHP代码中,如果在java加密过程中使用了填充,则正确完成取消填充。 但是如果文本在java加密过程中不需要填充,PHP un-padding函数返回null并且null被传递给$ data变量。 所以我一无所获! 通过改变几行PHP代码,每个思考开始正常工作。

导致问题的代码:

 $data=$etool->pkcs5_unpad($data);// in some cases null is retuned $data =Json_decode($data,true); 

正确版本的代码:问题是固定的。

 $padding=$etool->pkcs5_unpad($data); if($padding!="") { $data=$padding; } $data =json_decode($data, true);