常用数据编码以及加密解密算法

 

一、加密算法概述

   加密是前后端开发经常需要使用到的技术,应用场景包括不限于用户登入、数据交易、信息通讯等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的 签名加密算法来达到业务目标。一般来说加密算法有摘要算法、对称加密算法、非对称加密算法这三种类型,常见的加密算法及分类如下表:

 

密码分类 国产商用密码 国际商用密码
摘要算法 SM3 MD5、SHA-1、SHA-2、MAC  
对称加密算法 SM1、SM4、SM7、ZUC(祖冲之算法)、SSF446 DES、3DES、AES、RC5、RC6、RC4
非对称加密算法 SM2、SM9 RSA、Rabin、DH、DSA、ECC

 加密相关概念:

  • 明文:明文指的是未被加密过的原始数据。
  • 密文:明文被某种加密算法加密之后,会变成密文,从而确保原始数据的安全。密文也可以被解密,得到原始的明文。
  • 密钥:密钥是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥,分别应用在对称加密和非对称加密上。

二、加密算法介绍

  2.1 摘要算法

  摘要算法又称哈希算法、散列算法。(严格意义上摘要算法不算是加密算法,但通常与加密算法一起使用并有一定相似性,所以这里拿来做比较)。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。

    特点

     1.加密不需要密钥 

           2.不可逆 

           3.任意长度转为固定长度

    常见算法

    国产:SM3、MD5

    国际:SHA-1、MAC、SHA-2(SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256)

    应用场景:存储密码、校验文件完整性、生成签名

        流程图

 

              

  2.2 对称加密算法

  对称加密,顾名思义,加密方与解密方使用同一钥匙(秘钥)。具体一些就是,发送方通过使用相应的加密算法和秘钥,对将要发送的信息进行加密;对于接收方而言,使用解密算法和相同的秘钥解锁信息,从而有能力阅读信息。

    特点

    1.加解密使用同一个密钥 

    2.可逆 

    3.加密速度比非对称加密算法快

    常见算法

      国产:SM1、SM4、SM7、ZUC(祖冲之算法)、SSF446

      国际:DES、3DES、AES、RC5、RC6、RC4

    应用场景:加密传输、密码、配置文件加密

        流程图

                

 

  2.3 非对称加密算法

  非对称加密需要两个密钥:公钥 (publickey) 和私钥 (privatekey)。公钥和私钥是一对,如果用公钥对数据加密,那么只能用对应的私钥解密。如果用私钥对数据加密,只能用对应的公钥进行解密。因为加密和解密用的是不同的密钥,所以称为非对称加密。

    特点

    1.一对密钥 

    2.公钥加密私钥解密,私钥加签公钥验签

    常见算法

    国产:SM2、SM9

    国际:RSA、Rabin、DH、DSA、ECC

    应用场景:数字签名、数字证书

        流程图

             

 --------------------------------------------------------------------------

 

java加密方式有哪些

‌Java中常用的加密方式主要包括以下几种‌:‌1

  1. 对称加密算法‌:

    • AES(Advanced Encryption Standard)‌:AES是一种广泛使用的对称加密算法,支持128、192和256位的密钥长度,适用于大量数据的加密,具有较高的安全性和效率。
    • DES(Data Encryption Standard)‌:虽然DES已经被认为不够安全,但它是对称加密算法的一个重要基础。3DES(Triple DES)是基于DES的改进,使用三个密钥对数据进行三次加密,以提高安全性。
  2. 非对称加密算法‌:

    • RSA(Rivest-Shamir-Adleman)‌:RSA是一种广泛使用的非对称加密算法,基于大数分解的原理,适用于安全通信和数字签名。
    • ECC(Elliptic Curve Cryptography)‌:ECC是一种基于椭圆曲线的非对称加密算法,相对于RSA更加高效。
  3. 哈希函数‌:

    • MD5(Message-Digest Algorithm 5)‌:MD5是一种单向哈希函数,常用于文件校验和密码存储,但因其安全性问题,不建议用于加密敏感数据。
    • SHA(Secure Hash Algorithm)‌:SHA家族包括SHA-1、SHA-256等,安全性高于MD5,常用于数字签名和文件校验。
  4. Base64编码‌:Base64是一种编码方式,用于将二进制数据转换为文本数据,常用于网络传输中。它不是加密算法,但可以用于数据编码和传输。

应用场景

  • ‌AES‌:适用于数据存储和传输加密,保护敏感数据不被窃听。
  • ‌RSA‌:用于电商订单付款、银行相关业务等需要高安全性的场景。
  • ‌MD5‌:常用于密码加密、文件校验等,但因其安全性问题,不建议用于加密敏感数据。
  • ‌Base64‌:用于图片转码、邮件传输等场景,将二进制数据转换为文本数据以便传输。

--------------------------------------------------------------

概要

   加密算法是一种用数学方法对数据进行变换的技术,目的是保护数据的安全,防止被未经授权的人读取或修改。加密算法可以分为三大类:对称加密算法、非对称加密算法和哈希算法(也叫摘要算法)。

   本文来梳理下开发中常用到的数据编码中的Base64以及常见的一些加密算法。

   一、数据编码

   1. Base64 算法

   Base64编码本质上是一种将二进制数据转成文本数据的方案。对于非二进制数据,是先将其转换成二进制形式,每 3 个字节(3 × 8 = 24 位)数据被分成 4 组,每组 6 位。然后每连续6bit(2的6次方=64)计算其十进制值,根据该值在A-Z,a-z,0-9,+,/ 这64个字符中找到对应的字符,最终得到一个文本字符串。每个 Base64 编码字符对应一个 ASCII 字符。

   注意填充:如果输入的字节数不是 3 的倍数,会使用 “=” 填充,使最终的编码长度是 4 的倍数(单位:字符)。

   1)可读性编码算法

   Base64算法并不是加密算法,它的出现是为了解决ASCII码在传输过程中可能出现乱码的问题。Base64是网络上最常见的用于传输8bit字节码的可读性编码算法之一。可读性编码算法不是为了保护数据的安全性,而是为了可读性。可读性编码不改变信息内容,只改变信息内容的表现形式。

   2)64种字符

   Base64使用了64种字符:大写A到Z、小写a到z、数字0到9、“+”和“/”,故得此名。

   Base64编码对照表:

   2. URL Base64算法

   UrlBase64 是 Base64 编码的一种变体,主要用于在 URL 中安全地传输二进制数据。它与标准的 Base64 编码相比,有一些微小的差异,以确保编码后的字符在 URL 中不会引起问题。

   UrlBase64 主要有以下两个特点:

   1)字符集不同:在标准的 Base64 编码中,使用字符 "+", "/",而这两个字符在 URL 中有特殊的含义,可能会引起歧义或导致 URL 解析错误。为了解决这个问题,UrlBase64 将字符 "+" 替换为 "-", 将 "/" 替换为 "_"。

   2)去掉填充字符: 标准的 Base64 编码在最后可能会使用一个或两个 "=" 字符进行填充,以使编码后的字符串长度是4的倍数。但是在 URL 中,这些填充字符可能引起问题,因此 UrlBase64 通常去掉填充字符,直接使用编码后的字符串。

   总体而言,UrlBase64 是为了适应 URL 中的特殊需求而修改的 Base64 编码。在处理需要在 URL 中传递的二进制数据时,使用 UrlBase64 可以确保编码后的字符串在 URL 中是安全且可靠的。在使用 UrlBase64 解码时,需要在解码之前将 "_" 替换为 "/",将 "-" 替换为 "+",并根据需要删除填充字符。

   二、哈希算法

   哈希算法也叫散列函数或摘要算法,它的作用是将任意长度的数据映射为固定长度的值的算法,通常这个映射值称为哈希值(Hash Value)或者散列值或者消息摘要。

   1. 不属于加密算法

   哈希算法其实不属于加密算法,只是可以用到某些加密场景中(例如密码加密),两者可以看作是并列关系。加密算法通常指的是可以将明文转换为密文,并且能够通过某种方式(如密钥)再将密文还原为明文的算法。而哈希算法是一种单向过程,它将输入信息转换成一个固定长度的哈希值,但这个过程是不可逆的,也就是说,不能从哈希值还原出原始信息。

   例如:哈希函数 SHA-256 会将任意长度的输入数据映射为一个固定长度的输出(256 位),但无法通过输出值反推出输入数据。

   2.  验证数据完整性

   相同的内容用同样的摘要算法获得的散列值是一样的,所以常用于验证数据的完整性和一致性。

   说明:原始数据的任何改变都会导致哈希值的巨大变化。

   3.  哈希算法分类

   主要分为三大类:MD(Message Digest,消息摘要算法)、SHA(Secure HashAlgorithm,安全散列算法)和MAC(Message Authentication Code,消息认证码算法)

   常见的散列算法:

   1)MD(Message Digest,消息摘要算法)

   MD2、MD4、MD5 等,已经不被推荐使用。

   2)SHA(Secure Hash Algorithm,安全哈希算法)

   SHA-1 系列安全性低,SHA2(SHA-224、SHA-256、SHA-384、SHA-512)、SHA3 系列安全性较高。抗碰撞性和抗篡改性显著提升。

   SHA-256 是最常用的,它提供了良好的安全性,并且计算效率适中,广泛用于区块链技术(例如比特币)和数字签名等场景。

   说明:MD 和 SHA 都是无密钥的纯哈希算法,用于数据完整性校验。

   3)Bcrypt(密码哈希算法)

   基于 Blowfish 加密算法的密码哈希算法,专门为密码加密而设计,安全性高,属于慢哈希算法。

   4)MAC(Message Authentication Code,消息认证码算法)

   MAC是结合密钥的哈希算法,增加了消息认证能力(消息是否在传输过程中被篡改,以及确保消息是由合法的发送方发出的)。

   HMAC 是一种利用哈希函数生成消息认证码(MAC)的机制:

  • 引入密钥增强安全性。
  • 可以结合任意安全哈希算法(如 SHA-256)。比如:在 JWT(JSON Web Token)中,HMAC 是常用的签名算法(如 HMAC-SHA256,简称HS256)。
  • 主要用于验证数据完整性和消息认证。

   5)CRC:(Cyclic Redundancy Check,循环冗余校验)

   CRC算法是一种哈希算法,用于生成一个固定长度的值来表示数据的“指纹”。它基于多项式除法的原理,用于检查数据的完整性。

   CRC32 是一种 CRC 算法,它的特点是生成 32 位的校验值,通常用于数据完整性校验、文件校验等场景。

    应用场景:在 Redis 中,通常通过 CRC16(key) & 16383 来得到哈希槽的位置。CRC16 算法生成的哈希值通常为 16 位。

   说明:CRC16 在处理较小的数据集时,冲突概率较低,虽然相比更强大的哈希算法(如 SHA 或 MD5)可能有一些碰撞的风险,但在 Redis 集群的应用场景中,CRC16 仍然足够满足高效、快速的数据分配

   5. 加密哈希算法和非加密哈希算法

   哈希算法本身不涉及加密/解密操作。根据它们的用途和设计目标进行区分,可以简单分为:

   1)加密哈希算法:安全性较高的哈希算法,它可以提供一定的数据完整性保护和数据防篡改能力,能够抵御一定的攻击手段,安全性相对较高,但性能较差,适用于对安全性要求较高的场景。例如 SHA2、SHA3、SM3、RIPEMD-160、BLAKE2、SipHash 等等。

   2)非加密哈希算法:安全性相对较低的哈希算法,易受到暴力破解、冲突攻击等攻击手段的影响,但性能较高,适用于对安全性没有要求的业务场景。例如 CRC32、MurMurHash3、SipHash 等等。

   说明:在常用的签名算法中,哈希算法本身并不是签名的独立算法,它通常是作为组成部分(数据摘要)与 HMAC、RSA、ECDSA 等加密算法结合使用的。

   6. 盐值
   盐值通常用于哈希算法中,特别是在密码存储中,以增强哈希计算的安全性。盐值的目的主要是防止彩虹表攻击和暴力破解。盐值是一个随机生成的字符串,它会与原始数据(如密码)一起参与哈希计算,从而使得同样的原始数据每次计算出的哈希值都不相同。

   举个例子:

1 // 将密码与盐值结合进行哈希计算
2     public static String hashPassword(String password, byte[] salt) throws Exception {
3         MessageDigest md = MessageDigest.getInstance("SHA-256");
4         md.update(salt); // 加入盐值
5         byte[] hashedBytes = md.digest(password.getBytes());
6         return Base64.getEncoder().encodeToString(hashedBytes);
7     }

   三、对称加密算法

   对称加密算法加密和解密使用的是同一份秘钥,解密是加密的逆运算。对称加密算法加密速度快,密文可逆,一旦秘钥文件泄露,就会导致原始数据暴露。对称加密的结果一般使用Base64算法编码,便于阅读和传输。JDK8支持的对称加密算法主要有DES、DESede、AES、Blowfish,以及RC2和RC4等。不同的算法秘钥长度不同,秘钥长度越长,加密安全性越高。

   1. DES算法

   DES(Data Encryption Standard,数据加密标准)算法是对称加密算法领域中的典型算法,DES算法秘钥较短(56位),以现在计算机的计算能力,DES算法加密的数据在24小时内可能被破解。所以DES算法已经被淘汰,建议使用AES算法,不过这里还是简单了解下。

   2.  DESede算法(3DES)

   作为DES算法的一种改良,DESede算法(也称为3DES,三重DES)针对其秘钥长度偏短和迭代次数偏少等问题做了相应改进,提高了安全强度,但同时也造成处理速度较慢、秘钥计算时间加长、加密效率不高的问题。所以这里还是简单了解下,实际还是推荐用AES。

   3DES 使用 三个密钥(或一个/两个密钥) 对数据进行 三次加密操作。

   加密/解密流程: 

  • 第一次加密:使用密钥 K1 对数据进行 DES 加密。
  • 第二次解密:使用密钥 K2 对数据进行 DES 解密。
  • 第三次加密:使用密钥 K3 对数据再次进行 DES 加密。

   最常用的配置是 112 位(2 密钥模式) 或 168 位(3 密钥模式),但由于其性能和安全性问题,3DES 已逐渐被淘汰。

   3.  AES算法

   AES(AdvancedEncryption Standard,高级数据加密标准)算法支持128位、192位和256位的秘钥长度,加密速度比DES和DESede都快,至今还没有被破解的报道。经过验证,目前采用的AES算法能够有效抵御已知的针对DES算法的所有攻击方法,如部分差分攻击、相关秘钥攻击等。AES算法因秘钥建立时间短、灵敏性好、内存需求低等优点,在各个领域得到广泛的研究与应用。

   四、非对称加密算法

   非对称加密和对称加密算法相比,多了一把秘钥,为双秘钥模式,一个公开称为公钥,一个保密称为私钥。遵循公钥加密私钥解密,或者私钥加密公钥解密。非对称加密算法源于DH算法,后又有基于椭圆曲线加密算法的密钥交换算法ECDH,不过目前最为流行的非对称加密算法是RSA。

   RSA (Rivest–Shamir–Adleman algorithm):

   RSA 算法是是目前应用最广泛的非对称加密算法,像 SSL/TLS、SSH 等协议中就用到了 RSA 算法。

   RSA算法是一种基于大数分解的困难性的非对称加密算法,它需要选择两个大素数作为私钥的一部分,然后计算出它们的乘积作为公钥的一部分(寻求两个大素数比较简单,而将它们的乘积进行因式分解却极其困难)。

   1. 使用公钥和私钥

   1)公钥用于加密数据,确保数据机密性。

   2)私钥用于解密数据,或者用于数字签名,确保数据的完整性和来源。

   2. 数据交换过程

   RSA算法的数据交换过程分为如下几步:

   1)A 生成 RSA 密钥对(包括公钥和私钥)。

   2)A 向 B 发布公钥,使得 B 能用公钥来加密数据。

   3)A 使用 B 的公钥加密数据,并将加密后的数据发送给 B。

   4)B 使用自己的私钥解密数据,以获取原始消息。

   5)B 使用 A 的公钥加密数据,并将加密后的数据发送给 A。

   6)A 使用自己的私钥解密数据,以获取原始消息。

   RSA 算法的优点是简单易用,可以用于数据加密和数字签名;缺点是运算速度慢,不适合大量数据的加密。

   五、应用举例

   请依据加密解密技术,以及信息摘要,数字签名技术解决以下问题

   设计一个安全邮件传输系统,要求如下:

   该邮件以加密方式传输,邮件最大附件内容可达2GB,发送者不可抵赖,若邮件被第三方截获,第三方无法篡改。

   下面是整体设计思路:

 

--------------------------------------------------------------

 

文章目录
* 一、摘要加密(Hash加密)
1
1.MD5加密
2.SHA加密
3.BCrypt加密
二、对称加密
1.AES加密(Rijndael[读作rain-dahl]加密)
2.PBE加密
三、非对称加密
1.RSA加密
(1)基本使用示例
(2)RSA与AES组合使用示例
四、加密相关其他技术
1.BASE64
引文:
传说在古罗马时代,发生了一次大战。正当敌方部队向罗马城推进时,古罗马皇帝凯撒向前线司令官发出了一封密信:VWRS
WUDIILF。这封密信被敌方情报人员翻遍英文字典,也查不出这两个词的意思。
此时古罗马皇帝同时又发出了另一个指令:“前进三步”。然后古罗马军队司令官根据第二个指令很快明白了这封密信的含义。
“前进三步”这个提示的意思是向前推算三位的意思。推算出的结果就是:STOP TRAFFIC
停止运输或停止交通的意思!据说恺撒是率先使用加密函的古代将领之一,因此这种加密方法被称为凯撒密码。
它是一种替代密码,通过将字母按顺序推后起3位起到加密作用,如将字母A换作字母D,将字母B换作字母E。凯撒算法是最早使用的算法之一,是最简单的一种对称加密算法。

一、摘要加密(Hash加密)
引文:
如果开发者需要保存密码(比如网站用户的密码),要考虑如何保护这些密码数据,网站用户密码的泄露是一件非常严重的事情,容易引起用户恐慌,
所以在安全方面是重中之重,直接将密码以明文写入数据库中是极不安全的,因为任何可以打开数据库的人,都将可以直接看到这些密码。
解决的办法是将密码加密后再存储进数据库,比较常用的加密方法是使用哈希函数(Hash Function),也就是摘要加密。
通过哈希函数,我们就可以将密码的哈希值存储进数据库。用户登录网站的时候,我们可以检验用户输入密码的哈希值是否与数据库中的哈希值相同。
由于哈希函数是不可逆的,即使有人打开了数据库,也无法看到用户的密码是多少。(但不意味着存储经过哈希函数加密后的密码就是绝对的安全!)

介绍:摘要加密是一种不需要密钥的加密算法,生成的密文是唯一的、定长的并且无法破解,具有不可逆性、唯一性。常见的算法有MD5、SHA等。

原理:通过hash算法(单向算法)对目标信息生成一段特定长度的唯一hash值。

应用场景:密码加密,数字签名,文件完整性的校验 ,版权等应用场景。

1.MD5加密
介绍:全称Message Digest
Algorithm(信息摘要算法),是将任意长度的数据字符串转化成短小的固定长度的值的单向操作,任意两个字符串不应有相同的散列值。

(1)不加盐版

使用示例:

public static byte[] encryMD5(byte[] data) throws Exception {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(data);
return md5.digest();
}

(2)加盐版
需要注意的是,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。
以及彩虹表技术(在字典法的基础上改进,以时间换空间)的兴起,可以建立彩虹表进行查表破解,目前这种方式已经很不安全了。
此时我们可以通过加盐来解决这个问题。
盐(Salt) 是什么?就是一个
随机生成的字符串。我们将盐与原始密码连接(concat)在一起(放在前面或后面都可以),然后将concat后的字符串加密。Salt这个值是由系统随机生成的,并且只有系统知道。

