飘易博客(作者:Flymorn)
订阅《飘易博客》RSS,第一时间查看最新文章!
飘易首页 | 留言本 | 关于我 | 订阅Feed

加密算法DES、3DES、AES、RSA、MD5、sha1区别及前后端加密实践

Author:飘易 Source:飘易
Categories:网络技术 PostTime:2021-1-10 22:24:38
正 文:

在项目的开发过程种,很多时候飘易会遇到前端和后端之间的数据传输的安全问题,在以前大家还没怎么注意这种传输的安全问题,很多密码的传输可能也是明文的 ,第三方劫持,中间人劫持,是可以轻易拿到客户端和服务端之间的明文通讯数据。这个对稍微有些安全审计要求的业务就通通不行了。


大家来和飘易一起看下DES、3DES、AES、RSA、MD5、sha1这些加密算法。


一、简介

DES:全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法;

3DES:(即Triple DES)是DES向AES过渡的加密算法,它使用3条56位的密钥对数据进行三次加密。3DES更为安全。

AES:高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,这个标准用来替代原先的DES;

RSA:公钥加密算法是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。

MD5:Message Digest Algorithm MD5(消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。

SHA1:即安全哈希算法(Secure Hash Algorithm),主要适用于数字签名标准,SHA1会产生一个160位的消息摘要;


二、可解密的加密算法


1. 对称加密:DES、3DES、AES

采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。

美国数据加密标准(DES)是对称密码算法,就是加密密钥能够从解密密钥中推算出来,反过来也成立。密钥较短,加密处理简单,加解密速度快,适用于加密大量数据的场合。


2. 非对称加密:RSA

RSA是非对称算法,加密密钥和解密密钥是不一样的,或者说不能由其中一个密钥推导出另一个密钥。RSA密钥尺寸大,加解密速度慢,一般用来加密少量数据,比如DES的密钥。

非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(public key)和私钥(private key)。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。


2.1 关于RSA的密钥说明:

这对公钥和私钥一般我们看到的是base64后的字符串。公钥一般是由3部分组成的:header,Modulus,Exponent,我们需要解析它,在C#里面我们可以借助一些库来实现,分别得到Modulus和Exponent,就可以创建 RSACryptoServiceProvider 实例了:

RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters();
RSAKeyInfo.Modulus = modulus;
RSAKeyInfo.Exponent = exponent;
RSA.ImportParameters(RSAKeyInfo);

详细的解释,可以从这篇帖子里看看:correctly create rsacryptoserviceprovider from public key


2.2 RSA可以加密的数据长度限制

由于RSA算法并不是为了加密大数据而设计的,所以我们不能使用RSA去加密大量数据,速度会比较慢。那么,我们可以加密多少长度的字符串呢?

这个长度一般在100-300字节以内,根据密钥长度而定,有些机器是  172 bytes,有些可能更少些。

关于为什么,可以留意下这篇 msdn 上的文章:Cryptography - Key not valid for use in specified state,这里引用下解释:

This is probably one of the most posted issues with regards to RSA encryption. The problem is not with the .NET provider. The problem is that the RSA algorithm (mathematically speaking) is not designed to encrypt large amounts of data.

RSA works in a product-exponent factorization puzzle with the input. The amount of input the RSA algorithm can encrypt is directly proportional to the size of the key. This is why RSA is not a bulk cipher (an algorithm that can encrypt arbitrarily large pieces of input, such as text). By the way, generating large RSA keys is VERY slow, and also has limits, so that's NOT a way around the problem. Simply speaking, RSA is not intended to encrypt messages.

To encrypt messages or any other large piece of data, you need a block cipher. Something like Rijndael or 3DES. They can take any length of input and encrypt/decrypt it. That's what they were designed to do. The problem is that they work with a single key. So the trick is how do you make sure both parties (the encryptor and decryptor) have the same key while making sure that nobody between them can intercept that key.

That's where RSA comes in. RSA is a 2-key system that has both a public and private key. Anything that you encrypt wiht the public key can be decrypted only with the private key. You can distribute the public key to anyone so they can encrypt things that only you can decrypt with the private key.

When you combine RSA with a block cipher, you have a system that can securely send the block cipher key using the RSA public key. Only the person with the RSA private key can decrypt the block cipher key (which is relatively small). Now both sides have the same block cipher key and can do bulk encryption to each other using the block cipher (Rijndael, 3DES, etc.). In that sense, RSA is used for doing secure key exchanges.

The other things that RSA is good for is digital signatures. You basically take a small hash of a much larger message, then you encrypt the hash code using the RSA private key. That's called a digital signature. Now anyone with the RSA public key can decrypt it. If it decrypts and verifies correctly, you can be farily certain the data came from the right person because only he or she has the corresponding RSA private key.

However, RSA is not intended for bulk encryption.


三、不可解密的加密算法

比较典型的不可逆加密算法:MD5、SHA1

SHA1 和 MD5 是搜索散列算法,将任意大小的数据映射到一个较小的、固定长度的唯一值。加密性强的散列一定是不可逆的,这就意味着通过散列结果,无法推出任何部分的原始信息。任何输入信息的变化,哪怕仅一位,都将导致散列结果的明显变化,这称之为雪崩效应。散列还应该是防冲突的,即找不出具有相同散列结果的两条信息。具有这些特性的散列结果就可以用于验证信息是否被修改。MD5 比 SHA1 大约快 33%。


四、前后端加密传输实现

在实际项目里,我们应该怎么选型呢?前后端应该怎样设计加密算法呢?


有几种思路,可以去实践。不管哪种思路,RSA非对称加密一般会选用的,再混合其他对称加密算法(DES, AES等)结合起来。


思路1:RSA+AES混合加密-单向加密

使用RSA秘钥生成工具生成一对公钥和私钥,前端保留RSA公钥,后端保留RSA私钥。


前端发送请求数据时

a. 先生成一串随机字符串作为AES的秘钥明文key,然后使用RSA公钥对这个key进行加密,得到加密后的AES秘钥密文key

b. 将要发送的请求数据用明文key进行AES加密,得到数据密文

密文key数据密文同发给后端进行处理。


后端处理数据时,先用RSA私钥对密文key解密得到明文key,用明文key对数据密文使用AES进行解密得到明文数据。

处理后(入库等)后端要返回结果给前端,这里分2种情况:

1)后端直接返回明文给前端,啥也不用处理了,适合返回结果里没有敏感内容;

