可逆加密算法

2021-01-20

不可逆信息摘要算法(也称Hash算法)主要有:MD5,SHA系列,HMAC系列。

可逆加密算法分 对称式 和 非对称式,

对称式:DES,3DES,AES系列

非对称式:RSA,ECC椭圆曲线加密相关算法

基础可逆加密算法

简单的对称可逆加密算法原理,我们可以把它理解为一道数学题:

假设A有一个数字 88,接下来A想对它加密,拿一个密钥数7来加密,比如把它相加得到密文 88+7=95

A把加密后的95传输给B,B手上有协商好的密钥数7,同时也知道解密算法,那么B只要把密文减去密钥即可得到原来数字 95-7=88

以上传输过程中,只传递了密文95,原数字和密钥数都没被传输,也就实现了加密效果。

现实过程中,我们把这个数字看成计算机中的一个存储单位,比如字节(一个字节8比特),我们只要把这个字节做一遍数学运算,就能实现加密效果。

算法实现

根据以上思路,用PHP来实现对称加解密函数:

/**
 * 加密函数 
 * By: Malu
 * @param $data
 * @param $key
 * @return string
 */
public function encrypt($data, $key)
{
    $key = md5($key);
    $x = 0;
    $len = strlen($data);
    $l = strlen($key);

    $char = "";
    $str = "";
    // 循环拼接私钥md5后的字符,组装到待加密字串长度
    for ($i = 0; $i < $len; $i++) {
        if ($x == $l) {
            $x = 0;
        }
        $char .= $key[$x];
        $x++;
    }
    for ($i = 0; $i < $len; $i++) {
        // ord() 函数返回字符串的首个字符的 ASCII 值。
        // 给每个字符循环 加上 私钥md5后的 ASCII 与 256 求模后的值(求模是防止长度越界,比如中文字符)
        // 最后把 ASCII 值转成字符
        $str .= chr((ord($data[$i]) + ord($char[$i])) % 256);
    }
    return base64_encode($str); // 用基础的64个字符替换
}

/**
 * 解密函数
 * By: Malu
 * @param $data
 * @param $key
 * @return string
 */
public function decrypt($data, $key)
{
    $key = md5($key);
    $x = 0;
    $data = base64_decode($data);
    $len = strlen($data);
    $l = strlen($key);

    $char = "";
    $str = "";
    // 循环拼接私钥md5后的字符,组装到加密字串长度一样长
    for ($i = 0; $i < $len; $i++) {
        if ($x == $l) {
            $x = 0;
        }
        $char .= substr($key, $x, 1);
        $x++;
    }
    for ($i = 0; $i < $len; $i++) {
        if (ord(substr($data, $i, 1)) < ord(substr($char, $i, 1))) {
            // 如果加密字串ASCII小于密文ASCII,表示长度已越界,需要补256
            // 那么把 加密字串ASCII + 256 - 私钥md5后的字符串ASCII
            $str .= chr((ord(substr($data, $i, 1)) + 256) - ord(substr($char, $i, 1)));
        } else {
            $str .= chr(ord(substr($data, $i, 1)) - ord(substr($char, $i, 1)));
        }
    }
    return $str;
}

升级算法

以上代码在PHP下运行正常,但是在异构代码下运行呢?

比如PHP项目与Java项目加密传输,标准ASCII码共定义了128个字符,而以上算法用到了256位;

为了标准化,我把它限制到128个字符,我们来改进一下算法:

1.加密原文前做一次base64,把原文降为64个字符集。

2.把加法运算改成异或运算,这样就不用做越界处理。

下面是PHP实现的升级版可逆加密算法:

//url base64编码
public function urlsafe_b64encode($string)
{
    $data = base64_encode($string);
    $data = str_replace(array('+', '/', '='), array('-', '_', ''), $data);
    return $data;
}

//url base64解码
public function urlsafe_b64decode($string)
{
    $data = str_replace(array('-', '_'), array('+', '/'), $string);
    $mod4 = strlen($data) % 4;
    if ($mod4) {
        $data .= substr('====', $mod4);
    }
    return base64_decode($data);
}

/**
 * 加密函数 V2
 * By: Malu
 * @param $data
 * @param $key
 * @return string
 */
public function encrypt_v2($data, $key)
{
    $key = md5($key);
    $x = 0;

    $data = $this->urlsafe_b64encode($data);

    $len = strlen($data);
    $l = strlen($key);  // 32

    $char = "";
    $str = "";
    // 循环拼接私钥md5后的字符,组装到待加密字串长度
    for ($i = 0; $i < $len; $i++) {
        if ($x == $l) {
            $x = 0;
        }
        $char .= $key[$x];
        $x++;
    }
    for ($i = 0; $i < $len; $i++) {
        // ord() 函数返回字符串的首个字符的 ASCII 值。
        // 给每个字符循环 私钥md5后的ASCII 与 与原文处理后的字符做异或运算
        // 最后把 ASCII 值转成字符
        $str .= chr(ord($data[$i]) ^ ord($char[$i]));
    }
    $str = $this->urlsafe_b64encode($str); // 可以在URL安全传输

    return $str;
}

