Java常见的加密方式
前言
传说在古罗马时代,发生了一次大战。正当敌方部队向罗马城推进时,古罗马皇帝凯撒向前线司令官发出了一封密信:VWRS WUDIILF。这封密信被敌方情报人员翻遍英文字典,也查不出这两个词的意思。
此时古罗马皇帝同时又发出了另一个指令:“前进三步”。然后古罗马军队司令官根据第二个指令很快明白了这封密信的含义。
“前进三步”这个提示的意思是向前推算三位的意思。推算出的结果就是:STOP TRAFFIC 停止运输或停止交通的意思!据说恺撒是率先使用加密函的古代将领之一,因此这种加密方法被称为凯撒密码。
它是一种替代密码,通过将字母按顺序推后起3位起到加密作用,如将字母A换作字母D,将字母B换作字母E。
凯撒算法是最早使用的算法之一,是最简单的一种对称加密算法。
算法分类
按是否需要key分类
不基于key的有: Base64算法、MD5
基于key的有: 对称加密算法、非对称加密算法、数字签名算法、数字证书、HMAC、RC4(对称加密)
按是否可逆分类
单向加密算法(不可解密):MD5、SHA、HMAC
非单项加密算法(可解密):BASE64、对称加密算法、非对称加密算法、数字签名算法、数字证书
散列算法
如MD5、SHA-1、SHA-2等,这些算法将任意长度的消息映射为固定长度的散列值,通常用于密码的存储和验证。
对称加密算法
如AES、DES、3DES等,这些算法使用相同的密钥进行加密和解密。
非对称加密算法
如RSA、DSA等,这些算法使用公钥进行加密,私钥进行解密,或使用私钥进行签名,公钥进行验签。
消息认证码(MAC)算法
如HmacMD5、HmacSHA1等,这些算法使用一个密钥和一条消息生成一个固定长度的MAC值,通常用于消息的完整性和真实性验证。
数字签名算法
如DSA、RSA等,这些算法使用私钥对消息进行签名,使用公钥对签名进行验证,用于数字证书和电子商务等场景。
摘要加密(Hash加密)
如果开发者需要保存密码(比如网站用户的密码),要考虑如何保护这些密码数据,网站用户密码的泄露是一件非常严重的事情,容易引起用户恐慌,
所以在安全方面是重中之重,直接将密码以明文写入数据库中是极不安全的,因为任何可以打开数据库的人,都将可以直接看到这些密码。
解决的办法是将密码加密后再存储进数据库,比较常用的加密方法是使用哈希函数(Hash Function),也就是摘要加密。
通过哈希函数,我们就可以将密码的哈希值存储进数据库。用户登录网站的时候,我们可以检验用户输入密码的哈希值是否与数据库中的哈希值相同。
由于哈希函数是不可逆的,即使有人打开了数据库,也无法看到用户的密码是多少。(但不意味着存储经过哈希函数加密后的密码就是绝对的安全!)
介绍:摘要加密是一种不需要密钥的加密算法,生成的密文是唯一的、定长的并且无法破解,具有不可逆性、唯一性。常见的算法有MD5、SHA等。
原理:通过hash算法(单向算法)对目标信息生成一段特定长度的唯一hash值。
应用场景:密码加密,数字签名,文件完整性的校验 ,版权等应用场景。
MD5加密
介绍:全称Message Digest Algorithm(信息摘要算法),是将任意长度的数据字符串转化成短小的固定长度的值的单向操作,任意两个字符串不应有相同的散列值。
MD5简易加密
public static byte[] encryMD5(byte[] data) throws Exception {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(data);
return md5.digest();
}
如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。
以及彩虹表技术(在字典法的基础上改进,以时间换空间)的兴起,可以建立彩虹表进行查表破解,目前这种方式已经很不安全了。
此时我们可以通过加盐来解决这个问题。
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")));
}
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"));
}
BCrypt加密
介绍:基于Blowfish算法的密码哈希函数,内部自己实现了随机加盐处理,是一种较为安全的加密方法。
原理:由四部分组成
1、saltRounds: 正数,代表hash杂凑次数,数值越高越安全,默认10次;
2、myPassword: 明文密码字符串;
3、salt: 盐,一个128bits随机字符串,22字符;
4、myHash: 经过明文密码password和盐salt进行hash,默认循环加盐hash10次,得到myHash。
每次明文字符串myPassword过来,就通过10次循环加盐salt加密后得到myHash, 然后拼接BCrypt版本号+salt盐+myHash等到最终的bcrypt密码 ,存入数据库中。
在下次校验时,从myHash中取出salt,(salt通过截取密文得到),salt跟password进行hash;得到的结果跟保存在DB中的hash进行比对。
BCrypt加密使用示例:
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
// 加密
String encodedPassword = BCrypt.hashpw("123456", BCrypt.gensalt());
System.out.println(encodedPassword);
// 验证密码是否正确
boolean flag = BCrypt.checkpw("123456", encodedPassword);
System.out.println(flag);
使用spring security安全框架进行BCrypt加密
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
// 创建BCryptPasswordEncoder对象
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 对密码进行加密
String md5password=passwordEncoder.encode("123456");
// 判断密码校验是否成功
boolean ps=passwordEncoder.matches("123456", "数据库密文");
BCryptPasswordEncoder是Spring Security中的一个加密方法。
BCryptPasswordEncoder方法采用了SHA-256+随机盐+密钥对密码进行加密。
SHA:安全Hash函数(SHA)是使用最广泛的Hash函数
加密算法与hash算法的区别:
加密算法是可逆的,加密算法的基本过程是对原来为明文的数据按某种算法进行处理,使其成为不可读的一段代码为“密文”,但在用相应的密钥进行操作之后就可以得到原来的内容 。
hash算法是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。
BCryptPasswordEncoder中的方法:
encode-加密:在BCryptPasswordEncoder中使用encode方法对密码进行加密,因为是通过hash算法进行加密,同样的密码输出的密文是不同的
matches-解密/匹配: 虽然通过encode方法加密的密码是不能解密的,但是在BCryptPasswordEncoder中提供了一个matches方法来匹配密码,它的原理是把需要配对的密码经过同一个hash函数计算,把计算得到的hash值到数据库中匹配,相同的hash值则说明是同一个密码。
对称加密
介绍:加密和解密使用相同密钥的加密算法。常见算法的有DES、3DES、AES等。
DES(Data Encryption Standard)和3DES(Triple DES)由于安全性原因基本已经退出舞台,作为取代的是AES(Advanced Encryption Standard)。
原理:密钥是控制加密及解密过程的指令。算法是一组规则,规定如何进行加密和解密。
应用场景:速度快,适用于离线的大量数据加密。
AES加密
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));
}
}
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组合使用进行加密。
注:在实际使用中,验签经常会加盐处理,拼接其他字符串如时间戳等后加密。
RSA加密
基本使用示例:
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("加解密异常");
}
}
}
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("加解密异常");
}
}
BASE64算法(编码方式)
介绍:Base64并不是一种加密方式,而是一种编码方式。很多时候,我们都将Base64编码作为数据加密后的传输 / 存储格式。
原理:根据ASCII编码值进行转换。Base64二进制数6位为一个单元(所以总字符数只能是64),一个字节有8位,将3个原字符转换成4个Base64密文。
应用场景:图片转码(应用于邮件,img标签,http加密)
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)));
Java常见的加密方式的更多相关文章
- python常见的加密方式
1.前言 我们所说的加密方式都是对二进制编码的格式进行加密,对应到python中,则是我妈们的bytes. 所以当我们在Python中进行加密操作的时候,要确保我们的操作是bytes,否则就会报错. ...
- python爬虫之常见的加密方式
前言 数据加密与解密通常是为了保证数据在传输过程中的安全性,自古以来就一直存在,古代主要应用在战争领域,战争中会有很多情报信息要传递,这些重要的信息都会经过加密,在发送到对应的人手上. 现代 ,在网络 ...
- 密码学系列——常见的加密方式(c#代码实操)
前言 说起加密方式,其实密码学的角度ASCII编码其实本身就是一种加密解密. 由于其公开,现在用于数字与字符的转换. 查看ASCII表可以去官网查查. 转换代码如下: static void Main ...
- https传输流程(加密方式、证书、传输安全)
http的缺点 http的数据是明文传输 如果用明文传输 很容易被第三方获取到传输的数据 因此我们一般要在网络传输过程中对数据进行加密 常见的加密方式 对称加密 秘钥key 待加密数据data a和b ...
- java常见加密方式介绍
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt260 本篇内容简要介绍BASE64.MD5.SHA.HMAC几种加密算法. ...
- Java常见加密技术的密钥与加密串长度
Java常见的Java方式 1.Base64编码 2.十六进制(Hex)编码 3.MD消息摘要 4.DES加密 5.3DES加密 6.AES加密 6.RSA加密
- 支付对接常用的加密方式介绍以及java代码实现
京东科技 姚永健 一.术语表: 1.对称算法 加密解密密钥是相同的.这些算法也叫秘密密钥算法或单密钥算法,它要求发送者和接收者在安全通信之前,商定一个密钥.对称算法的安全性依赖于密钥,泄漏密钥就意味着 ...
- 潭州课堂25班:Ph201805201 爬虫基础 第七课 Python与常见加密方式 (课堂笔记)
打开图形界面 18版 Python与常见加密方式 前言 我们所说的加密方式,都是对二进制编码的格式进行加密的,对应到Python中,则是我们的Bytes. 所以当我们在Python中进行加密操作的时 ...
- java中常用的加密方式
加密,是以某种特殊的算法改变原有的信息数据,使得未授权的用户即使获得了已加密的信息,但因不知解密的方法,仍然无法了解信息的内容.大体上分为双向加密和单向加密,而双向加密又分为对称加密和非对称加密(有些 ...
- java 实现md5加密的三种方式与解密
java 实现md5加密的三种方式 CreateTime--2018年5月31日15点04分 Author:Marydon 一.解密 说明:截止文章发布,Java没有实现解密,但是已有网站可以免费 ...
随机推荐
- 7月 Splashtop上线了这些新功能 快来看鸭
经过我们的攻城狮天天努力,我们的软件又得到了升级和完善,上线了一些有用的新功能和增强功能,快来看看吧. Splashtop已为Splashtop Business Access,Splashtop远程 ...
- JDBC连接MySQL 8时报错:MySQLNonTransientConnectionException: Public Key Retrieval is not allowed
需要设置属性 IDEA DBerver
- flask-wtf和WTForms官网翻译详解
https://flask-wtf.readthedocs.io/en/stable/# https://wtforms.readthedocs.io/en/2.3.x/ 介绍: wtformflas ...
- Java生成微信小程序码
官网文档地址:获取小程序码 package test; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.cor ...
- Vue Vue-Router params 传参 为空 path定义参数 参数 param is not repeatable
我在Vue-Router4.0.3版本上出现这个问题 因为官方 在2022年8月22日时废除了未定义的传参方式,所以必须使用定义的params. 解决办法: 在配置路由时:path路径上带上传值的ke ...
- 深入探讨Function Calling:在Semantic Kernel中的应用实践
引言 上一章我们熟悉了 OpenAI 的 function calling 的执行原理,这一章节我们讲解一下 function calling 在 Semantic Kernel 的应用. 在Open ...
- npm相关命令 npm 自定义脚本命令 自动重启应用
# 初始化生成package.json文件 npm init -y[不询问] # 查看本项目已安装模块 npm list # 安装模块 npm install 模块名[@版本号 可选] 或 npm ...
- 2023CSP-S游记
2023 CSP-S 游记 赛前 上午去花卉市场看了半天花,算是放松放松,主要是为了晚上给干妈过50岁生日. 还以为是 2 点开始,1 点 40 多就到了,然后去买了杯奶茶,然后进场. 结果我是第一考 ...
- [SWPUCTF 2021 新生赛]easy_md5
打开靶场可以看到一串代码,进行代码审计我们可以知道这个网页包含了一个叫flag2.php的文件,如果想要得到这个文件就得进行GET传参和POST传参. 并且这里用到一个MD5绕过,传参的值不能相等,但 ...
- 剑指Offer-49.把字符串转换成整数(C++/Java)
题目: 将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数. 数值为0或者字符串不是一个合法的数值则返回0 输入描述: 输入一个字符串,包括数字字母符号,可以为空 输出描述: 如果是合法的 ...