2)后端需要对明文结果进行加密:

那么后端用明文key对返回数据进行AES加密得到密文数据,将密文数据返回给前端。前端接收到后用明文key进行解密,得到最终结果。


因为对数据进行加密的AES秘钥是每次请求随机生成的,而且传输过程中AES是使用非对称加密的,只要后端持有的RSA私钥不泄露,可以保证数据通信安全。


后端加解密数据流图:


前端加解密数据流图:



思路2:前后端RSA+AES 混合双向加密

前、后端各自生成自己的RSA秘钥对(公钥、私钥),然后交换公钥(后端保存前端的RSA公钥,前端保存后端的RSA公钥)。


后端返回数据给前端时

后端随机生成AES的明文key,用前端的公钥进行RSA加密得到密文key;

用明文key把返回的数据进行AES加密得到密文数据

然后将密文数据密文key进行传输。

前端用自己的RSA私钥对密文key进行解密得到明文key,用明文key进行AES解密得到明文数据。


前端发送请求给后端时(和上面的流程类似):

前端随机生成AES的明文key,用后端的公钥进行RSA加密得到密文key;

用明文key把请求数据进行AES加密得到密文数据

然后将密文数据密文key进行传输。

后端用自己的RSA私钥对密文key进行解密得到明文key,用明文key进行AES解密得到明文数据。


五、代码实践

飘易的这里前端是 C# 编写的PC桌面端软件,服务端是PHP。


AES加密 C# 版本代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

/**
 * AES 加解密
 * 
 */
namespace PIAOYIORG
{
    class AES
    {
        // secret IV 16位
        private static byte[] iv = new byte[16] { 0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };

        // 加密
        public static string Encrypt(string message, string key)
        {
            // Create sha256 hash
            SHA256 mySHA256 = SHA256Managed.Create();
            byte[] keyByte = mySHA256.ComputeHash(Encoding.UTF8.GetBytes(key));//32位
            string encrypted = EncryptString(message, keyByte, iv);
            return encrypted;
        }

