该方案使用RSA加密和解密。
每次登录前,客户端从服务器端获取公钥和随机值。
公钥用于加密明文;
随机值可以加强每一次操作的安全性,随机值也加入明文中一并加密,服务端对随机值进行校验,校验后从缓存中销毁,这样就算被别人拿到加密后的密文再次发起请求,由于随机值已失效,请求也是无效的。
下面以js客户端为例,演示一下流程:
1、假设客户的密码以SHA256加密后存在数据库中
2.、客户输入用户名和密码点击 “登录”后,客户端发起请求,从服务器端获取公钥和随机值。
{ "rand": "SAXpJg", "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCK+oqElHP94+1BhhiTKX0pzziepN+C5Ff/qgmind2XvD35eWlCqzypGIXBoki526ZbsqrssbxTy5imhthe4eUTenLGUKkUgYUmDWrus8NmJm6IlXuqbGHaEY1zocsnlqVezOMj0AIUq5L65Y6e5XnEf1ludSzTF73MtFTjW8TRyQIDAQAB" }
3、客户端将用户输入的密码使用SHA256加密
<!--下载地址:https://github.com/Caligatio/jsSHA --> <script type="text/javascript" src="sha.js"></script> <!--下载地址:https://github.com/travist/jsencrypt--> <script type="text/javascript" src="jsencrypt.js"></script> <script> //用户输入的密码 var password1 = '123456'; //从服务端获得的公钥 var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCK+oqElHP94+1BhhiTKX0pzziepN+C5Ff/qgmind2XvD35eWlCqzypGIXBoki526ZbsqrssbxTy5imhthe4eUTenLGUKkUgYUmDWrus8NmJm6IlXuqbGHaEY1zocsnlqVezOMj0AIUq5L65Y6e5XnEf1ludSzTF73MtFTjW8TRyQIDAQAB"; //从服务端获得的随机值 var rand = 'SAXpJg'; //SHA-256加密 var shaObj = new jsSHA("SHA-256", "TEXT"); shaObj.update(password1); var hash = shaObj.getHash("HEX"); //组装明文:由加密后的密码和随机值组成 var text = hash + '|' + rand; console.log("待加密的文本: " + text); //使用RSA公钥加密 var encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); // password就可以发送到服务端进行解密校验了 var password = encrypt.encrypt(text); console.log("加密后的密文:" + password); </script>
控制台打印出来的结果:
待加密的明文:8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92|SAXpJg
加密后的密文:dgUBkZPZgL76+zMbKckAxb3C072I8b4nqAZlWUD/24Hp7UpAgiKx4P90xgs1UhWM2qputsjgpsgXLCNUg2vtO9MxpQk6zWUbyh4cxL08UcmMv3KIMO5rnbFxKEmuIbQ2G/3UZT8c+w899ERLCpDVyHrKSijdpvVoKrB6PzyjP+w=
然后将加密后的密文传到服务器端即可。
4、服务器端代码
RSAUtils.java
import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; import java.security.Security; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import javax.crypto.Cipher; import org.apache.commons.codec.binary.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; /** * RSA 工具 * @author Luxh * */ public class RSAUtils { private static final String ALGORITHM = "RSA"; private static final String PROVIDER = "BC"; private static final String TRANSFORMATION = "RSA/None/PKCS1Padding"; private static final int KEY_SIZE = 1024; private static KeyPair keyPair = null; /** * 初始化密钥对 */ static { try{ Security.addProvider(new BouncyCastleProvider()); SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER); keyPairGenerator.initialize(KEY_SIZE, secureRandom); keyPair = keyPairGenerator.generateKeyPair(); }catch(Exception e) { e.printStackTrace(); } } /** * 获取公钥 * @return */ public static RSAPublicKey getRSAPublicKey() { return (RSAPublicKey)keyPair.getPublic(); } /** * 获取Base64编码的公钥 * @return */ public static String getBase64PublicKey() { RSAPublicKey publicKey = getRSAPublicKey(); //return new String(Base64.encodeBase64(publicKey.getEncoded())); return Base64.encodeBase64String(publicKey.getEncoded()); } /** * 使用公钥加密 * @param data * @return */ public static String encrypt(byte[] data) { String ciphertext = ""; try { Cipher cipher = Cipher.getInstance(keyPair.getPublic().getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic()); ciphertext = Base64.encodeBase64String(cipher.doFinal(data)); } catch (Exception e) { e.printStackTrace(); } return ciphertext; } /** * 使用私钥解密 * @param ciphertext * @return */ public static String decrypt(String ciphertext) { String plaintext = ""; try { Security.addProvider(new BouncyCastleProvider()); Cipher cipher = Cipher.getInstance(TRANSFORMATION, PROVIDER); RSAPrivateKey pbk = (RSAPrivateKey)keyPair.getPrivate(); cipher.init(Cipher.DECRYPT_MODE, pbk); byte[] data = cipher.doFinal(Base64.decodeBase64(ciphertext)); plaintext = new String(data); }catch (Exception e) { e.printStackTrace(); } return plaintext; } }
DemoController.java
import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.google.common.collect.Maps; import io.caimi.util.RSAUtils; @RestController public class DemoController { /** * 获取公钥和随机值 * */ @RequestMapping("/secret") public Map<String, Object> secret(HttpServletRequest request) { Map<String, Object> resultMap = Maps.newHashMap(); // 获取公钥 String publicKey = RSAUtils.getBase64PublicKey(); resultMap.put("publicKey", publicKey); // 生成随机值 String rand = RandomStringUtils.randomAlphabetic(6); resultMap.put("rand", rand); // 将生成的随机值存到session中,实际使用可以存到第三方缓存中,并设置失效时间 request.getSession().setAttribute("rand", rand); return resultMap; } /** * 校验 * */ @RequestMapping(value="/check", method=RequestMethod.POST) public String check(HttpServletRequest request) { // 取得密文 String password = request.getParameter("password"); // 解密 String plaintext = RSAUtils.decrypt(password); String[] arr = plaintext.split("\\|"); // 校验随机值 String rand = arr[1]; String randInSession = (String) request.getSession().getAttribute("rand"); //随机值失效 request.getSession().removeAttribute("rand"); if(!rand.equals(randInSession)) { return "非法的请求"; } // 校验密码 String passwd = arr[0]; // 实际中根据用户名从数据库中查询出密码 String realPasswd = "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92"; if(!realPasswd.equals(passwd)) { return "密码输入错误"; } return "校验通过"; } }
maven依赖的一些jar
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.10</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.54</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency>