【问题标题】:Encryption Between PHP & JavaPHP 和 Java 之间的加密
【发布时间】:2017-10-29 07:17:55
【问题描述】:

我希望在 PHP 服务器和 Java 客户端之间加密数据。单独的代码工作正常,我想在 PHP 服务器上坚持使用 OpenSSL。

当我尝试解码 PHP 加密字符串时出现错误时,你们有没有看到我在这里遗漏的任何内容:

PHP:

<?php

$iv = 'fedcba9876543210'; #Same as in JAVA
$key = '0123456789abcdef'; #Same as in JAVA



$ciphers = openssl_get_cipher_methods(FALSE);
$ciphers_and_aliases = openssl_get_cipher_methods(true);
$cipher_aliases = array_diff($ciphers_and_aliases, $ciphers);

print_r($ciphers);

//print_r($cipher_aliases);


// DEFINE our cipher
define('AES_CBC', 'aes-128-cbc');
// Generate a 256-bit encryption key
// This should be stored somewhere instead of recreating it each time
$encryption_key = "test_key";
// Generate an initialization vector
// This *MUST* be available for decryption as well
//$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(AES_CBC));

// Create some data to encrypt
$data = "Hello World!!!";
$data_b64= base64_encode($data);
echo "Before encryption: $data<br><br>Before Base64: $data_b64<br><br>";
// Encrypt $data using aes-256-cbc cipher with the given encryption key and
// our initialization vector. The 0 gives us the default options, but can
// be changed to OPENSSL_RAW_DATA or OPENSSL_ZERO_PADDING
$encrypted = openssl_encrypt($data_b64, AES_CBC, $encryption_key, 0, $iv);
$len = strlen($encrypted);
echo "Encrypted Len: $len  <br><br>";
$encrypted64 = base64_encode($encrypted);
echo "Encrypted b64: $encrypted64<br><br>";
// If we lose the $iv variable, we can't decrypt this, so:
// - $encrypted is already base64-encoded from openssl_encrypt
// - Append a separator that we know won't exist in base64, ":"
// - And then append a base64-encoded $iv
$encrypted = $encrypted64 . ':' . base64_encode($iv);
echo "Encrypted: $encrypted<br><br>";
// To decrypt, separate the encrypted data from the initialization vector ($iv).
$parts = explode(':', $encrypted);
// $parts[0] = encrypted data
// $parts[1] = base-64 encoded initialization vector
// Don't forget to base64-decode the $iv before feeding it back to
//openssl_decrypt
$decrypted64 = openssl_decrypt(base64_decode($parts[0]), AES_CBC, $encryption_key, 0, base64_decode($parts[1]));
$decrypted = base64_decode($decrypted64);
echo "Decrypted: $decrypted\n";
?>

PHP 输出:

加密前:Hello World!!!

Base64 之前:SGVsbG8gV29ybGQhISE=

加密长度:44

加密 b64: U21yMVRGQTdROVc3TWJ1Wm1HUTBhMmZmenlIN2tvdWQ5SHA5ekVxUmp5az0=

加密: U21yMVRGQTdROVc3TWJ1Wm1HUTBhMmZmenlIN2tvdWQ5SHA5ekVxUmp5az0=:ZmVkY2JhOTg3NjU0MzIxMA==

解密:Hello World!!!

Java 代码:

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

import java.util.Base64;
import java.security.*;

public class Sandbox {

    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(Base64.getEncoder().encode(value.getBytes()));
            System.out.println("encrypted string: "
                    + Base64.getEncoder().encodeToString(encrypted));

            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");


            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] temp = Base64.getDecoder().decode(encrypted);

            System.out.println((new String(temp)).length());
            byte[] original = cipher.doFinal(temp);
            original = Base64.getDecoder().decode(original);

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "0123456789abcdef"; // 128 bit key
        String initVector = "fedcba9876543210"; // 16 bytes IV

//        for (Provider provider : Security.getProviders()) {
//            System.out.println(provider.getName());
//            for (String key2 : provider.stringPropertyNames()) {
//                System.out.println("\t" + key2 + "\t" + provider.getProperty(key2));
//            }
//        }
        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World!!!")));

        System.out.println(decrypt(key, initVector, "R090NDcvclAyY2E1cmxLWG9kSGlnUktHdEI5U05sRGxNdWF4NFFjUUV0OD0="));
    }
}

Java 输出:

> 
30
Hello World!!!

44

null

javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:913)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
    at javax.crypto.Cipher.doFinal(Cipher.java:2165)
    at Sandbox.decrypt(Sandbox.java:43)
    at Sandbox.main(Sandbox.java:67)