        // 解密
        public static string Decrypt(string message, string key)
        {
            // Create sha256 hash
            SHA256 mySHA256 = SHA256Managed.Create();
            byte[] keyByte = mySHA256.ComputeHash(Encoding.UTF8.GetBytes(key));//32位
            string decrypted = DecryptString(message, keyByte, iv);
            return decrypted;
        }

        // 加密
        public static string EncryptString(string plainText, byte[] key, byte[] iv)
        {
            // Instantiate a new Aes object to perform string symmetric encryption
            Aes encryptor = Aes.Create();

            encryptor.Mode = CipherMode.CBC;

            // Set key and IV
            encryptor.Key = key;
            encryptor.IV = iv;

            // Instantiate a new MemoryStream object to contain the encrypted bytes
            MemoryStream memoryStream = new MemoryStream();

            // Instantiate a new encryptor from our Aes object
            ICryptoTransform aesEncryptor = encryptor.CreateEncryptor();

            // Instantiate a new CryptoStream object to process the data and write it to the 
            // memory stream
            CryptoStream cryptoStream = new CryptoStream(memoryStream, aesEncryptor, CryptoStreamMode.Write);

            // Convert the plainText string into a byte array
            byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);

            // Encrypt the input plaintext string
            cryptoStream.Write(plainBytes, 0, plainBytes.Length);

            // Complete the encryption process
            cryptoStream.FlushFinalBlock();

            // Convert the encrypted data from a MemoryStream to a byte array
            byte[] cipherBytes = memoryStream.ToArray();

            // Close both the MemoryStream and the CryptoStream
            memoryStream.Close();
            cryptoStream.Close();

            // Convert the encrypted byte array to a base64 encoded string
            string cipherText = Convert.ToBase64String(cipherBytes, 0, cipherBytes.Length);

            // Return the encrypted data as a string
            return cipherText;
        }

        // 解密
        public static string DecryptString(string cipherText, byte[] key, byte[] iv)
        {
            // Instantiate a new Aes object to perform string symmetric encryption
            Aes encryptor = Aes.Create();

            encryptor.Mode = CipherMode.CBC;
            // Set key and IV
            encryptor.Key = key;
            encryptor.IV = iv;

            // Instantiate a new MemoryStream object to contain the encrypted bytes
            MemoryStream memoryStream = new MemoryStream();

            // Instantiate a new encryptor from our Aes object
            ICryptoTransform aesDecryptor = encryptor.CreateDecryptor();

            // Instantiate a new CryptoStream object to process the data and write it to the 
            // memory stream
            CryptoStream cryptoStream = new CryptoStream(memoryStream, aesDecryptor, CryptoStreamMode.Write);

            // Will contain decrypted plaintext
            string plainText = String.Empty;

            try
            {
                // Convert the ciphertext string into a byte array
                byte[] cipherBytes = Convert.FromBase64String(cipherText);

                // Decrypt the input ciphertext string
                cryptoStream.Write(cipherBytes, 0, cipherBytes.Length);

                // Complete the decryption process
                cryptoStream.FlushFinalBlock();

                // Convert the decrypted data from a MemoryStream to a byte array
                byte[] plainBytes = memoryStream.ToArray();

                // Convert the decrypted byte array to string
                plainText = Encoding.UTF8.GetString(plainBytes, 0, plainBytes.Length);
            }
            finally
            {
                // Close both the MemoryStream and the CryptoStream
                memoryStream.Close();
                cryptoStream.Close();
            }

            // Return the decrypted data as a string
            return plainText;
        }

    }
}


AES加密 PHP 版本代码:

<?php
namespace App\Libs;

/**
 * AES 加解密算法类
 * openssl_* 系列函数需要PHP7.1+
 */
class AesClass
{

    // IV must be exact 16 chars (128 bit)
    protected $iv = "";
    protected $method = 'aes-256-cbc';// 加密算法

    /**
     * 构造器
     */
    public function __construct()
    {
        // IV 初始化
        $this->iv = chr(0x1) . chr(0x2) . chr(0x3) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0) . chr(0x0); 
    }

    //加密 - php7.1+
    function encrypt($str, $key) {
        // Must be exact 32 chars (256 bit)
        $key32 = substr(hash('sha256', $key, true), 0, 32);
        $res = base64_encode(openssl_encrypt($str, $this->method, $key32, OPENSSL_RAW_DATA, $this->iv));
        return $res;
    }
    
    //解密 - php7.1+
    function decrypt($str, $key) {        
        // Must be exact 32 chars (256 bit)
        $key32 = substr(hash('sha256', $key, true), 0, 32);
        $res = openssl_decrypt(base64_decode($str), $this->method, $key32, OPENSSL_RAW_DATA, $this->iv);
        return $res;
    }
}


