消息摘要(Message Digest)又称为数字摘要(Digital Digest)
它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生
使用数字摘要生成的值是不可以篡改的,为了保证文件或者值的安全
无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出
只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出,消息摘要是单向、不可逆的
public class MessageDigestDemo {
public static void main(String[] args) throws Exception {
String md5 = digest("test", "MD5");
System.out.println(md5);
String sha1 = digest("test", "SHA-1");
System.out.println(sha1);
String sha256 = digest("test", "SHA-256");
System.out.println(sha256);
String sha512 = digest("test", "SHA-512");
System.out.println(sha512);
}
public static String digest(String content, String algorithm) throws Exception {
// 消息摘要核心类 MessageDigest
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
// 调用digest 方法获取数字摘要 返回一个byte 数组
byte[] digest = messageDigest.digest(content.getBytes());
StringBuilder builder = new StringBuilder();
// 将摘要转成一个16进制的字符串如果不足用0补全
for (byte b : digest) {
// 转成 16进制
String s = Integer.toHexString(b & 0xff);
if (s.length() == 1) {
// 如果生成的字符只有一个,前面补0
s = "0" + s;
}
builder.append(s);
}
return builder.toString();
}
}
需要主要的是DES算法中传入key必须是一个8位
public class DESDemo {
public static void main(String[] args) throws Exception {
String key = "12345678";
String secret = enCryptByDES("测试DES", key);
String deCryptByDES = deCryptByDES(secret,key);
assert secret.equals(deCryptByDES);
}
/**
* 解码
* @param secret Base64之后的密文
* @param key 秘钥key
* @return
* @throws Exception
*/
private static String deCryptByDES(String secret,String key) throws Exception{
String algorithm = "DES";
Cipher cipher = Cipher.getInstance(algorithm);
Key keySpec = new SecretKeySpec(key.getBytes(),algorithm);
// 第一个参数传入解密模式
cipher.init(Cipher.DECRYPT_MODE,keySpec);
// 需要将之前Base64 encode之后的秘钥进行decode转回byte[]
byte[] deCrypt = cipher.doFinal(Base64.decode(secret.getBytes()));
String content = new String(deCrypt);
System.out.println(content);
return content;
}
/**
*
* @param content 明文
* @param key DES秘钥 必须是8位!!
* @return
*/
private static String enCryptByDES(String content,String key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
// 算法
String algorithm = "DES";
// 调用cipher中的静态算法getInstance 传入具体的一个算法 如DES AES MD5 RSA..等
Cipher cipher = Cipher.getInstance(algorithm);
// 秘钥特征 加密规则
// 第一个参数key的字节
// 第二个参数表示加密算法
Key keySpec = new SecretKeySpec(key.getBytes(), algorithm);
// 调用cipher init方法初始化cipher对象
// 参数1 传入模式 如Cipher.ENCRYPT_MODE 加密模式 Cipher.DECRYPT_MODE 其他模式参考Cipher类中定义的静态常量
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// 调用cipher的的doFinal方法传入明文 对明文数据进行加密
byte[] secretePassword = cipher.doFinal(content.getBytes());
// 因为加密出来的字节数据直接打印会乱码 所以需要进行Base64编码输出成能识别的字符串
String encode = Base64.encode(secretePassword);
System.out.println(encode);
return encode;
}
}
AES是DES的加强 要求key的位数必须是16位
// 代码跟DES几乎一样
public class AESDemo {
public static void main(String[] args) throws Exception {
String content = "测试AES";
String key = "1234567812345678";
String secret = enCryptByAES(content, key);
String result = deCryptByAES(secret, key);
System.out.println(result.equals(content));
}
private static String enCryptByAES(String content, String key) throws Exception {
String enCryptMode = "AES";
String algorithm = "AES";
Cipher cipher = Cipher.getInstance(enCryptMode);
Key keySpec = new SecretKeySpec(key.getBytes(), algorithm);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] secret = cipher.doFinal(content.getBytes());
String encode = Base64.encode(secret);
System.out.println(encode);
return encode;
}
private static String deCryptByAES(String secret, String key) throws Exception {
String enCryptMode = "AES";
String algorithm = "AES";
Cipher cipher = Cipher.getInstance(enCryptMode);
Key keySpec = new SecretKeySpec(key.getBytes(), algorithm);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] content = cipher.doFinal(Base64.decode(secret));
String result = new String(content);
System.out.println(result);
return result;
}
}
数据块的大小为8位, 不够就补足
AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
DES/CBC/NoPadding (56)
DES/CBC/PKCS5Padding (56)
DES/ECB/NoPadding (56)
DES/ECB/PKCS5Padding (56)
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
RSA/ECB/PKCS1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
public class EnCryptModeDemo {
public static void main(String[] args) throws Exception {
String content = "测试加密模式";
String key = "12345678";
String algorithm = "DES";
// String mode = "DES/ECB/PKCS5Padding";
String mode = "DES/CBC/PKCS5Padding";
String secret = enCrypt(content, key, mode, algorithm);
String result = deCrypt(secret, key, mode, algorithm);
System.out.println(result.equals(content));
}
private static String deCrypt(String secret, String key, String mode, String algorithm) throws Exception {
Cipher cipher = Cipher.getInstance(mode);
Key keySpec = new SecretKeySpec(key.getBytes(),algorithm);
// 如果模式是CBC需要添加iv向量
if(mode.contains("CBC")){
IvParameterSpec iv = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.DECRYPT_MODE,keySpec,iv);
}else{
cipher.init(Cipher.DECRYPT_MODE,keySpec);
}
String result = new String(cipher.doFinal(Base64.decode(secret)));
System.out.println(result);
return result;
}
private static String enCrypt(String content, String key, String mode, String algorithm) throws Exception {
Cipher cipher = Cipher.getInstance(mode);
Key keySpec = new SecretKeySpec(key.getBytes(),algorithm);
// 如果模式是CBC需要添加iv向量
if(mode.contains("CBC")){
IvParameterSpec iv = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.ENCRYPT_MODE,keySpec,iv);
}else{
cipher.init(Cipher.ENCRYPT_MODE,keySpec);
}
String secret = Base64.encode(cipher.doFinal(content.getBytes()));
System.out.println(secret);
return secret;
}
}
非对称加密是现在加密算法,采用公钥public key 和私钥 private key 两把秘钥。公钥私钥是成对出现,如果公钥对数据加密则需要私钥解密,反之私钥加密必须使用公钥解密。常见的非对称加密算法有RSA ECC
特点
使用RSA生成秘钥对并保存到文件
public static void main(String[] args) throws Exception {
String algorithm = "RSA";
String[] keyPair = genKeyPair(algorithm);
System.out.println(keyPair[0]);
System.out.println(keyPair[1]);
// 随便网上找个工具即可 我这里使用的hutool 只用appache common也可以
FileUtil.writeString(keyPair[0],"./pub.key", Charset.forName("UTF-8"));
FileUtil.writeString(keyPair[1],"./pri.key", Charset.forName("UTF-8"));
}
private static String[] genKeyPair(String algorithm) throws Exception{
// 使用KeyPairGenerator 生成器创建非对称加密 传入算法
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
// 调用genKeyPair 生成秘钥对
KeyPair keyPair = keyPairGenerator.genKeyPair();
// getPrivate 获取私钥 getPublic 获取公钥
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// getEncoded获取byte数组 使用Base64进行可读性处理
String pub = Base64.encode(publicKey.getEncoded());
String pri = Base64.encode(privateKey.getEncoded());
return new String[]{pub,pri};
}
public class RSADemo {
public static void main(String[] args) throws Exception {
String algorithm = "RSA";
String pubKeyPath = "./pub.key";
String priKeyPath = "./pri.key";
// String[] keyPair = genKeyPair(algorithm);
// System.out.println(keyPair[0]);
// System.out.println(keyPair[1]);
// FileUtil.writeString(keyPair[0], pubKeyPath, Charset.forName("UTF-8"));
// FileUtil.writeString(keyPair[1], priKeyPath, Charset.forName("UTF-8"));
String content = "测试RSA";
String pubKey = FileUtil.readString(pubKeyPath, Charset.forName("UTF-8"));
String priKey = FileUtil.readString(priKeyPath, Charset.forName("UTF-8"));
String secretContent = enCryptByRSAPrivateKey(content, priKey, algorithm);
System.out.println(secretContent);
String result = deCryptByRSAPublicKey(secretContent, pubKey, algorithm);
System.out.println(result);
System.out.println(result.equals(content));
}
private static String[] genKeyPair(String algorithm) throws Exception {
// 使用KeyPairGenerator 生成器创建非对称加密 传入算法
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
// 调用genKeyPair 生成秘钥对
KeyPair keyPair = keyPairGenerator.genKeyPair();
// getPrivate 获取私钥 getPublic 获取公钥
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// getEncoded获取byte数组 使用Base64进行可读性处理
String pub = Base64.encode(publicKey.getEncoded());
String pri = Base64.encode(privateKey.getEncoded());
return new String[]{pub, pri};
}
private static String enCryptByRSAPrivateKey(String content, String key, String algorithm) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm);
// 统一密钥规范 进行Base64解码 秘钥使用PKCS8EncodedKeySpec类生成 KeySpec
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(key));
// 使用KeyFactory生成秘钥对方法
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
// 生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] bytes = cipher.doFinal(content.getBytes());
return Base64.encode(bytes);
}
private static String deCryptByRSAPublicKey(String secretContent, String key, String algorithm) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm);
// 统一密钥规范 进行Base64解码 公钥使用X509EncodedKeySpec生成 keySpec
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(key));
// 使用KeyFactory生成秘钥对方法
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
//生成公钥
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// 初始化cipher 传入keySpec
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(Base64.decode(secretContent.getBytes()));
return new String(result);
}
}
阮一峰老师对数字签名和数字证书理解的分享,我认为非常形象的解释了什么是数字证书,什么是签名,什么是CA,如何生成数字证书和数字签名。
数字签名(又称公钥数字签名)是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术来实现的,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。数字签名是非对称密钥加密技术与数字摘要技术的应用。
数字签名是什么
public class SignatureDemo {
public static void main(String[] args) throws Exception {
String content = "hello Jim";
// 非对称加密算法
String algorithm = "RSA";
String pubKeyPath = "./pub.key";
String priKeyPath = "./pri.key";
// 获取公钥和私钥
PublicKey publicKey = getPublicKey(pubKeyPath, algorithm);
PrivateKey privateKey = getPrivateKey(priKeyPath, algorithm);
// 数字签名的算法 这里选择sha256作为数字摘要算法结合rsa非对称加密算法最终确定数字签名算法
String signatureAlgorithm = "sha256withrsa";
// 获取数字签名
String signature = getSignature(content, signatureAlgorithm, privateKey);
System.out.println(signature);
// 签证数字签名是否如何
System.out.println(verifySignature(content, signature, signatureAlgorithm, publicKey));
}
/**
* 校验签名是否合法
* @param content 原文内容
* @param signatureData 数字签名数据
* @param signatureAlgorithm 签名算法
* @param publicKey 公钥
* @return
* @throws Exception
*/
private static Boolean verifySignature(String content, String signatureData, String signatureAlgorithm, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance(signatureAlgorithm);
// 初始化校验
signature.initVerify(publicKey);
// 传入得到的原文
signature.update(content.getBytes());
// 如果返回boolean 说明是没有被篡改过的原文,如果返回false 说明被篡改过或者不是正确的发送者
return signature.verify(Base64.decode(signatureData));
}
/**
* 生成数字签名
*
* @param content 需生成签名的原文
* @param signatureAlgorithm 数字签名算法
* @param privateKey 私钥
* @return
* @throws Exception
*/
private static String getSignature(String content, String signatureAlgorithm, PrivateKey privateKey) throws Exception {
// 调用JDK 的Signature类获取签名工具方法类
Signature signature = Signature.getInstance(signatureAlgorithm);
// 通过私钥初始化数字签名
signature.initSign(privateKey);
// 传入原文
signature.update(content.getBytes());
// 签名
byte[] sign = signature.sign();
// 将签名数据base64可视化
return Base64.encode(sign);
}
/**
* 获取公钥
*
* @param publicKeyPath 公钥路径
* @param algorithm 算法
* @return
* @throws Exception
*/
public static PublicKey getPublicKey(String publicKeyPath, String algorithm) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
String publicKeyStr = FileUtil.readString(publicKeyPath, Charset.defaultCharset());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(publicKeyStr));
return keyFactory.generatePublic(keySpec);
}
/**
* 获取私钥
*
* @param privateKeyPath 私钥路径
* @param algorithm 算法
* @return
* @throws Exception
*/
public static PrivateKey getPrivateKey(String privateKeyPath, String algorithm) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
String privateKeyStr = FileUtil.readString(privateKeyPath, Charset.defaultCharset());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyStr));
return keyFactory.generatePrivate(keySpec);
}
}
所谓Base64,即是说在编码过程中使用了64种字符:大写A到Z、小写a到z、数字0到9、“+”和“/”
Base64原理
1.base64将3个字节分成一组,每个字节8位,形成24位之后,然后将3个字节转成4组,每组6位,组成新的数据,对于不足3个字节的后面补0处理。这样的好处在于生成新的6位数据转十进制之后一定会落在0-63之间,这样就对应了base64码表上的64个字符
2.将新生成的6位的数据转成十进制得到Base64码表的索引,通过索引找到转换之后的字符
对于不足6位的在后面补零,得到一个新的二进制
对于不如3个字节的部分以=代替
如字符a只有一个字节
1.先获取a的ASCII码表值为97
2.97转二进制为01100001
3.每6位分一组不足补0 011000 010000
4.转十进制 24 16
5.查询base64码表YQ但是a只有一个字节不出3个字节剩余2个字节用==补全
6.的带YQ==
如Man
转ASCII码 77 97 110
转二进制 01001101 01100001 01101110
按6位分割 刚好分成4位 010011 010110 000101 101110
转10进制 19 22 5 46
查询base64码表 T W F u