RSA加密算法及其与SpringMVC集成
如有不足,敬请各位提出批评,定会改正。THX!
本文介绍的是RSA加密算法+Spring Security在SpringMVC中的集成使用。
Spring Security是什么?
学习Spring Security的网址http://www.iteye.com/blogs/subjects/spingsecurity3inside。
Spring-Security 自带的加密算法有
- bcrypt
- plaintext
- sha
- sha-256
- md5
- md4
- {sha}
- {ssha}
由于md5加密算法,使用较为普遍,但是可以通过碰撞的方式破解,不采用。
RSA是什么?
同RSA(Ron Rivest,Adi Shamir,Len Adleman三位天才的名字)一样,ECC(Elliptic Curves Cryptography,椭圆曲线密码编码学)也属于公开密钥算法。但是ECC算法在jdk1.5后加入支持,目前仅仅只能完成密钥的生成与解析。
RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。今天只有短的RSA钥匙才可能被强力方式解破。到2008年为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被解破的。但在分布式计算和量子计算机理论日趋成熟的今天,RSA加密安全性受到了挑战。RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
RSA密钥长度随着保密级别提高,增加很快。下表列出了对同一安全级别所对应的密钥长度。
保密级别
|
对称密钥长度(bit)
|
RSA密钥长度(bit)
|
ECC密钥长度(bit)
|
保密年限
|
80
|
80
|
1024
|
160
|
2010
|
112
|
112
|
2048
|
224
|
2030
|
128
|
128
|
3072
|
256
|
2040
|
192
|
192
|
7680
|
384
|
2080
|
256
|
256
|
15360
|
512
|
2120
|
如何实现的RSA+Spring Security 在Spring MVC中的实现,直接上web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" metadata-complete="false" version="2.5"> <!--配置上下文参数,指定spring配置文件的位置 -->
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/applicationContext-*.xml</param-value> </context-param>
<!--Spring Security 过滤器-->
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
<!--Spring 字符集过滤器 -->
<!--包含设置两个参数encoding和forceEncoding--> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>*</url-pattern> </filter-mapping> <filter> <filter-name>hibernateFilter</filter-name> <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class> <init-param> <param-name>excludeSuffixs</param-name> <param-value>js,css,jpg,gif</param-value> </init-param> </filter> <filter-mapping> <filter-name>hibernateFilter</filter-name> <url-pattern>*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>com.user.sec.MyHttpSessionEventPublisher</listener-class> </listener> <!-- <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener> --> <listener> <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class> </listener> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>WEB-INF/classes/properties/log4j.properties</param-value> </context-param> <context-param> <param-name>log4jRefreshInterval</param-name> <param-value>60000</param-value> </context-param> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/applicationContext-springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <servlet-name>springmvc</servlet-name> </filter-mapping> <session-config> <session-timeout>30</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <error-page> <error-code>500</error-code> <location>/500.html</location> </error-page> </web-app>
applicationContext-security.xml
Spring Security采用就近原则,有多个约束时,从上至下只要找到第一条满足就返回,因此因该将最严格的约束放在最前面,而将最宽松的约束放在最后面.auto-config属性可以让spring security为我们自动配置几种常用的权限控制机制,包括form,anonymous, rememberMe等。当然你也可以手工配置。例如:<http auto-config="true">
对于拦截pattern的设置,具体如下:
/ 所有带/的请求
/* 代表这个域下面的请求 例如:/user/xxx 这种会被拦截 但是不会拦截 /user/xxx/xxx
/** 代表跨域请求 例如:/user/xxx 和 /user/xxx/xxx都会被拦截
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd"> <global-method-security secured-annotations="enabled" />
<!--配置不过滤的资源,包括登陆请求,静态资源访问,注册页面,获取登陆注册验证码,当然也包括此篇文章所涉及从后台获取的公钥接口-->
<http pattern="/resources/**" security="none" /> <http pattern="/login.html" security="none" /> <http pattern="/register.html" security="none" /> <http pattern="/admin/user/getPairKey" security="none" <http use-expressions="true" auto-config="true"> <!--允许登录用户操作 --> <intercept-url pattern="/pages/**" access="permitAll" /> <intercept-url pattern="/**" access="isAuthenticated()" /> <!--所有用户均可使用 --> <!-- <intercept-url pattern="/**" access="permitAll" /> --> <!-- 允许匿名用户访问 --> <!-- <intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY" /> --> <!-- <intercept-url pattern="/" access="ROLE_ANONYMOUS" /> --> <!-- 验证失败页面 --> <form-login login-page="/login.html" authentication-failure-url="/login.html?error=true" login-processing-url="/j_spring_security_check" default-target-url="/index.html" always-use-default-target="true" /> <!-- 注销页面 --> <logout logout-success-url="/login.html" /> <!-- 记住登陆 --> <remember-me key="mpbuser" user-service-ref="userDetailService" /> <session-management invalid-session-url="/login.html" session-authentication-error-url="/login.html"> <concurrency-control max-sessions="100" error-if-maximum-exceeded="false" expired-url="/login.html" /> </session-management> <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myDefineFilter" /> <!-- 验证权限不足处理类 --> <access-denied-handler ref="accessDeniedHandler" /> <http-basic /> </http> <beans:bean id="encoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder" /> <beans:bean id="RSAEncoder" class="com.common.component.util.RSAPasswordEncoder" /> <authentication-manager alias="authenticationManager" erase-credentials="false"> <authentication-provider user-service-ref="userDetailService"> <password-encoder ref="RSAEncoder" /> </authentication-provider> <!--自定义Provider --> <!-- <authentication-provider ref="myAuthenticationProvider"> </authentication-provider> --> </authentication-manager> <beans:bean id="myDefineFilter" class="com.user.sec.MyFilterSecurityInterceptor"> <beans:property name="authenticationManager" ref="authenticationManager" /> <!-- <beans:property name="accessDecisionManager" ref="accessDecisionManager" /> --> <beans:property name="securityMetadataSource" ref="databaseDefinitionSource" /> <beans:property name="accessDecisionManager" ref="myAccessDecisionManager" /> </beans:bean> <!-- <beans:bean id="myAuthenticationProvider" class="crowdfunding.user.sec.MyAuthenticationProvider"/> --> <beans:bean id="databaseDefinitionSource" class="com.user.sec.DefinitionSourceFactoryBean"> <beans:constructor-arg ref="resourceDetailService" /> <!-- <beans:constructor-arg ref="userDetailService" /> --> </beans:bean> <beans:bean id="myAccessDecisionManager" class="com.user.sec.MyAccessDecisionManager"> </beans:bean> <beans:bean id="accessDeniedHandler" class="com.user.sec.MyAccessDeniedHandler" /> </beans:beans>
RSAPasswordEncoder.Java
package com.common.component.util; import java.net.URLDecoder; import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.encoding.PasswordEncoder; import Decoder.BASE64Decoder; import Decoder.BASE64Encoder; import com.common.component.util.MpbSecureRSAUtil; /******************************************************* * Description:自定义RSA处理类 * 如需增加Spring Security 另外的加密方式,重新定义此类<br/> ********************************************************/ public class RSAPasswordEncoder implements PasswordEncoder { /***************************************** * Description:明文加密 <br/> * @return * @param rawPass * 密码 ******************************************/ public String encodePassword(String rawPass, Object salt) { String encoder = ""; try { encoder = MpbSecureRSAUtil.decryptString(rawPass); } catch (Exception e) { e.printStackTrace(); } return encoder; } /***************************************** * Description:验证密码是否有效主要是此方法的使用<br/> * * @return boolean ******************************************/ public boolean isPasswordValid(String encPass, String rawPass, Object salt) { if ( !MpbStringUtil.isNotBlank(encPass) || encPass.length() == 0) { return false; } String decPsw = MpbSecureRSAUtil.decryptString(encPass); String inpdecPsw = MpbSecureRSAUtil.decryptStringByJs(rawPass); try { decPsw = URLDecoder.decode(decPsw, "UTF-8"); } catch (Exception e) { e.printStackTrace(); return false; } return inpdecPsw.equals(decPsw); } /***************************************** * Description:私钥解密 <br/> * * @return ******************************************/ public String decryptPassword(String rawPass, Object salt) { String encoder = ""; try { encoder = MpbSecureRSAUtil.decryptString(rawPass); } catch (Exception e) { e.printStackTrace(); } return encoder; } /***************************************** * Description:Base64解密 <br/> * @param key * @return * @throws Exception ******************************************/ public static byte[] decoderBase64(String key) throws Exception { return (new BASE64Decoder()).decodeBuffer(key); } /***************************************** * Description:Base64加密 <br/> * * @param key * @return * @throws Exception ******************************************/ public static String encoderBase64(byte[] key) throws Exception { return (new BASE64Encoder()).encodeBuffer(key); } }
login.html
<script type="text/javascript" src="resources/js/RSA/security.js"></script>
function mysubmit() { if ($("input[name='_spring_security_remember_me']").attr("checked") == "checked") { //如果记住用户名和密码框被选中则执行remenber()方法 remember(); }else{ //如果未选中则清空cookie $.mpbSetCookie("EBS_ISCHECKED", ""); } //从后台获取公钥并加密,将加密后的密文传输到服务器处理 $.mpbPost( "/admin/user/getPairKey", { "_method" : "GET" }, function(data) { var modulus = data['modul']; var exponent = data['exponent']; var key = RSAUtils.getKeyPair(exponent, '', modulus); var pwd = $("#psw").val(); pwd = encodeURIComponent(pwd); pwd = RSAUtils.encryptedString(key, pwd); //$("#psw").attr("value",pwd); $("input[name='j_password']").val(pwd); $("#myform").submit(); }); }
Controler.Java
@ResponseBody @RequestMapping(value = "/getPairKey", method = RequestMethod.GET) public Map<?,?> getKey() throws Exception { RSAPublicKey publicKey = MpbSecureRSAUtil.getDefaultPublicKey(); Map<String, String> key = new HashMap<String,String>(); key.put("modul", new String(Hex.encodeHex(publicKey.getModulus().toByteArray()))); key.put("exponent", new String(Hex.encodeHex(publicKey.getPublicExponent() .toByteArray()))); return key; }
文件地址:
https://files.cnblogs.com/files/Sonet-life/security.js
https://files.cnblogs.com/files/Sonet-life/MpbSecureRSAUtil.rar
整个加密的思路:
1、项目在启动时就检查是否生成密钥对文件,没有就强制生成新的,记住利用RSA注册的用户在数据库保存的是加密后的密文,RSA密钥对文件要保证一致性,如果密钥对文件生成了新的,而且你没备份之前的密钥对文件,将会造成不可挽回的后果。最好的方法,通过对MpbSecureRSAUtil中密钥对文件的生成路径设置成硬盘下的,不要放到Tomcat服务器中,否则每次你删除项目的时候,或者重新部署都会造成密钥对文件的重新生成。或者每次都用旧的替换新生成的,保持可用性。
2、前台请求服务器的公钥,传输到前台,因为公钥是公开的,使用公钥在前台加密用户登录注册或者其他保密的信息,将公钥加密的密文传输到服务器。服务器通过与公钥对应的私钥,解密数据库的密文和传输过来的密文,将解密后的字符相比较,再将确认信息返回到前台,进行下一步的操作。