Cipher加密
常见加密方式
对称加密
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密
常见加密算法
- DES
- 数据加密
- 加密的key必须是8位
- AES(常用)
- 高级加密
- DES加强版,key必须是16位
由于加密后有负数,所以一般结合Base64转码使用(后面会解释)
DES
/**
* ClassName: DesDemo
* Description:
* date: 2021/12/10 14:30
* des和aes是一样的,把算法和转换换成aes即可,其他不用改
* 由于输出有负数,所以需要使用base64转码
* @author Yee
* @since JDK 1.8
*/
public class DesDemo {
public static void main(String[] args) throws Exception{
//加密原文
String input = "叶生";
//key,如果使用的是des,必须是8位
String key = "13579246";
//要使用的算法
String algorithm = "DES";
//要转换成什么加密格式
String transformation = "DES";
//调用加密方法
String encrypt = encryptDES(input, key, algorithm, transformation);
System.out.println("加密之后:"+encrypt);
//调用解密方法
String decrypt = decryptDES(encrypt, key, algorithm, transformation);
System.out.println("解密之后:"+decrypt);
}
/**
* 抽取加密方法
* @param input 原文
* @param key 密钥,DES密钥长度必须是8个字符
* @param algorithm 密钥算法
* @param transformation Cipher对象的算法
* @return 返回密文
* @throws Exception
*/
private static String encryptDES(String input, String key, String algorithm, String transformation) throws Exception {
//加密对象,需要传入一个加密格式
Cipher cipher = Cipher.getInstance(transformation);
//指定密钥规则,需要传入key的字节数组和加密的算法
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), algorithm);
/**
* 初始化密码,根据选择的模式判断是加密还是解密
* 第二个参数是密钥
*/
cipher.init(Cipher.ENCRYPT_MODE,keySpec);
//进行加密
byte[] bytes = cipher.doFinal(input.getBytes());
//由于加密后字节码有负数无法显示
//需要使用base64转码可显示加密内容
String encode = Base64.encode(bytes);
return encode;
}
/**
* 抽取解密方法
* @param input 密文
* @param key 密钥
* @param algorithm 密钥算法
* @param transformation cipher算法
* @return 返回原文
* @throws Exception
*/
private static String decryptDES(String input, String key, String algorithm, String transformation) throws Exception{
//获得解密对象
Cipher cipher = Cipher.getInstance(transformation);
//密钥规则
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), algorithm);
//密码初始化
cipher.init(Cipher.DECRYPT_MODE,keySpec);
//解密
byte[] bytes = cipher.doFinal(Base64.decode(input));
return new String(bytes);
}
}
AES
AES
加密解密和 DES
加密解密代码一样
修改加密算法就行
修改的代码是,其他地方不变
// AES加密算法,比较高级,所以key的大小必须是16个字节
String key = "1234567812345678";
String transformation = "AES";
// 指定获取密钥的算法
String algorithm = "AES";
// 先测试加密,然后在测试解密
Base64
Base64可以将负数转为62个常见字母和斜杠和加号
Base64 算法原理
3个字节为一组,一个字节 8位,一共 就是24位
把3个字节转成4组,每组6位,不足8位,高位补0
这样做的好处在于base取的是后面6位,去掉高2位 ,
那么base64的取值就可以控制在0-63位了
Base64构成
- 小写 a - z 26个字母
- 大写 A - Z 26个字母
- 数字 0 - 9 10个数字
- + / 两个字符
一个是64个,所以叫Base64
由于三个字节一组,不足位数,会使用等号补齐
消息摘要
也称为数字摘要
无论输入的消息有多长,计算出来的消息摘要的长度总是固定的
比如MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出
但相同的输入必会产生相同的输出,弊端,超大型数据库可以存储常见的密码,
因为相同的原文总是计算出固定的密文,所以不安全,现在作用于文件加密
常见的算法
- MD5(常用)
- SHA256
- SHA512(常用)
消息摘要不是使用base64进行编码的,所以我们需要把值转成16进制
/**
* ClassName: DigestDemo
* Description:
* date: 2021/12/10 18:58
* 信息摘要,不需要使用base64,使用16进制替换负数
*
* @author Yee
* @since JDK 1.8
*/
public class DigestDemo {
public static void main(String[] args) throws Exception {
//原文
String input = "abc";
//加密算法,填入算法名即可
String algorithm = "MD5";
//获得信息摘要
MessageDigest instance = MessageDigest.getInstance(algorithm);
//获得字节数组
byte[] digest = instance.digest(input.getBytes());
//转成base64和md5不合适,使用16进制,去掉高位
//留最低8位,可以解决负数问题
//拼接md5
StringBuffer sb = new StringBuffer();
for (byte b : digest) {
String str = Integer.toHexString(b & 0xff);
//不足两位补0,否则生成的md5长度缺失
if (str.length() == 1){
str = "0"+str;
}
sb.append(str);
}
System.out.println(sb.toString());
}
}
文件消息摘要
因为相同的原文总是计算出固定的密文,所以不安全,现在作用于文件加密
网上下载软件有些提供sha512或者md5可以进行校验是否原版软件
/**
* ClassName: FileDomo
* Description:
* date: 2021/12/10 19:41
* 文件的信息摘要,由于查询文件有没有被更改
*
* @author Yee
* @since JDK 1.8
*/
public class FileDemo {
public static void main(String[] args) throws Exception {
//加密方式
String algorithm = "sha-512";
//文件路径
FileInputStream fis = new FileInputStream("F:\\apache-tomcat-8.5.73-windows-x64.zip");
String file = getDigestFile(algorithm, fis);
System.out.println("文件的sha-512:"+file);
}
private static String getDigestFile(String algorithm, FileInputStream fis) throws IOException, NoSuchAlgorithmException {
//记录读取的长度
int len ;
//每次读1024字节
byte[] buffer = new byte[1024];
//字节流存储
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
while ((len = fis.read(buffer)) != -1){
//要写入的数组,从0开始,到len长度
outputStream.write(buffer,0,len);
}
//获得信息摘要
MessageDigest instance = MessageDigest.getInstance(algorithm);
// 在缓冲区的内容转换为字节数组
byte[] digest = instance.digest(outputStream.toByteArray());
return toHex(digest);
}
//信息摘要转为16进制
private static String toHex(byte[] digest) {
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
String s = Integer.toHexString(b & 0xff);
// 保持数据的完整性,前面不够的用0补齐
if (s.length()==1){
s="0"+s;
}
sb.append(s);
}
return sb.toString();
}
}
摘要总结
- MD5算法 : 摘要结果16个字节, 转16进制后32个字节
- SHA256算法 : 摘要结果32个字节, 转16进制后64个字节
- SHA512算法 : 摘要结果64个字节, 转16进制后128个字节
非对称加密
与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)
和私有密(privatekey)
因为加密和解密使用的是两个不同
的密钥,所以这种算法叫作非对称加密算法
- 如果使用私钥加密, 只能使用公钥解密
- 如果使用公钥加密, 只能使用私钥解密
常见算法
- RSA(常用)
- ECC
这里把密钥保存在本地,并把读取密钥方法作为静态方法方便调用
/**
* ClassName: RSAdemo
* Description:
* date: 2021/12/10 22:24
* 非对称加密算法
* 公钥和私钥
* @author Yee
* @since JDK 1.8
*/
public class RSAdemo {
public static void main(String[] args) throws Exception {
//要加密的原文
String input = "叶生";
//加密算法
String algorithm = "RSA";
//保存密钥到本地,这里没加前缀,默认是根目录,没有该文件会自动创建
generateKeyToFile(algorithm,"publicKey.pub","privateKey.pri");
//读取本地私钥
// PrivateKey privateKey = getPrivateKey("privateKey.pri",algorithm);
//读取本地公钥
// PublicKey publicKey = getPublicKey("publicKey.pub",algorithm);
}
/**
* 读取公钥
* @param pulickPath 公钥路径
* @param algorithm 算法
* @return
*/
public static PublicKey getPublicKey(String pulickPath, String algorithm) throws Exception {
//文件转为字符串
String publicKeyStr = FileUtils.readFileToString(new File(pulickPath), Charset.defaultCharset());
//读取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
//base64解码,这个类表示私钥,是一个规则,专用于公钥转码
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyStr));
//生成公钥,传入规则
return keyFactory.generatePublic(spec);
}
/**
* 读取私钥
* @param priPath 密钥路径
* @param algorithm 算法
* @return 返回私钥
* @throws Exception
*/
public static PrivateKey getPrivateKey(String priPath,String algorithm) throws Exception {
//将文件内容转为字符串,按照默认方式,怎么存的,怎么取
String privateKeyStr = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());
//获取密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
//64解码,这个类表示私钥,是一个规则,专用于私钥转码
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyStr));
//生成私钥,传入一个规则
return keyFactory.generatePrivate(spec);
}
/**
*
* @param algorithm 加密算法
* @param pubPath 公钥存储路径
* @param priPath 私钥存储路径
* @throws Exception
*/
public static void generateKeyToFile(String algorithm,String pubPath,String priPath) throws Exception {
//密钥生成器
KeyPairGenerator instance = KeyPairGenerator.getInstance(algorithm);
//生成密钥对
KeyPair keyPair = instance.generateKeyPair();
//生成私钥
PrivateKey privateKey = keyPair.getPrivate();
//生成公钥
PublicKey publicKey = keyPair.getPublic();
//获得私钥字节
byte[] privateKeyEncoded = privateKey.getEncoded();
//获得公钥字节
byte[] publicKeyEncoded = publicKey.getEncoded();
//密钥进行转码
String privateKeyStr = Base64.encode(privateKeyEncoded);
String publickKeyStr = Base64.encode(publicKeyEncoded);
/**
* 保存公钥和私钥,使用工具类.pom已经导入
* 1,保存的路径
* 2,保存的数据
* 3,编码的格式
*/
FileUtils.writeStringToFile(new File(pubPath),publickKeyStr, Charset.forName("UTF-8"));
FileUtils.writeStringToFile(new File(priPath),privateKeyStr, Charset.forName("UTF-8"));
}
/**
* 加密
* @param algorithm 算法
* @param key 密钥
* @param input 原文
* @return 返回密文
* @throws Exception
*/
public static String encrypRSA(String algorithm, Key key, String input) throws Exception{
//私钥加密,创建加密对象
Cipher cipher = Cipher.getInstance(algorithm);
//选择加密模式,和key
cipher.init(Cipher.ENCRYPT_MODE,key);
//私钥进行加密
byte[] bytes = cipher.doFinal(input.getBytes());
//64编码
return Base64.encode(bytes);
}
/**
* 解密
* @param algorithm 算法
* @param key 密钥
* @param encrypted 密文
* @return 返回原文
* @throws Exception
*/
public static String decryptRSA(String algorithm,Key key,String encrypted) throws Exception{
Cipher cipher = Cipher.getInstance(algorithm);
// 私钥进行解密
cipher.init(Cipher.DECRYPT_MODE,key);
// 由于密文进行了Base64编码, 在这里需要进行解码
// byte[] decode = Base64.decode(encrypted);
//密文解密
byte[] bytes1 = cipher.doFinal(encrypted.getBytes());
return new String(bytes1);
}
}
数字签名
公钥数字签名
使用的是非对称加密和信息摘要结合使用
比如常见的ssl证书就是数字签名,用于网站https验证
常见的结合算法sha256withrsa和sha512withrsa
/**
* ClassName: SignatureDemo
* Description:
* date: 2021/12/11 11:00
* 数字签名,采用现在流行的sha-512和rsa
* @author Yee
* @since JDK 1.8
*/
public class SignatureDemo {
public static void main(String[] args) throws Exception {
//原文
String input = "叶生";
//密钥算法
String algorithm = "RSA";
//签名算法
String singn = "sha256withrsa";
//读取本地私钥,使用的是上个封装的方法
PrivateKey privateKey = RSAdemo.getPrivateKey("privateKey.pri",algorithm);
//读取本地公钥,使用的是上个封装的方法
PublicKey publicKey = RSAdemo.getPublicKey("publicKey.pub",algorithm);
//生成签名,使用私钥生成
String signatured = getSignature(input,singn,privateKey);
//校验签名
boolean flage = verifySignature(input,singn,publicKey,signatured);
}
/**
* 校验签名
* @param input 原文
* @param algorithm 算法
* @param publicKey 公钥
* @param signatured 签名
* @return
*/
private static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signatured) throws Exception {
//获取签名
Signature signature = Signature.getInstance(algorithm);
//初始化签名
signature.initVerify(publicKey);
//传入原文
signature.update(input.getBytes());
//校验签名
return signature.verify(Base64.decode(signatured));
}
/**
* 生成签名
* @param input 原文
* @param algorithm 算法
* @param privateKey 私钥
* @return
*/
private static String getSignature(String input, String algorithm, PrivateKey privateKey) throws Exception {
//获得签名对象
Signature signature = Signature.getInstance(algorithm);
//初始化签名
signature.initSign(privateKey);
//更新原文
signature.update(input.getBytes());
// 开始签名
byte[] sign = signature.sign();
//对签名就行64编码
return Base64.encode(sign);
}
}