加盐版算法:
每次保存密码到数据库时,都生成一个随机16位数字,将这16位数字和密码相加再求MD5摘要,然后在摘要中再将这16位数字按规则掺入形成一个48位的字符串。
在验证密码时再从48位字符串中按规则提取16位数字,和用户输入的密码相加再MD5。按照这种方法形成的结果肯定是不可直接反查的,且同一个密码每次保存时形成的摘要也都是不同的。

加盐使用示例:

public static String encryMD5Salt(String data) throws Exception {
MessageDigest md5 = MessageDigest.getInstance("MD5");
//每次的盐都是随机的
String salt = UUID.randomUUID().toString().substring(0,16);
md5.update((data + salt).getBytes());
//获取十六进制字符串形式的MD5摘要
String password = new String(new Hex().encode(md5.digest()));
char[] cs = new char[48];
for (int i = 0; i < 48; i += 3) {
cs[i] = password.charAt(i / 3 * 2);
char c = salt.charAt(i / 3);
cs[i + 1] = c;
cs[i + 2] = password.charAt(i / 3 * 2 + 1);
}
return new String(cs);
}

/**
* 校验密码是否正确
*/
public static boolean verify(String password, String md5) throws NoSuchAlgorithmException {
char[] cs1 = new char[32];
char[] cs2 = new char[16];
for (int i = 0; i < 48; i += 3) {
cs1[i / 3 * 2] = md5.charAt(i);
cs1[i / 3 * 2 + 1] = md5.charAt(i + 2);
cs2[i / 3] = md5.charAt(i + 1);
}
String salt = new String(cs2);
MessageDigest md5Verify = MessageDigest.getInstance("MD5");
md5Verify.update((password + salt).getBytes());
return new String(new Hex().encode(md5Verify.digest())).equals(new String(cs1));
}

结果测试:

public static void main(String[] args) throws Exception {
//System.out.println(encryMD5("123456"));
System.out.println(encryMD5Salt("123456"));
System.out.println(verify("123456",encryMD5Salt("123456")));
}

2.SHA加密
介绍:全称Secure Hash
Algorithm(安全散列算法),是FIPS所认证的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。

SHA与MD5:
(1)对强行攻击的安全性:最显著和最重要的区别是SHA-1摘要比MD5摘要长32位。使用强行技术,产生任何一个报文使其摘要等于给定报摘要的难度对MD5是2128数量级的操作,而对SHA-1则是2160数量级的操作。
这样,SHA-1对强行攻击有更大的强度。
(2)对密码分析的安全性:由于MD5的设计,易受密码分析的攻击,SHA-1显得不易受这样的攻击。
(3)速度:在相同的硬件上,SHA-1的运行速度比MD5慢。

注:SHA-256、SHA-512加密(目前用于比特币区块链的哈希算法)已经是当前很安全的加密方式,不需要额外进行加盐处理。

示例:

public static byte[] encryptSHA(byte[] data) throws Exception{
MessageDigest sha = MessageDigest.getInstance("SHA");
sha.update(data);
return sha.digest();
}


public static String SHAEncrypt(final String content) {
try {
MessageDigest sha = MessageDigest.getInstance("SHA");
byte[] sha_byte = sha.digest(content.getBytes());
StringBuffer hexValue = new StringBuffer();
for (byte b : sha_byte) {
//将其中的每个字节转成十六进制字符串:byte类型的数据最高位是符号位,通过和0xff进行与操作,转换为int类型的正整数。
String toHexString = Integer.toHexString(b & 0xff);
hexValue.append(toHexString.length() == 1 ? "0" + toHexString : toHexString);
}
return hexValue.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}

 

//SHA-256加密
public static String SHA256Encrypt(String sourceStr) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
if (null != md) {
md.update(sourceStr.getBytes());
String digestStr = getDigestStr(md.digest());
return digestStr;
}

return null;
}

结果测试:
public static void main(String[] args) throws Exception {
//System.out.println(encryMD5("123456"));
//System.out.println(encryMD5Salt("123456"));
//System.out.println(verify("123456",encryMD5Salt("123456")));
System.out.println(encryptSHA("123456".getBytes()));
System.out.println(SHAEncrypt("123456"));
System.out.println(SHA256Encrypt("123456"));
}

3.BCrypt加密
介绍:基于Blowfish算法的密码哈希函数,内部自己实现了随机加盐处理,是一种较为安全的加密方法。

原理:由四部分组成①saltRounds: 正数,代表hash杂凑次数,数值越高越安全,默认10次;②myPassword: 明文密码字符串;③salt:
盐,一个128bits随机字符串,22字符;
④myHash: 经过明文密码password和盐salt进行hash,默认循环加盐hash10次,得到myHash。
每次明文字符串myPassword过来,就通过10次循环加盐salt加密后得到myHash,
然后拼接BCrypt版本号+salt盐+myHash等到最终的bcrypt密码 ,存入数据库中。
在下次校验时,从myHash中取出salt,(salt通过截取密文得到),salt跟password进行hash;得到的结果跟保存在DB中的hash进行比对。

使用示例:

