如何在PHP和Android之间加密和解密数据

5

这些是我用来在 PHP 服务器和安卓应用之间加密和解密数据的类。

有时候 PHP 解密类不起作用:

例如当我在安卓端加密 "abc"、"zdf" 或者 "091360532561524369510" 时,PHP 类无法解密从安卓客户端传输过来的加密数据。

请您检查一下这些类。 Java 类:

import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class ApiCrypter
{
    private String iv= "0123456789012345";
    private String secretkey= "9876543210987654";
    private IvParameterSpec ivspec;
    private SecretKeySpec keyspec;
    private Cipher cipher;

    public ApiCrypter()
    {
        ivspec = new IvParameterSpec(iv.getBytes());
        keyspec = new SecretKeySpec(secretkey.getBytes(), "AES");

        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            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(text.getBytes("UTF-8"));
        }
        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;
        }
    }
}

PHP类:

<?php
class ApiCrypter
{
    private $iv  = '0123456789012345';
    private $key = '9876543210987654';

    public function __construct() 
    {
    }

    public function encrypt($str)
    { 
        $str = $this->pkcs5_pad($str);   
        $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);
    }

    public function decrypt($code)
    { 
        $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); 
        $ut =  utf8_encode(trim($decrypted));
        return $this->pkcs5_unpad($ut);
    }

    protected function hex2bin($hexdata)
    {
        $bindata = ''; 
        for ($i = 0; $i < strlen($hexdata); $i += 2) {
            $bindata .= chr(hexdec(substr($hexdata, $i, 2)));
        } 
        return $bindata;
    } 

    protected function pkcs5_pad ($text)
    {
        $blocksize = 16;
        $pad = $blocksize - (strlen($text) % $blocksize);
        return $text . str_repeat(chr($pad), $pad);
    }

    protected 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);
    }
}
?>

当你说“android”时,你可能指的是“java”。你需要懂得Java和PHP的人来查看你的问题。我建议你添加“java”标签。 - KIKO Software
不要修剪你的解密数据。有时会删除填充。 - t.m.adam
@t.m.adam 哇!太对了 :) 非常非常感谢。 - X Fa
只是让你知道,你的代码不安全。它使用了固定的IV。在正确的情况下,很容易检索明文。 - Luke Joshua Park
@LukeJoshuaPark 谢谢,我是初学者,你有什么建议吗? - X Fa
@XFa IV不必保密,只需要随机。因此,请使用随机字节生成器创建IV并将其前置到您的密文中。另外,请考虑使用KDF来生成您的密钥,并将mcrypt替换为openssl,因为mcrypt已被弃用。 - t.m.adam
1个回答

1

有几件事情需要注意:

  1. 每个消息的IV应该是不同的并且是不可预测的,永远不要硬编码。
  2. 不要在PHP中使用mcrypt。

这是解决问题最简单的方法:
  1. 获取两种语言中的libsodium。如果您将PHP升级到7.2或更高版本,应该会自动获取Sodium扩展(除非您的操作系统供应商有些问题)。
  2. 使用crypto_secretbox()(或您所用语言中的等效API)进行加密,使用crypto_secretbox_open()进行解密。
这比学习正确的CBC模式、初始化向量、填充方案、RNG和密文完整性的方式要简单得多。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接