BUILD SUCCESSFUL (total time: 1 second)

【问题讨论】:

  • 也许这个 SO answer 会帮助你。
  • 如果您使用 SSL,为什么要加密?使用 https url,android 会自动进行加密/解密。只需将服务器设置为使用 https。真的不建议你自己做这样的事情,很容易弄错一些微妙的东西——随机值不够(rand 不够)、错误的 IV 等等。
  • @Gabe - 我完全同意你的说法。这是客户寻求的特殊设计要求。所以我需要找到解决办法。
  • @Jib 这就是你教育客户的工作。他正在做出一个不那么安全、成本更高且漏洞百出的决定。
  • 但是您的问题 - AES 要求将输入填充为 16 个字节的倍数。如果不是,该算法将不起作用。但是用什么来填充它是一个问题——用错误的信息填充它实际上会泄漏信息并削弱加密。这是您可能遇到的微妙问题以及为什么您真的不应该这样做的一个示例。 (我实际上不知道 AES-CBC 的正确填充是什么,它因算法而异)。

标签: java php android encryption


【解决方案1】:

可以找到工作版本 - https://github.com/chaudhuri-ab/CrossPlatformCiphers

需要记住的一点是,如果您没有在 PHP 中指定 OPENSSL_RAW_DATA,则数据将被加密为 base64。这让我很反感。

PHP:

class PHP_AES_Cipher {

    private static $OPENSSL_CIPHER_NAME = "aes-128-cbc"; //Name of OpenSSL Cipher 
    private static $CIPHER_KEY_LEN = 16; //128 bits

    /**
     * Encrypt data using AES Cipher (CBC) with 128 bit key
     * 
     * @param type $key - key to use should be 16 bytes long (128 bits)
     * @param type $iv - initialization vector
     * @param type $data - data to encrypt
     * @return encrypted data in base64 encoding with iv attached at end after a :
     */

    static function encrypt($key, $iv, $data) {
        if (strlen($key) < PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = str_pad("$key", PHP_AES_Cipher::$CIPHER_KEY_LEN, "0"); //0 pad to len 16
        } else if (strlen($key) > PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = substr($str, 0, PHP_AES_Cipher::$CIPHER_KEY_LEN); //truncate to 16 bytes
        }

        $encodedEncryptedData = base64_encode(openssl_encrypt($data, PHP_AES_Cipher::$OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, $iv));
        $encodedIV = base64_encode($iv);
        $encryptedPayload = $encodedEncryptedData.":".$encodedIV;

        return $encryptedPayload;

    }

    /**
     * Decrypt data using AES Cipher (CBC) with 128 bit key
     * 
     * @param type $key - key to use should be 16 bytes long (128 bits)
     * @param type $data - data to be decrypted in base64 encoding with iv attached at the end after a :
     * @return decrypted data
     */
    static function decrypt($key, $data) {
        if (strlen($key) < PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = str_pad("$key", PHP_AES_Cipher::$CIPHER_KEY_LEN, "0"); //0 pad to len 16
        } else if (strlen($key) > PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = substr($str, 0, PHP_AES_Cipher::$CIPHER_KEY_LEN); //truncate to 16 bytes
        }

        $parts = explode(':', $data); //Separate Encrypted data from iv.
        $decryptedData = openssl_decrypt(base64_decode($parts[0]), PHP_AES_Cipher::$OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, base64_decode($parts[1]));

        return $decryptedData;
    }

}

Java:

package ciphers;

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

import java.util.Base64;

public class Java_AES_Cipher {

    private static String CIPHER_NAME = "AES/CBC/PKCS5PADDING";
    private static int CIPHER_KEY_LEN = 16; //128 bits

