咱们在求职面试中,常常会被问到,如何设计一个安全对外的接口呢? 其实能够回答这一点,加签和验签,这将让你的接口更加有安全。接下来,本文将和你们一块儿来学习加签和验签。从理论到实战,加油哦~java
本文已经收录到我的github,文章有用的话,能够给个star呀:git
❝https://github.com/whx123/JavaHomegithub
❞
对称加密:加密和解密使用相同密钥的加密算法。 面试
非对称加密:非对称加密算法须要两个密钥(公开密钥和私有密钥)。公钥与私钥是成对存在的,若是用公钥对数据进行加密,只有对应的私钥才能解密。算法
❝假设你有一个文件,你用字母a加密,只有字母b才能解密;或者你用b加密,只有a才能解密,那么a和b就是一对公私钥。若是密钥a公开,密钥b你就要私自保存好啦,这时候密钥a就是公钥,密钥b就是私钥。相反,若是b公开,a就要保存好,这时候呢,秘钥b就是公钥,秘钥a就是私钥。安全
❞
「加签」:用Hash函数把原始报文生成报文摘要,而后用私钥对这个摘要进行加密,就获得这个报文对应的数字签名。一般来讲呢,请求方会把「数字签名和报文原文」一并发送给接收方。并发
「验签」:接收方拿到原始报文和数字签名后,用「同一个Hash函数」从报文中生成摘要A。另外,用对方提供的公钥对数字签名进行解密,获得摘要B,对比A和B是否相同,就能够得知报文有没有被篡改过。app
上小节中,加签和验签咱们已经知道概念啦,那么,为何须要加签和验签呢?有些朋友可能以为,咱们不是用「公钥加密,私钥解密」就行了嘛?编辑器
接下来呢,举个demo吧。函数
❝假设如今有A公司,要接入C公司的转帐系统。在一开始呢,C公司把本身的公钥寄给A公司,本身收藏好私钥。A公司这边的商户,发起转帐时,A公司先用C公司的公钥,对请求报文加密,加密报文到达C公司的转帐系统时,C公司就用本身的私钥把报文揭开。假设在加密的报文在传输过程当中,被中间人Actor获取了,他也郁闷,由于他没有私钥,看着天鹅肉,又吃不了。原本想修改报文,给本身帐号转一个亿的,哈哈。这个实现方式看起来是完美无缺,稳得一匹的。
❞
可是呢,若是一开始,C公司把公钥发给公司A的时候,就被中间人Actor获取到呢,酱紫就出问题了。
❝中间人Actor截取了C的公钥,他把本身的公钥发给了A公司,A误觉得这就是C公司的公钥。A在发起转帐时,用Actor的公钥,对请求报文加密,加密报文到在传输过程,Actor又截取了,这时候,他用本身的私钥解密,而后修改了报文(给本身转一个亿),再用C的公钥加密,发给C公司,C公司收到报文后,继续用本身的私钥解密。最后是否是A公司的转帐帐户损失了一个亿呢~
❞
C公司是怎么区分报文是否是来自A呢,仍是被中间人修改过呢?为了代表身份和报文真实性,这就须要「加签验签」啦!
❝A公司把本身的公钥也发送给C公司,私钥本身保留着。在发起转帐时,先用本身的私钥对请求报文加签,因而获得本身的数字签名。再把数字签名和请求报文一块儿发送给C公司。C公司收到报文后,拿A的公钥进行验签,若是原始报文和数字签名的摘要内容不一致,那就是报文被篡改啦~
❞
有些朋友可能有疑问,假设A在发本身的公钥给C公司的时候,也被中间人Actor截取了呢。嗯嗯,咱们来模拟一波Actor又截取了公钥,看看Actor能干出什么事情来~哈哈
❝假设Actor截取到A的公钥后,随后也截取了到A发往C的报文。他截取到报文后,第一件想作的事确定是修改报文内容。可是若是单单修改原始报文是不能够的,由于发过去C公司确定验签不过啦。可是呢,数字签名彷佛解不开,由于消息摘要算法(hash算法)没法逆向解开的,只起验证的做用呢....
❞
因此呢,公钥与私钥是用来加密与加密的,「加签与验签是用来证实身份」,以避免被篡改的。
消息摘要算法主要分三类:MD(Message Digest,消息摘要算法)、SHA(Secure Hash Algorithm,安全散列算法)和MAC(Message Authentication Code,消息认证码算法)。
MD(Message Digest,消息摘要算法)家族,包括MD2,MD4,MD5。
举个例子,看看如何获取字符串的MD5值吧:
public class MD5Test { public static void main(String[] args) throws UnsupportedEncodingException { String s = "123"; byte[] result = getMD5Bytes(s.getBytes()); StringBuilder stringBuilder = new StringBuilder(); for (byte temp : result) { if (temp >= 0 && temp < 16) { stringBuilder.append("0"); } stringBuilder.append(Integer.toHexString(temp & 0xff)); } System.out.println(s + ",MD5加密后:" + stringBuilder.toString()); } private static byte[] getMD5Bytes(byte[] content) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5.digest(content); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } }
运行结果:
123,MD5加密后:202cb962ac59075b964b07152d234b70
SHA(Secure Hash Algorithm,安全散列算法),包括SHA-0、SHA-一、SHA-2(SHA-256,SHA-512,SHA-224,SHA-384等)、SHA-3。它是在MD算法基础上实现的,与MD算法区别在于「摘要长度」,SHA 算法的摘要「长度更长,安全性更高」。
❝❞
- SHA-0发布以后很快就被NSA撤回,由于含有会下降密码安全性的错误,它是SHA-1的前身。
- SHA-1在许多安全协议中广为使用,包括TLS、GnuPG、SSH、S/MIME和IPsec,是MD5的后继者。
- SHA-2包括SHA-22四、SHA-25六、SHA-38四、SHA-5十二、SHA-512/22四、SHA-512/256。它的算法跟SHA-1基本上类似,目前尚未出现明显弱点。
- SHA-3是2015年正式发布,因为对「MD5出现成功的破解」,以及对SHA-0和SHA-1出现理论上破解的方法,SHA-3应运而生。它与以前算法不一样的是,它是可替换的加密散列算法。
SHA-一、SHA-2(SHA-256,SHA-512,SHA-224,SHA-384)等算法是比较经常使用的,咱们来看看跟MD5的对比吧
算法类型 | 摘要长度(bits) | 最大输入消息长度(bits) | 碰撞攻击(bits) | 性能示例(MiB/s) |
---|---|---|---|---|
MD5 | 128 | 无限 | ≤18(发现碰撞) | 335 |
SHA-1 | 160 | 2^64 − 1 | <63(发现碰撞) | 192 |
SHA-224 | 224 | 2^64 − 1 | 112 | 139 |
SHA-256 | 256 | 2^64 − 1 | 128 | 139 |
SHA-384 | 384 | 2^128 − 1 | 192 | 154 |
SHA-512 | 512 | 2^128 − 1 | 256 | 154 |
MAC算法 MAC(Message Authentication Code,消息认证码算法),是带密钥的Hash函数。输入密钥和消息,输出一个消息摘要。它集合了MD和SHA两大系列消息摘要算法。
加密和解密使用「相同密钥」的加密算法就是对称加密算法。常见的对称加密算法有AES、3DES、DES、RC五、RC6等。
数据加密标准(英语:Data Encryption Standard,缩写为 DES)是一种对称密钥加密块密码算法。DES算法的入口参数有三个:Key、Data、Mode。
三重数据加密算法(英语:Triple Data Encryption Algorithm,又称3DES(Triple DES),是一种对称密钥加密块密码,至关因而对每一个数据块应用三次数据加密标准(DES)算法。
AES,高级加密标准(英语:Advanced Encryption Standard),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。
非对称加密算法须要两个密钥:公钥和私钥。公钥与私钥是成对存在的,若是用公钥对数据进行加密,只有用对应的私钥才能解密。主要的非对称加密算法有:RSA、Elgamal、DSA、D-H、ECC。
国密即国家密码局认定的国产密码算法。为了保障商用密码的安全性,国家商用密码管理办公室制定了一系列密码标准,即SM1,SM2,SM3,SM4等国密算法。
这个小节先介绍一下加签验签须要用到的API吧~
- java.security.Signature.getInstance(String algorithm); //根据对应算法,初始化签名对象 - KeyFactory.getInstance(String algorithm);// 根据对应算法,生成KeyFactory对象 - KeyFactory.generatePrivate(KeySpec keySpec); //生成私钥 - java.security.Signature.initSign(PrivateKey privateKey) //由私钥,初始化加签对象 - java.security.Signature.update(byte[] data) //把原始报文更新到加签对象 - java.security.Signature.sign();//加签
「Signature.getInstance(String algorithm);」
「KeyFactory.getInstance(String algorithm);」
「KeyFactory.generatePrivate(KeySpec keySpec)」
「Signature.initSign(PrivateKey privateKey)」
「Signature.update(byte[] data)」
「java.security.Signature.sign();」
- java.security.Signature.getInstance(String algorithm); //根据对应算法,初始化签名对象 - KeyFactory.getInstance(String algorithm);// 根据对应算法,生成KeyFactory对象 - KeyFactory.generatePublic(KeySpec keySpec); //生成公钥 - java.security.Signature.initVerify(publicKey); //由公钥,初始化验签对象 - java.security.Signature.update(byte[] data) //把原始报文更新到验签对象 - java.security.Signature.verify(byte[] signature);//验签
「Signature.getInstance(String algorithm)」
「KeyFactory.getInstance(String algorithm);」
「KeyFactory.generatePublic(KeySpec keySpec);」
「Signature.initVerify(publicKey);」
「Signature.update(byte[] data)」
「Signature.verify(byte[] signature);」
前几个小节讨论完概念,是时候上代码实战了,我这边用的是SHA-256做为摘要算法,RSA做为签名验签算法,以下:
package pattern; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; /** * 加签验签demo * @Author 捡田螺的小男孩 */ public class SignatureTest { //公钥字符串 private static final String PUBLIC_KEY_STR = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaJzVjC5K6kbS2YE2fiDs6H8pB\n" + "JFDGEYqqJJC9I3E0Ebr5FsofdImV5eWdBSeADwcR9ppNbpORdZmcX6SipogKx9PX\n" + "5aAO4GPesroVeOs91xrLEGt/arteW8iSD+ZaGDUVV3+wcEdci/eCvFlc5PUuZJou\n" + "M2XZaDK4Fg2IRTfDXQIDAQAB"; //私钥字符串 private static final String PRIVATE_KEY_STR = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANonNWMLkrqRtLZg\n" + "TZ+IOzofykEkUMYRiqokkL0jcTQRuvkWyh90iZXl5Z0FJ4APBxH2mk1uk5F1mZxf\n" + "pKKmiArH09floA7gY96yuhV46z3XGssQa39qu15byJIP5loYNRVXf7BwR1yL94K8\n" + "WVzk9S5kmi4zZdloMrgWDYhFN8NdAgMBAAECgYA9bz1Bn0i68b2KfqRdgOfs/nbe\n" + "0XNN1DLQp2t7WDfRCg01iI1zPkZgyFVZWtI85f5/uIrLs5ArLosL1oNuqqc0nNne\n" + "CvJK+ZxvA98Hx3ZqYTzDnleR054YhofL5awbhSciYVic204DOG1rhSsYWMqtX7J7\n" + "3geoWL7TYdMfYXcCAQJBAPMMKsz6ZJh98EeQ1tDG5gpAGWFQkYNrxZDelP/LjeO0\n" + "TP3XkQnIpcaZoCs7V/rRGRGMWwQ2BUdc/01in89ZZ5ECQQDlx2oBc1CtOAm2UAhN\n" + "1xWrPkZWENQ53wTrwXO4qbTGDfBKon0AehLlGCSqxQ71aufLkNO7ZlX0IHTAlnk1\n" + "TvENAkAGSEQ69CXxgx/Y2beTwfBkR2/gghKg0QJUUkyLqBlMz3ZGAXJwTE1sqr/n\n" + "HiuSAiGhwH0ByNuuEotO1sPGukrhAkAMK26a2w+nzPL+u+hkrwKPykGRZ1zGH+Cz\n" + "19AYNKzFXJGgclCqiMydY5T1knBDYUEbj/UW1Mmyn1FvrciHoUG1AkAEMEIuDauz\n" + "JabEAU08YmZw6OoDGsukRWaPfjOEiVhH88p00veM1R37nwhoDMGyEGXVeVzNPvk7\n" + "cELg28MSRzCK"; public static void main(String[] args) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidKeySpecException { //原始报文 String plain = "欢迎你们关注个人公众号,捡田螺的小男孩"; //加签 byte[] signatureByte = sign(plain); System.out.println("原始报文是:" + plain); System.out.println("加签结果:"); System.out.println(new BASE64Encoder().encode(signatureByte)); //验签 boolean verifyResult = verify(plain, signatureByte); System.out.println("验签结果:" + verifyResult); } /** * 加签方法 * @param plain * @return * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws UnsupportedEncodingException * @throws SignatureException */ private static byte[] sign(String plain) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, SignatureException { //根据对应算法,获取签名对象实例 Signature signature = Signature.getInstance("SHA256WithRSA"); //获取私钥,加签用的是私钥,私钥通常是在配置文件里面读的,这里为了演示方便,根据私钥字符串生成私钥对象 PrivateKey privateKey = getPriveteKey(PRIVATE_KEY_STR); //初始化签名对象 signature.initSign(privateKey); //把原始报文更新到对象 signature.update(plain.getBytes("UTF-8")); //加签 return signature.sign(); } /** * 验签方法 * @param plain * @param signatureByte * @return * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws IOException * @throws SignatureException * @throws InvalidKeySpecException */ private static boolean verify(String plain, byte[] signatureByte) throws NoSuchAlgorithmException, InvalidKeyException, IOException, SignatureException, InvalidKeySpecException { //获取公钥 PublicKey publicKey = getPublicKey(PUBLIC_KEY_STR); //根据对应算法,获取签名对象实例 Signature signature = Signature.getInstance("SHA256WithRSA"); //初始化签名对象 signature.initVerify(publicKey); //把原始报文更新到签名对象 signature.update(plain.getBytes("UTF-8")); //进行验签 return signature.verify(signatureByte); } private static PublicKey getPublicKey(String publicKeyStr) throws InvalidKeySpecException, IOException { PublicKey publicKey = null; try { java.security.spec.X509EncodedKeySpec bobPubKeySpec = new java.security.spec.X509EncodedKeySpec( new BASE64Decoder().decodeBuffer(publicKeyStr)); // RSA对称加密算法 java.security.KeyFactory keyFactory; keyFactory = java.security.KeyFactory.getInstance("RSA"); // 生成公钥对象 publicKey = keyFactory.generatePublic(bobPubKeySpec); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return publicKey; } private static PrivateKey getPriveteKey(String privateKeyStr) { PrivateKey privateKey = null; PKCS8EncodedKeySpec priPKCS8; try { priPKCS8 = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(privateKeyStr)); KeyFactory keyf = KeyFactory.getInstance("RSA"); privateKey = keyf.generatePrivate(priPKCS8); } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { e.printStackTrace(); } return privateKey; } }
「运行结果:」
原始报文是:欢迎你们关注个人公众号,捡田螺的小男孩 加签结果: Oz15/aybGe42eGHbc+iMoSYHSCc8tfRskTVjjGSTPD4HjadL0CC5JUWNUW0WxHjUb4MvxWo2oeWE Qw0+m61d+JgBMto/TWcVDcgwL/AbObsbWdQ6E/fVRqG13clkE8MyKsjt9Z7tcbwpycYTv0rUR4co rndAVfBdtv5KeV+OXqM= 验签结果:true
[1]维基百科:https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5
[2]百度百科:https://baike.baidu.com/
[3]经常使用消息摘要算法简介:https://cloud.tencent.com/developer/article/1584742
[4]浅谈常见的七种加密算法及实现:https://juejin.im/post/5b48b0d7e51d4519962ea383#heading-12
[5]【易错概念】国密算法SM1(SCB2)、SM二、SM三、SM四、SM七、SM九、ZUC:https://www.jianshu.com/p/8c3657a1769f