// 加密
String encodedPassword = BCrypt.hashpw("123456", BCrypt.gensalt());
System.out.println(encodedPassword);

// 验证密码是否正确
boolean flag = BCrypt.checkpw("123456", encodedPassword);
System.out.println(flag);

所需依赖:

<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>

二、对称加密
介绍:加密和解密使用相同密钥的加密算法。常见算法的有DES、3DES、AES等。
DES(Data Encryption Standard)和3DES(Triple
DES)由于安全性原因基本已经退出舞台,作为取代的是AES(Advanced Encryption Standard)。

原理:密钥是控制加密及解密过程的指令。算法是一组规则,规定如何进行加密和解密。

应用场景:速度快,适用于离线的大量数据加密。

1.AES加密(Rijndael[读作rain-dahl]加密)
AES一共有四种加密模式:电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)和输出反馈模式(OFB)。
以下是ECB的使用示例:

package com.example.user.utils;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Random;

public class AESUtils {


/**
* 加解密统一编码方式
*/
private final static String ENCODING = "utf-8";

/**
* 加解密方式
*/
private final static String ALGORITHM = "AES";

/**
* 加密模式及填充方式
*/
private final static String PATTERN = "AES/ECB/pkcs5padding";

/**
* 秘钥生成来源
*/
public static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

/**
* 生成AES密钥对象
*
*/
public static String generateAESKey() {
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < 16; i++) {
sb.append(ALLCHAR.charAt(random.nextInt(ALLCHAR.length())));
}
return sb.toString();
}


