Android 和 PHP 中的 AES CBC 通过 Base64

AES CBC in Android and PHP via Base64

所以我的代码中有一个特定的问题。它有时有效,有时无效,我不知道为什么。

它应该如何工作: Android 使用 AES/CBC/PKCS5Padding 使用随机 IV 和我的密钥加密消息,将加密消息转换为 base64 并发送使用 POST 方法到服务器。 服务器将消息转换为二进制形式,对其进行解密并将微笑附加到消息中。接下来将消息发送回 Android。如果消息为空,服务器会向我发送 "Empty" 文本。

工作原理: 我总是从服务器接收数据,所以连接正常。不幸的是,我得到了 3 种类型的答案:

  1. 我的微笑留言 - 没关系
  2. "Empty..." 文本 - 但解密以某种方式工作,在 PHP 调试模式下没问题
  3. 我的 IV 太短的错误 - 很少见

一条线索: 我查看了 base64 数据,发现当 base64 字符串为“+”字符时出现情况 2,但我不知道它有什么帮助。

Android部分发送数据做服务器:

HttpURLConnection urlConnection;
String message = null;
String answer = null;
String data = "a piece of data";

try {
    byte[] wynikByte = encrypt(data.getBytes("UTF-8"));
    message = Base64.encodeToString(wynikByte, Base64.DEFAULT);
} catch (UnsupportedEncodingException ex){
    Log.e("CRYPT", "Not working");
}

try {
    // Connect to server
    urlConnection = (HttpURLConnection) ((new URL(url).openConnection()));
    urlConnection.setDoOutput(true);
    urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    urlConnection.setRequestMethod("POST");
    urlConnection.connect();

    // Send to server
    OutputStream outputStream = urlConnection.getOutputStream();
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
    writer.write("dane=" + message);
    writer.close();
    outputStream.close();

    // Read answer
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
    String line = null;
    StringBuilder sb = new StringBuilder();
    while ((line = bufferedReader.readLine()) != null) {
        sb.append(line);
    }
    bufferedReader.close();
    answer = sb.toString();

} catch (UnsupportedEncodingException | IOException ex) {
        e.printStackTrace();
    }   
return message + "\n" + answer;

Android加密方式:

public static byte[] encrypt(byte[] plaintext) {
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey key = new SecretKeySpec(hexStringToByteArray(klucz2), "AES");

        SecureRandom random = new SecureRandom();
        byte iv[] = new byte[16];//generate random 16 byte IV AES is always 16bytes
        random.nextBytes(iv);
        IvParameterSpec ivspec = new IvParameterSpec(iv);

        cipher.init(Cipher.ENCRYPT_MODE, key, ivspec);
        byte[] encrypted = cipher.doFinal(plaintext);
        byte[] ciphertext = new byte[iv.length + encrypted.length];
        System.arraycopy(iv, 0, ciphertext, 0, iv.length);
        System.arraycopy(encrypted, 0, ciphertext, iv.length, encrypted.length);
        return ciphertext;

    } catch (InvalidKeyException | NoSuchAlgorithmException
            | NoSuchPaddingException
            | IllegalBlockSizeException | InvalidAlgorithmParameterException
            | BadPaddingException e) {
        throw new IllegalStateException(
                "CBC encryption with standard algorithm should never fail",
                e);
    }
} 

PHP 文件和我的密钥也用在 android app:

<?php
if (isset($_POST['dane']))
{
    $dane = $_POST['dane'];
    $key = pack('H*', "73f826a001837efe6278b82789267aca");

    $blocksize = mcrypt_get_block_size('rijndael_128', 'cbc');
    $ciphertext = base64_decode($dane, $powodzenie);
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
    $iv_old = substr($ciphertext, 0, $iv_size);
    $ciphertext = substr($ciphertext, $iv_size);
    $plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext, MCRYPT_MODE_CBC, $iv_old);
    $plaintext  = pkcs5_unpad($plaintext);
    if($plaintext == "")
    {
        echo "Empty...";
        return;
    }
    $plaintext = $plaintext . " :)";
    echo $plaintext;
} else {
    echo "Dane is empty";
}

// PHP don't have pkcs5 methods to pad
function pkcs5_pad ($text, $blocksize) 
{ 
    $pad = $blocksize - (strlen($text) % $blocksize); 
    return $text . str_repeat(chr($pad), $pad); 
} 

// PHP don't have pkcs5 methods to unpad
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); 
} 
?>

您需要在标记行之前正确编码 message 字符串。是的,base64 是 7 位安全的,但它也包含在表单编码数据中重要的字符。 [+= 特别是]

// Send to server
OutputStream outputStream = urlConnection.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
writer.write("dane=" + message); // here
writer.close();
outputStream.close();

解决方案 1 是将 += 分别替换为 %2B%3D

解决方案 2 是切换到多部分编码。

我更喜欢解决方案 2。实施起来需要做更多的工作,但物有所值。