    /**
     * Encrypt data using AES Cipher (CBC) with 128 bit key
     * 
     * 
     * @param key  - key to use should be 16 bytes long (128 bits)
     * @param iv - initialization vector
     * @param data - data to encrypt
     * @return encryptedData data in base64 encoding with iv attached at end after a :
     */
    public static String encrypt(String key, String iv, String data) {
        try {
            if (key.length() < Java_AES_Cipher.CIPHER_KEY_LEN) {
                int numPad = Java_AES_Cipher.CIPHER_KEY_LEN - key.length();

                for(int i = 0; i < numPad; i++){
                    key += "0"; //0 pad to len 16 bytes
                }

            } else if (key.length() > Java_AES_Cipher.CIPHER_KEY_LEN) {
                key = key.substring(0, CIPHER_KEY_LEN); //truncate to 16 bytes
            }


            IvParameterSpec initVector = new IvParameterSpec(iv.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance(Java_AES_Cipher.CIPHER_NAME);
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, initVector);

            byte[] encryptedData = cipher.doFinal((data.getBytes()));

            String base64_EncryptedData = Base64.getEncoder().encodeToString(encryptedData);
            String base64_IV = Base64.getEncoder().encodeToString(iv.getBytes("UTF-8"));

            return base64_EncryptedData + ":" + base64_IV;

        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    /**
     * Decrypt data using AES Cipher (CBC) with 128 bit key
     * 
     * @param key - key to use should be 16 bytes long (128 bits)
     * @param data - encrypted data with iv at the end separate by :
     * @return decrypted data string
     */

    public static String decrypt(String key, String data) {
        try {

            String[] parts = data.split(":");

            IvParameterSpec iv = new IvParameterSpec(Base64.getDecoder().decode(parts[1]));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance(Java_AES_Cipher.CIPHER_NAME);
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] decodedEncryptedData = Base64.getDecoder().decode(parts[0]);

            byte[] original = cipher.doFinal(decodedEncryptedData);

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

}

【讨论】:

  • 这没问题,但 API >=26,我找不到类似的解决方案来降低 API 级别,你能帮忙吗?
  • 密钥不是字符串,需要使用 PBKDF2 等算法进行密码加密。并且 CBC 模式对于传输消息是完全不安全的,读取明文/填充 oracle 攻击。
  • 我找不到您在 PHP 代码中定义变量 $str 的位置
【解决方案2】:

我无法弄清楚您的方法失败的原因。顺便说一下,我是这样做的,

Java

import com.sun.org.apache.xml.internal.security.utils.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;

public class MyClass {

    public static void main(String[] args) {
        String data = "Arnab C";
        final String enc = DarKnight.getEncrypted(data);
        System.out.println("Encrypted : " + enc);
        System.out.println("Decrypted : " + DarKnight.getDecrypted(enc));
    }

    static class DarKnight {

        private static final String ALGORITHM = "AES";

        private static final byte[] SALT = "tHeApAcHe6410111".getBytes();// THE KEY MUST BE SAME
        private static final String X = DarKnight.class.getSimpleName();

        static String getEncrypted(String plainText) {

            if (plainText == null) {
                return null;
            }

            Key salt = getSalt();

            try {
                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, salt);
                byte[] encodedValue = cipher.doFinal(plainText.getBytes());
                return Base64.encode(encodedValue);
            } catch (Exception e) {
                e.printStackTrace();
            }

            throw new IllegalArgumentException("Failed to encrypt data");
        }

        public static String getDecrypted(String encodedText) {

            if (encodedText == null) {
                return null;
            }

            Key salt = getSalt();
            try {
                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, salt);
                byte[] decodedValue = Base64.decode(encodedText);
                byte[] decValue = cipher.doFinal(decodedValue);
                return new String(decValue);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        static Key getSalt() {
            return new SecretKeySpec(SALT, ALGORITHM);
        }

    }
}

PHP

<?php

$key = "tHeApAcHe6410111";

function encrypt($text,$key){
     $block = mcrypt_get_block_size('rijndael_128', 'ecb');
     $pad = $block - (strlen($text) % $block);
     $text .= str_repeat(chr($pad), $pad);
     return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_ECB));
}

function decrypt($str, $key){ 
     $str = base64_decode($str);
     $str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB);
     $block = mcrypt_get_block_size('rijndael_128', 'ecb');
     $pad = ord($str[($len = strlen($str)) - 1]);
     $len = strlen($str);
     $pad = ord($str[$len-1]);
     return substr($str, 0, strlen($str) - $pad);
}

$enc =  encrypt("Arnab C",$GLOBALS['key']);
echo "Encrypted : ".$enc."</br>";
$dec = decrypt($enc,$GLOBALS['key']);
echo "Decrypted : ".$dec;

Java 输出

加密:PJG1Uu6SjJuuVGf7ApuHAw==

解密:Arnab C

PHP 输出

加密:PJG1Uu6SjJuuVGf7ApuHAw==

解密:Arnab C

【讨论】:

  • 在 php 端使用 OpenSSL 与 Mcrypt(一个不再积极开发的库)是否适合您?另外,如果您使用静脉输液,它对您有用吗?谢谢
  • 我确实让它工作了。 OpenSSL 选项有所作为。了解默认情况下 PHP 中的 OpenSSL 将生成 Base64 编码的字符串。上面回答。感谢您的回复并 +1。
  • 好吧兄弟(y)