/**
* AES加密
*
* @param plainText
* @param key
* @return
* @throws Exception
*/
public static String encrypt(String plainText, String key) throws Exception {
if (key == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (key.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
SecretKey secretKey = new SecretKeySpec(key.getBytes(ENCODING), ALGORITHM);
// AES加密采用pkcs5padding填充
Cipher cipher = Cipher.getInstance(PATTERN);
//用密匙初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
//执行加密操作
byte[] encryptData = cipher.doFinal(plainText.getBytes(ENCODING));
return Base64.getEncoder().encodeToString(encryptData);
}


/**
* AES解密
*
* @param plainText
* @param key
* @return
* @throws Exception
*/
public static String decrypt(String plainText, String key) throws Exception {
SecretKey secretKey = new SecretKeySpec(key.getBytes(ENCODING), ALGORITHM);
// 获取 AES 密码器
Cipher cipher = Cipher.getInstance(PATTERN);
// 初始化密码器(解密模型)
cipher.init(Cipher.DECRYPT_MODE, secretKey);
// 解密数据, 返回明文
byte[] encryptData = cipher.doFinal(Base64.getDecoder().decode(plainText));
return new String(encryptData, ENCODING);
}

public static void main(String[] args) throws Exception {
String key = generateAESKey();
System.out.println(encrypt("123456",key));
System.out.println(decrypt(encrypt("123456",key),key));
}
}

2.PBE加密
介绍:全称Password Based
Encryption,基于口令加密。其特点是使用口令代替了密钥,而口令由用户自己掌管,采用随机数杂凑多重加密等方法保证数据的安全性。

口令和密钥的区别:
口令:一般与用户名对应,是某个用户自己编织的便于 记忆的一串单词、数字、汉字字符,口令的特点容易被记忆,
也容易泄露和被盗取,容易被社会工程学、暴力破解、撞库等方式获取。
密钥:是经过加密算法计算出来的,密钥一般不容易记忆,不容易被破解,而且很多时候密钥是作为算法的参数出现的,算法对于密钥长度也是有要求的,因为加密算法的作用就是利用密钥来扰乱明文顺序。

原理:PBE算法在加密过程中并不是直接使用口令来加密,而是加密的密钥由口令生成,这个功能由PBE算法中的KDF函数完成。
KDF函数的实现过程为:将用户输入的口令首先通过“盐”(salt)的扰乱产生准密钥,再将准密钥经过散列函数(摘要)多次迭代后生成最终加密密钥,
密钥生成后,PBE算法再选用对称加密算法对数据进行加密。–结合了摘要加密与对称加密

使用示例:

package com.example.user.utils;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.*;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

public enum PBEAlgorithmUtils {
PBE_With_MD5_And_DES("PBEWithMD5andDES", "default"),
PBE_WITH_MD5_AND_TRIPE_DES("PBEWithMD5AndTripeDES", "default"),
PBE_WITH_SHA1_AND_DESEDE("PBEWithSHA1AndDESede", "default"),
PBE_WITH_SHA1_AND_RC2_40("PBEWithSHA1AndRC2_40", "default"),
PBE_WITH_MD5_AND_RC2("PBEWithMD5AndRC2", "BC"),
PBE_WITH_SHA1_AND_DES("PBEWithSHA1AndDES", "BC"),
PBE_WITH_SHA1_AND_RC2("PBEWithSHA1AndRC2", "BC"),
PBE_WITH_SHA_AND_IDEA_CBC("PBEWithSHAAndIDEA-CBC", "BC"),
PBE_WITH_SHA_AND_2_KEY_TRIPLE_DES_CBC("PBEWithSHAAnd2-KeyTripleDES-CBC", "BC"),
PBE_WITH_SHA_AND_3_KEY_TRIPLE_DES_CBC("PBEWithSHAAnd3-KeyTripleDES-CBC", "BC"),
PBE_WITH_SHA_AND_128_BIT_RC2_CBC("PBEWithSHAAnd128BitRC2-CBC", "BC"),
PBE_WITH_SHA_AND_40_BIT_RC2_CBC("PBEWithSHAAnd40BitRC2-CBC", "BC"),
PBE_WITH_SHA_AND_128_BIT_RC4("PBEWithSHAAnd128BitRC4", "BC"),
PBE_WITH_SHA_AND_40_BIT_RC4("PBEWithSHAAnd40BitRC4", "BC"),
PBE_WITH_SHA_AND_BLOWFISH("PBEWithSHAAndBlowfish", "BC");

static {
Security.addProvider(new BouncyCastleProvider());
}

private String algorithm = "";
private String providerName = "";

PBEAlgorithmUtils(String algorithm, String providerName) {
this.algorithm = algorithm;
this.providerName = providerName;
}

public String encrypt(String plainText, String password, String saltStr, int iterationCount) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchProviderException {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory factory = null;
if ("default".equals(this.providerName)) {
factory = SecretKeyFactory.getInstance(this.algorithm);
} else {
factory = SecretKeyFactory.getInstance(this.algorithm, this.providerName);
}
SecretKey secretKey = factory.generateSecret(keySpec);
PBEParameterSpec parameterSpec = new PBEParameterSpec(saltStr.getBytes(), iterationCount);
Cipher cipher = Cipher.getInstance(this.algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] bytes = cipher.doFinal(plainText.getBytes());
String cipherText = Base64.getEncoder().encodeToString(bytes);
System.out.println(String.format("%s(%s-%d) plain text: %s -> cipher text: %s", this.algorithm, saltStr, iterationCount, plainText, cipherText));
return cipherText;
}

public String decrypt(String base64CipherText, String password, String saltStr, int iterationCount) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchProviderException {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory factory = null;
if ("default".equals(this.providerName)) {
factory = SecretKeyFactory.getInstance(this.algorithm);
} else {
factory = SecretKeyFactory.getInstance(this.algorithm, this.providerName);
}

SecretKey secretKey = factory.generateSecret(keySpec);
PBEParameterSpec parameterSpec = new PBEParameterSpec(saltStr.getBytes(), iterationCount);
Cipher cipher = Cipher.getInstance(this.algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
byte[] cipherBytes = Base64.getDecoder().decode(base64CipherText);
byte[] plainBytes = cipher.doFinal(cipherBytes);
String plainText = new String(plainBytes).trim();
System.out.println(String.format("%s(%s-%d) cipher text: %s -> plain text: %s", this.algorithm, saltStr, iterationCount, base64CipherText, plainText));
return plainText;
}

public static void main(String[] args) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeySpecException, BadPaddingException, InvalidKeyException, NoSuchProviderException {

//口令为password,加密盐为salt,加密十次,使用MD5加密生成密钥,然后使用RC2进行对称加密
String text = PBEAlgorithmUtils.PBE_WITH_MD5_AND_RC2.encrypt("123456","password","salt",10);
PBEAlgorithmUtils.PBE_WITH_MD5_AND_RC2.decrypt(text,"password","salt",10);

//口令为password,加密盐为salt,加密十次,使用SHA加密生成密钥,然后使用DES进行对称加密
String text2 = PBEAlgorithmUtils.PBE_WITH_SHA1_AND_DES.encrypt("123456","password","salt",10);
PBEAlgorithmUtils.PBE_WITH_SHA1_AND_DES.decrypt(text2,"password","salt",10);
}
}

三、非对称加密
介绍:非对称加密算法是一种密钥的保密方法,加密和解密使用两个不同的密钥,公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。
公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。常见的加密算法有RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)。

特点:算法强度复杂;加密解密速度没有对称密钥算法的速度快

应用场景:电商订单付款、银行相关业务、数字签名、与AES组合使用进行加密
注:在实际使用中,验签经常会加盐处理,拼接其他字符串如时间戳等后加密。

1.RSA加密
(1)基本使用示例
package com.example.user.utils;


import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RSAUtils {
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;

/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 编码
*/
private static String charset = "utf-8";

 

/**
* 获取密钥对
*
* @return 密钥对
*/
public static KeyPair getKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(1024);
return generator.generateKeyPair();
}

/**
* 获取私钥
*
* @param privateKey 私钥字符串
* @return
*/
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(privateKey.getBytes(charset));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
return keyFactory.generatePrivate(keySpec);
}

/**
* 获取公钥
*
* @param publicKey 公钥字符串
* @return
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(publicKey.getBytes(charset));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
return keyFactory.generatePublic(keySpec);
}

/**
* RSA加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return
*/
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = data.getBytes(charset).length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data.getBytes(charset), offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data.getBytes(charset), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
// 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
// 加密后的字符串
return Base64.encodeBase64String(encryptedData);
}