RSA 加密 - C# 代码:

string rsa_public_key = "MIGfMA0GCS...GwyB2E19DMv8PqQIDAQAB";//RSA公钥,合并成1行

// 随机生成AES密钥
string aeskey = Guid.NewGuid().ToString();//36位
string aeskeyDecrypt = RSACrypt.encryptData(aeskey, rsa_public_key, "UTF-8");// 使用RSA公钥加密aes密钥            
aeskeyDecrypt = HttpUtility.UrlEncode(aeskeyDecrypt);// +/=需要编码,不然PHP的base64_decode无法解码

// AES加密方法
string dataGo = data.ToString();
dataGo = AES.Encrypt(dataGo, aeskey);// aes加密
dataGo = HttpUtility.UrlEncode(dataGo);// +/=需要编码,不然PHP的base64_decode无法解码

// 接下来可以把 dataGo, aeskeyDecrypt 发送给服务端了


RSA 解密 - PHP 代码:

// RSA公钥和私钥
protected $rsa_public_key = '-----BEGIN PUBLIC KEY-----
M000MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBYT0M8lo2CQwmR2/4XVr5Gyex
...
p000wyB2E19DMv8PqQIDAQAB
-----END PUBLIC KEY-----';
protected $rsa_private_key = '-----BEGIN RSA PRIVATE KEY-----
M00CXAIBAAKBgQDBYT0M8lo2CQwmR2/4XVr5GyexRQxh7beUl2hNpdF/CNAuG7zY
...
1R3Al+vtP1Ci5a3BE809vckxTBpoliGY86eBZuAee00=
-----END RSA PRIVATE KEY-----';
...
$data = $request->input('data');// aes加密后的数据体
$key = $request->input('key');// aes密钥-密文
// 用RSA私钥解密aes密钥
$aeskey = "";// aes密钥-明文
$private_key = openssl_pkey_get_private($this->rsa_private_key);//成功返回密钥资源标识符,失败返回false
$deres = openssl_private_decrypt(base64_decode($key), $aeskey, $private_key);
if(!$deres || empty($aeskey)) {
    return $this->sendErrorData("decrypt fail");
}
// AES解密
$aes = new \App\Libs\AesClass();
$data = $aes->decrypt($data, $aeskey);
if(empty($data)){
    return $this->sendErrorData("data empty");
}
// 继续处理啦

注意,PHP里RSA公钥和私钥要采用代码里的换行的方式,不能合并成一行,不然openssl_*函数处理失败。


六,总结

说起安全,没有绝对的安全。加密也是,不是加密了,就可以实现绝对的安全。但经过加密处理后,要想轻易的拿到明文数据,就不是那么容易了。服务端的代码我们可以比较放心的存储,但是客户端是对外开放的,如果是JS的前端,很多加密算法就一目了然。如果是C#这类的前端,可以做好反编译和混淆。如果是安卓APP,可以把加密的算法放到so文件里,so文件多加几层壳。这些措施,可以提高门槛。


更复杂的处理是采用CA证书,CA的根证书内置于操作系统(或者浏览器),它的作用在于验证收到的服务端的公钥是不是伪造的。你看银行的网上交易,银行会给咱们U盾硬件之类的东东,也是为了更安全的交易。


【参考】

结合RSA与AES实现前后端加密通信

前后端API交互数据加密——AES与RSA混合加密完整实例

作者:飘易
来源:飘易
版权所有。转载时必须以链接形式注明作者和原始出处及本声明。
上一篇:修复mysqldump Incorrect information in file frm (1033)
下一篇:Linux用户组(Group)和用户(User)的常用管理操作
1条评论 “加密算法DES、3DES、AES、RSA、MD5、sha1区别及前后端加密实践”
1 suoka
2021-12-12 22:38:20
大哥还在坚守,看到你又有动力了
发表评论
名称(*必填)
邮件(选填)
网站(选填)

记住我,下次回复时不用重新输入个人信息
© 2007-2019 飘易博客 Www.Piaoyi.Org 原创文章版权由飘易所有