北在北方

太白枝头看,花开不计年,杯中浮日月,楼外是青天。

导航

一种简单的登录加密方案

Posted on 2016-04-20 20:27  CN.programmer.Luxh  阅读(2454)  评论(0编辑  收藏  举报

该方案使用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;
    }
    
}
View Code

 

  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 "校验通过";
    }
    
}
View Code

 

  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>