/**
* RSA解密
*
* @param data 待解密数据
* @param privateKey 私钥
* @return
*/
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] dataBytes = Base64.decodeBase64(data);
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
// 解密后的内容
return new String(decryptedData, "UTF-8");
}

/**
* 签名
*
* @param data 待签名数据
* @param privateKey 私钥
* @return 签名
*/
public static String sign(String data, String privateKey) throws Exception {
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(priKey);
signature.update(data.getBytes(charset));
return new String(Base64.encodeBase64(signature.sign()),charset);
}

/**
* 验签
*
* @param srcData 原始字符串
* @param publicKey 公钥
* @param sign 签名
* @return 是否验签通过
*/
public static boolean verify(String srcData, String publicKey, String sign) throws Exception {
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));

Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(pubKey);
signature.update(srcData.getBytes(charset));
return signature.verify(Base64.decodeBase64(sign.getBytes(charset)));
}

public static void main(String[] args) throws Exception {
try {
// 生成密钥对
KeyPair keyPair = getKeyPair();
String privateKey = new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded()));
String publicKey = new String(Base64.encodeBase64(keyPair.getPublic().getEncoded()));
System.out.println("私钥:" + privateKey);
System.out.println("公钥:" + publicKey);
// RSA加密
String data = "123456";
String encryptData = encrypt(data, getPublicKey(publicKey));
System.out.println("加密后内容:" + encryptData);
// RSA解密
String decryptData = decrypt(encryptData, getPrivateKey(privateKey));
System.out.println("解密后内容:" + decryptData);

// RSA签名
String sign = sign(data, privateKey);
// RSA验签
boolean result = verify(data, publicKey, sign);
System.out.print("验签结果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.print("加解密异常");
}
}

}

(2)RSA与AES组合使用示例
为什么要使用RSA与AES组合加密呢?
对称加密(AES)的优势在于加密较快,但劣势在于秘钥一旦给出去就不安全了。非对称加密(RSA)的优势在于安全,就算提供公钥出去,别人也解密不了数据,但加密速度较慢。
使用流程:
Ⅰ.先生成一个随机AES秘钥字符串;
Ⅱ.使用RSA公钥加密AES秘钥,然后再用AES秘钥加密真正的内容;
Ⅲ.把skey=加密的AES秘钥,body=AES秘钥加密的内容传过去;
Ⅳ.对面使用RSA私钥解密AES秘钥,然后用AES秘钥解密出内容。
使用示例:

public static void main(String[] args) throws Exception {
try {
//生成AES密钥
String AESKey = AESUtils.generateAESKey();
// 生成rsa密钥对
KeyPair keyPair = getKeyPair();
String privateKey = new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded()));
String publicKey = new String(Base64.encodeBase64(keyPair.getPublic().getEncoded()));
System.out.println("私钥:" + privateKey);
System.out.println("公钥:" + publicKey);

//加密端Start
// RSA加密AESKey
String encryptKey = encrypt(AESKey, getPublicKey(publicKey));
System.out.println("加密后的AES密钥:" + encryptKey);

// AES加密后的内容
String data = "123456";
String encryptData = AESUtils.encrypt(data,AESKey);
System.out.println("AES加密后内容:" + encryptData);
//加密端 end


//解密端Start
// RSA解密拿到AESKey
String key = decrypt(encryptKey,keyPair.getPrivate());
System.out.println("解密后的AES密钥:" + key);

// RSA验签
String result = AESUtils.decrypt(encryptData,key);
System.out.print("结果:" + result);
//解密端end

} catch (Exception e) {
e.printStackTrace();
System.out.print("加解密异常");
}
}

四、加密相关其他技术
1.BASE64
介绍:Base64并不是一种加密方式,而是一种编码方式。很多时候,我们都将Base64编码作为数据加密后的传输 / 存储格式。

原理:根据ASCII编码值进行转换。Base64二进制数6位为一个单元(所以总字符数只能是64),一个字节有8位,将3个原字符转换成4个Base64密文。(后面的使用示例也能看出来)

应用场景:Base64能够将任何数据转换为易移植的字符串,避免了传输过程中失真问题。最初,Base64是为了解决电子邮件中无法直接使用非ASCII字符的问题。
一段数据先经过Base64编码为ASCII字符串后,可以在接收端,通过Base64解码还原为原数据后,而无需担心传输过程中失真。
很多时候,我们都将Base64编码作为数据加密后的传输 / 存储格式。例如,一段明文数据通过MD5
、SHA等手段加密后,经过Base64编码为字符串,就可以很方便地进行传输 & 存储。

使用示例:

byte[] bytes = "123456".getBytes();
System.out.println("Base64加密前:"+Arrays.toString(bytes));
byte[] encodeBytes = Base64.encodeBase64(bytes);
System.out.println("Base64加密后:"+Arrays.toString(encodeBytes));
System.out.println("Base64解密后:"+Arrays.toString(Base64.decodeBase64(encodeBytes)));

posted @ 2025-01-08 16:35  hanease  阅读(691)  评论(0)    收藏  举报