密码学
概述
- 散列函数
散列函数,也见杂凑函数、摘要函数或哈希函数,可将任意长度的消息经过运算,变成固定长度数值,常见的有MD5、SHA-1、SHA256,多应用在文件校验,数字签名中。
MD5 可以将任意长度的原文生成一个128位(16字节)的哈希值
SHA-1可以将任意长度的原文生成一个160位(20字节)的哈希值
- 对称密码
对称密码应用了相同的加密密钥和解密密钥。对称密码分为:序列密码(流密码),分组密码(块密码)两种。流密码是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密,块密码是先对信息流分块,再对每一块分别加密。
例如原文为1234567890,流加密即先对1进行加密,再对2进行加密,再对3进行加密……最后拼接成密文;块加密先分成不同的块,如1234成块,5678成块,90XX(XX为补位数字)成块,再分别对不同块进行加密,最后拼接成密文。前文提到的古典密码学加密方法,都属于流加密。
- 非对称密码
对称密码的密钥安全极其重要,加密者和解密者需要提前协商密钥,并各自确保密钥的安全性,一但密钥泄露,即使算法是安全的也无法保障原文信息的私密性。
在实际的使用中,远程的提前协商密钥不容易实现,即使协商好,在远程传输过程中也容易被他人获取,因此非对称密钥此时就凸显出了优势。
非对称密码有两支密钥,公钥(publickey)和私钥(privatekey),加密和解密运算使用的密钥不同。用公钥对原文进行加密后,需要由私钥进行解密;用私钥对原文进行加密后(此时一般称为签名),需要由公钥进行解密(此时一般称为验签)。公钥可以公开的,大家使用公钥对信息进行加密,再发送给私钥的持有者,私钥持有者使用私钥对信息进行解密,获得信息原文。因为私钥只有单一人持有,因此不用担心被他人解密获取信息原文。
- base64
Base64就是一种基于64个可打印字符来表示二进制数据的方法。
Base64编码是从二进制到字符的过程。
在项目中,将报文进行压缩、加密后,最后一步必然是使用base64编码,因为base64编码的字符串,更适合不同平台、不同语言的传输。
BASE64是编码, 不是压缩, 编码后只会增加字节数;(比之前多3分之一)
算法简单, 几乎不会影响效率;
算法可逆, 解码很方便, 不用于私密信息通信;
加密后的字符串只有[0-9a-zA-Z+/=], 不可打印字符(包括转移字符)也可传输;
- 数字签名
A发送消息给B:A(不可抵赖)——————>B(不可伪造)(不可重用)
(1)不可抵赖 (假如:A可以否认发过该消息,B无法证明A确实发了该消息)
(2)不可伪造 (假如:B伪造一个不同的消息,但声称是从A收到的)
(3)不可重用 (假如:签名没有和消息绑定)
传统笔迹签名:
一般认为,传统的手写签名具有以下特点:
1)手写签名是“不可伪造”的
2)手写签名是“不可抵赖”的
3)手写签名是“不可重用”的
数字签名至少应该具有这些能力
- 消息摘要:
消息摘要:Message Digest,又称为数字摘要(Digital Digest),要高大上还可以将其叫为:单向散列函数(one-way hash function)、哈希函数、杂凑函数等。消息摘要是一个唯一对应一个消息的固定长度的值,由一个单向哈希加密函数对消息进行计算而得到。通俗点说,无论是什么消息、无论什么时间、什么地点,只要采用同样的计算规则(算法),得到的结果都是一样的;并且无论消息长还是短,同一个算法计算得到的长度都是固定的。
从上面的描述可以看出消息摘要的特点:
1)只要消息不同,对其摘要后产生的结果也不同;
2)相同的消息一定会得到相同的结果;
3)无论消息的长短,计算出来的消息摘要长度是固定的;例如:
MD5 摘要后的长度为 128 个bit;
SHA-1 摘要后的长度为 160 个bit;
4)消息摘要算法是单向的,不可逆。
常见的消息摘要算法有: MD5、SHA-1、SHA-256、SHA-512,其他还有 MD4、SHA-2、SHA-224、SHA-384 等。
加密的代码实现
散列函数
md5:
md5,其实就是一中算法。可以将一个字符串,或文件,或压缩包,执行md5后,就可以生成一个固定长度为128bit的串。这个串,基本上是唯一的。把一个任意长度的字节串变换成⼀定⻓度的⼗六进制数字串。 默认为128bit,也就是128个0和1的二进制串。这样表达是很不友好。所以将二进制转成了16进制,每4个bit表示一个16进制所以128/4 = 32 换成16进制表示后,为32位了。
Java md5 代码测试:
DigestUtils 全类名为 org.apache.commons.codec.digest.DigestUtils;
@org.junit.Test
public void md5test(){
String name = "hello world";
String encode = DigestUtils.md5Hex(name);
System.out.println( encode+","+encode.length());//5eb63bbbe01eeed093cb22bb8f5acdc3,32
}
SHA-1
SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。 【维基百科】
@org.junit.Test
public void sha1test(){
String name = "hello world";
String encode = DigestUtils.sha1Hex(name);
System.out.println( encode+","+encode.length());//2aae6c35c94fcfb415dbe95f408b9ce91ee846ed,40
}
对称加密
DES
返回结果:
jdk des encrypt:70a5396f60b0ccd35ce507382664fb93
jdk des decrypt:hello world
@org.junit.Test
public void jdkDES() {
String string = "hello world";
try {
// 生成key//返回生成指定算法密钥的KeyGenerator对象
KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");
keyGenerator.init(56);//初始化此密钥生成器,使其具有确定的密钥大小
SecretKey secretKey = keyGenerator.generateKey();//生成一个密钥
byte[] bs = secretKey.getEncoded();
// key转换
DESKeySpec desKeySpec = new DESKeySpec(bs); //实例化DES密钥规则
SecretKeyFactory factory = SecretKeyFactory.getInstance("DES"); //实例化密钥工厂
Key convertSecretKey = factory.generateSecret(desKeySpec); //生成密钥
// 加密
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, convertSecretKey);
byte[] result = cipher.doFinal(string.getBytes());
System.out.println("jdk des encrypt:" + Hex.encodeHexString(result));
// 解密
cipher.init(Cipher.DECRYPT_MODE, convertSecretKey);
result = cipher.doFinal(result);
System.out.println("jdk des decrypt:" + new String(result));
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
AES:
jdk aes encrypt:[B@76f2b07d
jdk aes decrypt:hello world
@org.junit.Test
public void jdkAES() {
String string = "hello world";
try {
// 根据指定的 RNG 算法, 创建安全随机数生成器
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
// 设置 密钥key的字节数组 作为安全随机数生成器的种子
random.setSeed(string.getBytes());
// 创建 AES算法生成器
KeyGenerator gen = KeyGenerator.getInstance("AES");
// 初始化算法生成器
gen.init(128);
SecretKey secretKey = gen.generateKey();
// 获取 AES 密码器
Cipher cipher = Cipher.getInstance("AES");
// 初始化密码器(加密模型)
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// 加密
byte[] result = cipher.doFinal(string.getBytes());
System.out.println("jdk aes encrypt:" +result);
// 解密
// 初始化密码器(解密模型)
cipher.init(Cipher.DECRYPT_MODE, secretKey);
result = cipher.doFinal(result);
System.out.println("jdk aes decrypt:" + new String(result));
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
非对称加密
RSA
公钥加密,私钥解密
私钥:[B@2cfb4a64
公钥:[B@5474c6c
jdk rsa encrypt:[B@184f6be2
jdk rsa decrypt:hello world
@org.junit.Test
public void jdkRSA() {
try {
String string = "hello world";
KeyPair generator = KeyPairGenerator.getInstance("RSA").generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) generator.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) generator.getPublic();
String privateKeyStr = String.valueOf(Base64.encode(privateKey.getEncoded()));
String publicKeyStr = String.valueOf(Base64.encode(publicKey.getEncoded()));
System.out.println("私钥:" + privateKeyStr);
System.out.println("公钥:" + publicKeyStr);
// 获取 RSA 密码器
Cipher cipher = Cipher.getInstance("RSA");
// 初始化密码器(加密模型)
cipher.init(Cipher.ENCRYPT_MODE, (RSAPublicKey) generator.getPublic());
byte[] result = cipher.doFinal(string.getBytes("UTF-8"));
System.out.println("jdk rsa encrypt:" + result);
cipher.init(Cipher.DECRYPT_MODE, (RSAPrivateKey) generator.getPrivate());
result = cipher.doFinal(result);
System.out.println("jdk rsa decrypt:" + new String(result));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
base64
使用的包: sun.misc.BASE64Decoder; sun.misc.BASE64Encoder;
返回结果:
aGVsbG8gd29ybGQ=
hello world
@org.junit.Test
public void testBase64(){
String string = "hello world";
String encode =new BASE64Encoder().encode(string.getBytes());
System.out.println(encode);
try {
byte[] result = new BASE64Decoder().decodeBuffer(encode);
System.out.println( new String(result));
} catch (IOException e) {
e.printStackTrace();
}
}
数字签名
使用的包: sun.misc.BASE64Decoder; sun.misc.BASE64Encoder;
返回结果:
私钥:[B@2cfb4a64
公钥:[B@5474c6c
signature = X5gkjGn/ULpEftsLnLmqR7w1jPDtN7dH7GTl+Meh89zDyuYM2PVXhvLEyqwc1i+xiLUsv1WLy/q1
Jheiv3t2DcZIAyRyHHTqaQVDpSB5qWsDrkD6P+C3B8TKONiNuXqjOuUyQhQgj9EGLsZaMR1e6/aU
0EQqKB61fh2wwwFMqRyP/CqIKjjyf302CSrr9+ooHLI7JxzR6PVCK0TfDB1uQIkHFAzL+/XJWR1T
v0NZMC/QKdPqP+Qm34PRB6IIqUHXo23FPDhdMyLRaS//ZsA80hKE8WEMssIKELhICxCDVjxY2TuO
qftYYLhIqoz9yF18MzwLGiDjj7OEoQZ7paOmTg==
verify = true
@org.junit.Test
public void verySingn() {
String string = "hello world";
try {
/****************生成密钥部分*****************/
KeyPair generator = KeyPairGenerator.getInstance("RSA").generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) generator.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) generator.getPublic();
String privateKeyStr = String.valueOf(Base64.encode(privateKey.getEncoded()));
String publicKeyStr = String.valueOf(Base64.encode(publicKey.getEncoded()));
System.out.println("私钥:" + privateKeyStr);
System.out.println("公钥:" + publicKeyStr);
/****************得到签名 && 验证签名部门*****************/
// 获取签名对象
Signature signature = Signature.getInstance("sha256withrsa");
// 初始化签名
signature.initSign(privateKey);
// 传入原文
signature.update(string.getBytes());
// 签名
byte[] sign = signature.sign();
// Base64编码
String encode = new BASE64Encoder().encode(sign);
System.out.println("signature = " + encode);
// 初始化校验
signature.initVerify(publicKey);
// 传入原文
signature.update(string.getBytes());
// 校验
byte[] decode = new BASE64Decoder().decodeBuffer(encode);
boolean verify = signature.verify(decode);
System.out.println("verify = " + verify);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
摘要算法
package com.test.digest;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 消息摘要算法,为了防止篡改
*/
public class DigestDemo {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 原文
String input = "Hello World";
// 算法
String algorithm = "MD5";
String MD5 = getDigest(input, algorithm);
// 使用base64进行转码
// String encode = Base64.encode(digest1);
// System.out.println("encode = " + encode);
System.out.println("MD5 = " + MD5);
System.out.println("MD5.length() = " + MD5.length());
String sha1 = getDigest(input, "SHA-1");
System.out.println("sha1 = " + sha1);
System.out.println("sha1.length() = " + sha1.length());
String sha256 = getDigest(input, "SHA-256");
System.out.println("sha256 = " + sha256);
System.out.println("sha256.length() = " + sha256.length());
String sha512 = getDigest(input, "SHA-512");
System.out.println("sha512 = " + sha512);
System.out.println("sha512.length() = " + sha512.length());
}
private static String getDigest(String input, String algorithm) throws NoSuchAlgorithmException {
// 创建消息摘要对象
MessageDigest digest = MessageDigest.getInstance(algorithm);
// 执行消息摘要算法
byte[] digest1 = digest.digest(input.getBytes());
System.out.println("密文的字节长度:" + digest1.length);
// 转16进制
return toHex(digest1);
}
private static String toHex(byte[] input) {
StringBuilder sb = new StringBuilder();
for (byte b : input) {
// 转成 16进制
String s = Integer.toHexString(b & 0xff);
//System.out.println(s);
if (s.length() == 1){
// 如果生成的字符只有一个,前面补0
s = "0"+s;
}
sb.append(s);
}
System.out.println(sb.toString());
return sb.toString();
}
}