/**
 * 解密函数v2
 * By: Malu
 * @param $data
 * @param $key
 * @return string
 */
public function decrypt_v2($data, $key)
{
    $key = md5($key);
    $x = 0;
    $data = $this->urlsafe_b64decode($data);

    $len = strlen($data);
    $l = strlen($key);

    $char = "";
    $str = "";
    // 循环拼接私钥md5后的字符,组装到加密字串长度一样长
    for ($i = 0; $i < $len; $i++) {
        if ($x == $l) {
            $x = 0;
        }
        $char .= substr($key, $x, 1);
        $x++;
    }
    for ($i = 0; $i < $len; $i++) {
        // 把加密字串ASCII 与 私钥md5后的字符串ASCII 做异或运算
        // 最后把 ASCII 值还原成字符
        $str .= chr(ord(substr($data, $i, 1)) ^ ord(substr($char, $i, 1)));
    }
    $str = $this->urlsafe_b64decode($str);
    return $str;
}

下面是Java实现的升级版可逆加密算法:

// ord() 函数返回字符串的首个字符的 ASCII 值
public static int ord(String s) {
    return s.length() > 0 ? (s.getBytes(StandardCharsets.UTF_8)[0] & 0xff) : 0;
}

/**
 * 加密函数 V2
 * By: Malu
 *
 * @param data
 * @param key
 * @return
 */
public String encrypt(String data, String key) {
    key = md5(key);

    final Base64.Encoder encoder = Base64.getUrlEncoder();
    byte[] textByte = new byte[0];
    try {
        textByte = data.getBytes("UTF-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    String encodedText = encoder.encodeToString(textByte);

    Integer x = 0;
    Integer len = encodedText.length();
    Integer l = key.length();

    String char_tmp = "";
    String str = "";
    // 循环拼接私钥md5后的字符,组装到待加密字串长度
    for (Integer i = 0; i < len; i++) {
        if (x == l) {
            x = 0;
        }
        char_tmp += key.substring(x, x + 1);
        x++;
    }

    for (Integer i = 0; i < len; i++) {
        // ord() 函数返回字符串的首个字符的 ASCII 值。
        // 给每个字符循环 私钥md5后的ASCII 与 与原文处理后的字符做异或运算
        // 最后把 ASCII 值转成字符
        str += (char) (ord(encodedText.substring(i, i + 1)) ^ ord(char_tmp.substring(i, i + 1)));
    }

    try {
        textByte = str.getBytes("UTF-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    encodedText = encoder.encodeToString(textByte);

    return encodedText.replace("=", ""); // 可以在URL安全传输
}

/**
 * 解密函数 V2
 * By: Malu
 *
 * @param data
 * @param key
 * @return
 */
public String decrypt(String data, String key) {
    key = md5(key);
    Integer x = 0;
    String data_tmp = "";

    try {
        byte[] decodedBytes = Base64.getUrlDecoder().decode(data);
        data_tmp = new String(decodedBytes, "utf-8");
    } catch (Exception $e) {

    }

    Integer len = data_tmp.length();
    Integer l = key.length();

    String char_tmp = "";
    String str_tmp = "";
    // 循环拼接私钥md5后的字符,组装到加密字串长度一样长
    for (Integer i = 0; i < len; i++) {
        if (x == l) {
            x = 0;
        }
        char_tmp += key.substring(x, x + 1);
        x++;
    }
    for (Integer i = 0; i < len; i++) {
        // 把加密字串ASCII 与 私钥md5后的字符串ASCII 做异或运算
        // 最后把 ASCII 值还原成字符
        str_tmp += (chr(ord(data_tmp.substring(i, i + 1)) ^ ord(char_tmp.substring(i, i + 1))));
    }

    try {
        byte[] decodedBytes = Base64.getUrlDecoder().decode(str_tmp);
        data_tmp = new String(decodedBytes, "utf-8");
    } catch (Exception $e) {

    }

    return data_tmp;
}

算法加强

以上实现了基础的加密解密过程,我们可以自由改进其算法,比如把md5替换成其他哈希算法,也可以加盐,双重md5,甚至可以通过另外接口做成协商密钥,让密文随机变化,来加强加密强度。

PHP加密传输库

加解密库 php-encrypted-transmission 添加到 composer.json 配置文件

$ composer require malu/php-encrypted-transmission

升级 composer

$ composer update

使用示例

// If you installed via composer, just use this code to require autoloader on the top of your projects.
require 'vendor/autoload.php';

// Using Medoo namespace
use Malu\Encrypted\Encrypted;

$data = ["hello","malu","bbq"];

// 加密输出
$encrypt_data = Encrypted::encrypt(json_encode($data), "34f7e6dd6acf03192d82f0337c8c54ba");
echo $encrypt_data;

// 解密输出
echo Encrypted::decrypt($encrypt_data, "34f7e6dd6acf03192d82f0337c8c54ba");