【解决方案3】:

我使用了 theapache64 的解决方案,但在 PHP 7.2 中它停止工作,因为 Mcrypt 已被弃用并随后被删除。所以我更改了代码并且它可以工作:

function encrypt($data, $key) {
    return base64_encode(openssl_encrypt($data, "aes-128-ecb", $key, OPENSSL_RAW_DATA));
}

function decrypt($data, $key) {
    return openssl_decrypt(base64_decode($data), "aes-128-ecb", $key, OPENSSL_RAW_DATA);
}

【讨论】:

    【解决方案4】:

    在 Java 8 中,您不能使用

    import com.sun.org.apache.xml.internal.security.utils.Base64;
    

    相反,您可以使用

    import android.util.Base64;
    

    那么你还需要更改 Base64.decode 和 Base64.encode 行。

    完整代码如下(来自petrnohejl的评论,关于在PHP7中已弃用的MCRYPT,考虑在内):

    Java:

    import android.util.Base64;
    import java.security.Key;
    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
    
    public class MyClass {
        public static void main(String[] args) {
            String data = "Arnab C";
            final String enc = DarKnight.getEncrypted(data);
            System.out.println("Encrypted : " + enc);
            System.out.println("Decrypted : " + DarKnight.getDecrypted(enc));
        }
    
        static class DarKnight {
            private static final String ALGORITHM = "AES";
            private static final byte[] SALT = "tHeApAcHe6410111".getBytes();// THE KEY MUST BE SAME
            private static final String X = DarKnight.class.getSimpleName();
            static String getEncrypted(String plainText) {
                if (plainText == null) {
                    return null;
                }
    
                Key salt = getSalt();
    
                try {
                    Cipher cipher = Cipher.getInstance(ALGORITHM);
                    cipher.init(Cipher.ENCRYPT_MODE, salt);
                    byte[] encodedValue = cipher.doFinal(plainText.getBytes());
                    return Base64.encodeToString(encodedValue,Base64.DEFAULT);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                throw new IllegalArgumentException("Failed to encrypt data");
            }
    
            public static String getDecrypted(String encodedText) {
                if (encodedText == null) {
                    return null;
                }
    
                Key salt = getSalt();
                try {
                    Cipher cipher = Cipher.getInstance(ALGORITHM);
                    cipher.init(Cipher.DECRYPT_MODE, salt);
                    byte[] decodedValue = Base64.decode(encodedText, Base64.DEFAULT);
                    byte[] decValue = cipher.doFinal(decodedValue);
                    return new String(decValue);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
    
            static Key getSalt() {
                return new SecretKeySpec(SALT, ALGORITHM);
            }
        }
    }
    

    PHP:

    <?php
    
    $key = "tHeApAcHe6410111";
    
    function encrypt($data, $key) {
        return base64_encode(openssl_encrypt($data, "aes-128-ecb", $key, OPENSSL_RAW_DATA));
    }
    
    function decrypt($data, $key) {
        return openssl_decrypt(base64_decode($data), "aes-128-ecb", $key, OPENSSL_RAW_DATA);
    }
    
    $enc =  encrypt("Arnab C",$GLOBALS['key']);
    echo "Encrypted : ".$enc."</br>";
    $dec = decrypt($enc,$GLOBALS['key']);
    echo "Decrypted : ".$dec;
    
    ?>
    

    别给我点数,我只是个送信的,把几个帖子合在一起了。

    【讨论】:

    • 警告:此代码采用ECB模式,无密码派生方式,不安全。
    • 嗨,Maarten,还有什么更好的方法?您的,阿尔伯特·范·哈滕
    • 一种基于密码的密钥派生方法,例如 PBKDF2 或 Argon2,例如GCM 模式。但是对于传输模式的安全性,您确实需要一种安全的传输模式(TLS、SSH、Signal 等)——没有真正的解决方法。
    • 谢谢,马丁。我正在使用 TLS 传输数据,这一点毫无疑问 :) 我确实使用了密码派生方法 - 但可能不是您的意思 :) Albert van Harten
    • 您根本没有使用任何密钥派生,您只是使用盐作为密钥,全部字符串化。如果您无法区分盐和密钥,您可能不想发布加密代码。您的代码完全不安全,并且提供安全性是加密的目标,我个人认为它没有任何用处。
    猜你喜欢
    • 2012-02-04
    • 2011-11-05
    • 1970-01-01
    • 2013-01-11
    • 2011-09-30
    • 2018-02-04
    • 2012-07-12
    • 1970-01-01
    • 2023-03-23
    相关资源
    最近更新 更多