数字签名简介
为什么需要数字签名:
因为信息时代,习惯在网络上交流,而网络上的消息很容易被别有用心的人伪造,窃取,篡改。
数字签名:
是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。(将信息类比与人,那么数字签名的作用就类似于身份证。用于对消息的发送者身份及内容的确认)
所以数字签名有两个作用,一个是对消息发送者身份的确认 二是对消息内容未被篡改的确认。
如何做到的
假设有三个人 Bob和Lisa是一对恋人,而Doug是想要破坏他们情感的黑客。
不使用数字签名的情况
Bob向Lisa发送一串消息
Doug可以很轻易的通过网络抓取到这串消息,甚至在发送到Lisa的途中被暗中的修改。这个时候Doug能获取消息的内容,并能篡改消息的内容。
使用数字签名
对信息进行加密让Doug不能辨别到原始的信息内容,也不能修改
Bob对发送的消息(I Love You)进行加密处理。
第一步:对消息进行Hash 散列算法,生成信息摘要。散列算法可以将任意数据产生出一个固定位数的散列值,具体看采用什么算法,后面会介绍。
散列算法具有以下特点:
- 压缩性:无论数据长度是多少,计算出来的MD5值长度相同
- 容易计算性:由原数据容易计算出MD5值
- 抗修改性:即便修改一个字节,计算出来的MD5值也会巨大差异
- 抗碰撞性:知道数据和MD5值,很小概率找到相同MD5值相同的原数据。
第二步:对生成的信息摘要进行加密,一般采用非对称加密算法,一般是RSA算法,它有两个秘钥。这两个秘钥完全不同但又能互相解密。长的秘钥一般作位私钥,短的秘钥作为公钥。Bob把公钥散播出去,而把私钥保留不让任何人知道。
Bob使用自己的私钥对摘要进行加密处理,得到一串加密后的信息摘要,这个一般叫做数字签名(signature)。
第三步:Bob将消息和数字签名 一起发送个Lisa。
Lisa收到信息,首先使用Bob的公钥对数字签名进行解密,如果能解密则证明这则消息是Bob发出来的(因为公钥私钥互相解密,钥匙不正确java会抛出异常),再对信息进行Hash散列算法,如果与解密后的摘要一直则认为消息未被篡改。
如此一来,就算Doug获取了消息的内容,也无法对消息进行篡改。
但是如果Doug通过手段 把Lisa手中的Bob的公钥替换成了自己的公钥呢?也就是说 Lisa 如何保证自己手中的Bob的公钥是真实的未被篡改的呢。或者说Lisa如何才能获取到正确的Bob公钥。
现在的问题转变为了公钥的真实性谁做担保
为了解决这个问题,解决公钥发布的困难,建立了许多证书机构来为这些公钥做担保。例如Bob可以到证书机构,给出他的身份证各种身份证明以及他的公钥。证书机构一旦确认Bob的身份,证书机构将会用证书机构的私钥对Bob的公钥进行签名,最终得到的数字序列被称为数字证书。
于是有了第四步:
第四步:Bob将自己的公钥去证书中心做认证并得到数字证书,Bob将要发送的消息和数字签名以及数字证书一起发送给Lisa。
这样Lisa去证书中心获取证书中心的公钥解开数字证书得到Bob的公钥,再用Bob的公钥解开数字签名即可。
但是这样就可以安全了吗?不!难道证书中心的公钥就一定是可信的吗,如果厉害的黑客把证书中心的私钥给替换了,或者是窃取了证书中心的私钥。还是存在风险的,没有绝对的安全。
还有就是私钥的保存问题,如果Bob的私钥泄露后也会造成安全风险。或者将来有一天大整数的因数分解不再困难,那么可以通过公钥推导出私钥。
错误秘钥解密,java报错。因为解析错误
散列(Hash)算法
MD5
介绍:
MD5信息摘要算法(MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以将任意数据产生出一个128位(16字节)的散列值,用于确保信息传输完整一致。
应用:
可以在登录注册模块使用MD5加密用户的密码再存入数据库,防止有人直接从数据库获取用户信息。
MD5算法有以下特点:
- 压缩性:无论数据长度是多少,计算出来的MD5值长度相同
- 容易计算性:由原数据容易计算出MD5值
- 抗修改性:即便修改一个字节,计算出来的MD5值也会巨大差异
- 抗碰撞性:知道数据和MD5值,很小概率找到相同MD5值相同的原数据。
代码:
/**
* MD5散列
* @param str
* @return
*/
public byte[] getMD5(String str) {
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] bytes = messageDigest.digest(str.getBytes());
return bytes;
}
//或者使用hutool工具包
//链接:http://www.hutool.cn/docs/#/crypto/摘要加密-Digester
SHA系列
安全散列算法(Secure Hash Algorithm,SHA)是一个密码散列函数家族,是FIPS所认证的安全散列算法。
能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。
就是说SHA-1加密算法有碰撞的可能性,虽然很小。
SHA256都会产生一个256bit长的散列值(哈希值),用于确保信息传输完整一致,称作消息摘要。这个摘要相当于是个长度为32个字节的数组,通常用一个长度为64的十六进制字符串来表示。
/**
* SHA-256散列
* @param str
* @return
*/
public byte[] getSHA256(String str){
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] bytes = messageDigest.digest(str.getBytes());
return bytes;
}
HMAC系列
HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,它可以与任何迭代散列函数捆绑使用。
MAC(Message Authentication Code,消息认证码算法)是含有密钥的散列函数算法,兼容了MD和SHA算法的特性,并在此基础上加入了密钥。消息的散列值由只有通信双方知道的秘密密钥K来控制,因次,我们也常把MAC称为HMAC(keyed-Hash Message Authentication Code)。
是一种加密算法,它引入了密钥,其安全性已经不完全依赖于所使用的Hash算法。
/**
* HMAC
* @param str
* @return
*/
public byte[] getHMAC(String str, SecretKeySpec secretKey) {
Mac mac = null;
try {
mac = Mac.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
try {
// 初始化mac
mac.init(secretKey);
} catch (InvalidKeyException e) {
e.printStackTrace();
}
byte[] bytes = mac.doFinal(str.getBytes());
return bytes;
}
要使用不可逆加密,推荐使用SHA256、SHA384、SHA512以及HMAC-SHA256、HMAC-SHA384、HMAC-SHA512这几种算法。
对称加密算法
对称加密算法是应用比较早的算法,在数据加密和解密的时用的都是同一个密钥。
常见的对称加密算法有DES、3DES、AES128、AES192、AES256 其中AES后面的数字代表的是密钥长度。对称加密算法的安全性相对较低,比较适用的场景就是内网环境中的加解密
DES
DES全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法
DES是对称加密算法领域中的典型算法,其密钥默认长度为56位。
DES是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。
密钥长64位,密钥事实上是56位参与DES运算(第8、16、24、32、40、48、56、64位是校验位,使得每个密钥都有奇数个1),分组后的明文组和56位的密钥按位替代或交换的方法形成密文组
/**
* DES加密
*/
public byte[] encryptDES(byte[] dataSource, String pwd){
SecureRandom random = new SecureRandom();
try {
DESKeySpec desKeySpec = new DESKeySpec(pwd.getBytes());
//创建一个密钥工厂
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);
//加密
Cipher cipher = Cipher.getInstance("DES");
//密钥初始化Chiper对象
cipher.init(Cipher.ENCRYPT_MODE, secretKey, random);
//正式执行加密操作
return cipher.doFinal(dataSource);
}catch (Throwable e){
e.printStackTrace();
}
return null;
}
/**
* DES解密
*/
public byte[] decryptDES(byte[] str,String pwd)
throws InvalidKeyException,
NoSuchAlgorithmException,
InvalidKeySpecException,
NoSuchPaddingException,
BadPaddingException,
IllegalBlockSizeException {
SecureRandom random = new SecureRandom();
DESKeySpec desKeySpec = new DESKeySpec(pwd.getBytes());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);
//解密
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE,secretKey,random);
return cipher.doFinal(str);
}
3DES
由于计算机运算能力的增强,原版DES密码的密钥长度变得容易被暴力破解;3DES即是设计用来提供一种相对简单的方法,即通过增加DES的密钥长度来避免类似的攻击,而不是设计一种全新的块密码算法。
3DES(或称为Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称。是DES向AES过渡的加密算法。
它使用3条56位的密钥对数据进行三次加密。是DES的一个更安全的变形。它以DES为基本模块,通过组合分组方法设计出分组加密算法。比起最初的DES,3DES更为安全。密钥长度默认为168位,还可以选择128位。
AES
AES 高级数据加密标准,能够有效抵御已知的针对DES算法的所有攻击。
AES支持三种长度的密钥:
128位,192位,256位 (bit)
AES128,AES192,AES256,实际上就是指的AES算法对不同长度密钥的使用。
把明文按照128bit拆分成若干个明文块。
按照选择的填充方式来填充最后一个明文块。
每一个明文块利用AES加密器和密钥,加密成密文块。
拼接所有的密文块,成为最终的密文结果。
非对称加密算法
非对称加密算法有两个密钥,这两个密钥完全不同但又完全匹配。只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。常见的非对称加密有RSA、SM2等。
RSA
1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字命名,叫做RSA算法。从那时直到现在,RSA算法一直是最广为使用的"非对称加密算法"。毫不夸张地说,只要有计算机网络的地方,就有RSA算法。
这种算法非常可靠,密钥越长,它就越难破解。1024位的RSA密钥基本安全,2048位的密钥极其安全。
RSA密钥至少为500位长,一般推荐使用1024位。
RSA算法的核心是:欧拉函数、互质关系、模反元素
RSA秘钥生成过程
加密是“求E次方,然后mod N”
解密是“求D次方,然后mod N
加密盐
加密盐就是一个随机字符串用来和我们的加密串拼接后进行加密。
加盐主要是为了提供加密字符串的安全性。假如有一个加盐后的加密串,黑客通过一定手段这个加密串,他拿到的明文,并不是我们加密前的字符串,而是加密前的字符串和盐组合的字符串,这样相对来说又增加了字符串的安全性。
Base64编码
为什么需要Base64编码:
我们知道在计算机中的字节共有256个组合,对应就是ascii码,而ascii码的128~255之间的值是不可见字符。而在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就先把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。
对证书来说,特别是根证书,一般都是作Base64编码的,因为它要在网上被许多人下载。电子邮件的附件一般也作Base64编码的,因为一个附件数据往往是有不可见字符的。
Base64如何编码:
任何数据都可以看做一个比特流,而正常一个字节是8位,一般对应ASCLL码,但是ASCLL高位部分存在不可见字符,网络上交换数据对不可见字符的处理容易产生错误。故将8位 改为6位,2的六次方,转成都是可见字符,缺点是数据量变大了1/3。
例如三个字节 01110011 00110001 00110011(二进制)
进行Base64编码:6个一组(4组) 011100 110011 000100 110011
然后计算机一个字节占8位,高位补上两个0
00011100 00110011 00000100 00110011
再对应Base64编码转换成字符 c z E z
如果字节数不是3的倍数怎么办,在后面补0 补齐成为3的倍数字节数。
例如:01100111 01011010 (对应ASCll编码的 g Z)
6个一组 011001 110101 1010 (不够)
不够四的倍数组,补0:011001 110101 101000 000000
高位补0:00011001 00110101 00101000 00000000
于是对应 Zlo=
不够字节数补上的0对应的字符是=,与字符A区分
注意:这是标准Base64码表,由于62 63 对应的字符在某些系统中可能存在语义上的错误,所以存在其他变种Base64编码。
标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为ANSI SQL中已将“%”号用作通配符。
为解决此问题,可采用一种用于URL的改进Base64编码,它不仅在末尾去掉填充的’='号,并将标准Base64中的“+”和“/”分别改成了“-”和“_”,这样就免去了在URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。
另有一种用于正则表达式的改进Base64变种,它将“+”和“/”改成了“!”和“-”,因为“+”,“/”以及前面在IRCu中用到的“[”和“]”在正则表达式中都可能具有特殊含义。
此外还有一些变种,它们将“+/”改为“-”或“.”(用作编程语言中的标识符名称)或“.-”(用于XML中的Nmtoken)甚至“_:”(用于XML中的Name)。