Java加密
单向加密
接收一段明文,然后以一种不可逆的方式将它转换成一段密文
①、MD5,将无论多长的数据最后编码128位数据,常用文件校验、密码加密、散列数据
byte[] data = ...;//明文数据
MessageDigest md5 = MessageDigest.getInstance("md5");
md5.update(data);//加密后的数据
对明文加密后,拼接一个随机字符串(存储用户密码时可以选择用户信息位salt)
密文=MD5(MD5(明文) + salt)
②、SHA,安全性高于MD5,最终解密结果是160位数据
MessageDigest sha = MessageDigest.getInstance("SHA1");
sha.update(data);
③、HMAC
MAC算法除了HmacMD5,还有HmacSHA1/HmacSHA256/Hmac/Hmac512
//初始化密钥
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5");
SecretKey secretKey = keyGenerator.generateKey();
byte[] secret = secretKey.getEncoded();
//HMAC加密
SecretKey secretKey = new SecretKeySpec(secret,"HmacMD5");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
byte[] data = ...;//明文
mac.doFinal(data);
对称加密
①、DES
64位的密钥把64位的明文经过16轮替换和移位操作变为64位密文输出块
3个入口参数
- Key 工作密钥,8个字节64位,史记应用其中的56位
- Data 要被加密或解密的数据,8个字节64位
- Mode DES的工作方式,包括加密或解密
//加密
byte[] secret = ...;//密钥
DESKeySpec keySpec = new DESKeySpec(secret);
SecretKey key = SecretKeyFactory.getInstance("DES");
generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE,key);
byte[] encryData = cipher.doFinal(data);//加密数据
//解密
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE,key);
cipher.doFinal(encryData);//解密数据
②、AES
DES的升级版,支持可变分组长度,分组长度可设定为32bit的任意倍数,最小值为128bit,最大值256bit
密钥长度可设定为32bit的任意倍数,范围为128-256bit
byte[] secret = ..;//密钥
SecretKey key = new SecretKeySpec(secret,"AES");
Cipher cipher = Cipher.getInstance(secret,"AES");
cipher.init(Cipher.ENCRYPT_MODE,key);
byte[] encryData = cipher.doFinal(data);//加密数据
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE,key);
cipher.doFinal(encryData);//解密数据
③、PBE
//生成salt
byte[] salt = new byte[8];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
//加密
String password = ..;//用户口令
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHMD5andDES");
SecretKey secretKey = keyFactory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(salt,200);//迭代200次
Cipher cipher = Cipher.getInstance("PBEWITHMD5andDES");
cipher.init(Cipher.ENCRYPT_MODE,secretKey,paramSpec);
byte[] encryData = cipher.doFinal(data);
//解密
PBEParameterSpec paramSpec = new PBEParameterSpec(salt,200);//迭代200次
Cipher cipher = Cipher.getInstance("PBEWITHMD5andDES");
cipher.init(Cipher.DECRYPT_MODE,secretKey,paramSpec);
cipher.doFinal(encryData);
非对称加密
需要两个密钥,一个公开的公钥用来加密数据,一个私有的私钥用来解密数据
RSA
- A构建一对密钥,将公钥公布给B,将私钥保留
- A使用私钥加密数据并签名,发送给B
- B使用A的公钥、签名要验证收到的密文是否有效,若有效则使用A的公钥解密
- B使用A的公钥加密数据,发送给A,A使用自己的私钥进行解密
//生成公钥和私钥
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("SA");
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
//公钥
byte[] publicKey = keyPair.getPublic().getencoded();
//私钥
byte[] privateKey = keyPair.getPrivate().getEncoded();
//公钥加密数据
X509EncodedKeySpec x509KeySpec = new X509EncodeKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Key publicKey = keyFactory.generatePublic(x509KeySpec);
//对数据加密
Cipher cipher Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE,publicKey);
String plainText = "plain text.";
byte[] encryData = cipher.doFinal(plainText.getBytes());
//私钥解密数据
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//对数据解密
Clihper cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE,privateKey);
cipher.doFinal(encryData);
对于私钥使用加密模式,公钥使用解密模式,即可实现私钥签名、公钥验证的流程
DH
- A构建一对密钥,将公钥公布给B,将私钥保留
- B通过A公钥构架密钥对儿,将公钥公布给A,将私钥保留
- AB双方互通本地密钥算法
- AB双方公开自己的公钥,使用对方的公钥和刚才产生的私钥加密数据,同时可以使用对方的公钥和自己的私钥对数据解密
//初始化A的公钥和私钥
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
keyPairGenerator.initialize(1024);//密钥字节数
KeyPair keyPair = keyPairGenerator.generateKeyPair();
byte[] aPublicKey = keyPair.getPublic().getEncoded();//Ad的公钥
byte[] aPrivateKey = keyPair.getPrivate().getEncoded();//A的私钥
//初始化B的公钥和私钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(aPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance("DH");
PublicKey aPubKey = keyFactory.generatePublic(x509KeySpec);
DHParameterSpec dhParamSpec = ((DHPublicKey) aPubKey).getParams();
//由A的公钥构建B的密钥对
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyFactory.getAlgorithm());
keyPairGenerator.ininialize(dhParamSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
byte[] bPublicKey = keyPair.getPublic().getEncoded();//B的公钥
byte[] bPrivateKey = keyPair.getPrivate().getEncoded();//B的私钥
//用A的公钥和B的私钥构建密文
String plainText = "你好";
KeyFactory keyFactory = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(aPublicKey);
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(bPrivateKey);
Key priKey = keyFactory.generatePrivate(okcs8KeySpec);
KeyArgeement keyArgee = KeyAgreement.getInstance(keyFactory.getAlgorithm());
keyArgee.init(priKey);
keyAgree.doPhase(pubKey,true);
SecretKey secretKey = keyAgree.generateSecret("DES");//本地密钥
Clipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE,secretKey);
byte[] encryData = cipher.doFinal(plainText,getBytes());
//用B的公钥、A的私钥解密
KeyFactory keyFactory = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(bPublicKey);
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
KeyAgreement keyAgree = KeyAgreement.getInstance(keyFactory.getAlgorithm());
keyAgree.init(priKey);
keyAgree.doPhase(pubKey,true);
Secret secretKey = keyAgree.generateSecret("DES");//本地密钥
Clipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE,secretKey);
cipher.doFinal(encryData);
HTTPS
不仅对数据进行了加密,还对数据完整性提供了保护,并且提供了身份验证的功能
应用场景:其中一方先生成一个对称加密的密钥,然后通过非对称加密的方式来发送这个密钥,这样双方之后的通信就可以用对称加密这种高效率的算法进行解密了
Https交互过程:
- 客户端向服务端发送请求,将客户端的功能和首选项传递给服务器,包括客户端支持的SSL版本、加密组件列表
- 服务器端发送选择的连接参数(从客户端加密组件中筛选出的加密组件内容和压缩方法)以及证书(包含公钥等信息)给客户端
- 客户端读取证书中的所有人、有效期等信息并进行校验。然后通过预制的CA验证证书合法性,有问题则提示
- 客户端生成用于数据加密的对称密钥,然后用服务器的公钥进行加密并发送给服务器端
- 服务器端使用自己的私钥解密数据,获得用于数据加密的对称密钥
- 安全的通道建立完毕,后续基于对称呢个加密算法传输数据
WEB安全
客户端安全:通过浏览器进行攻击的安全问题
- 跨站点脚本攻击
- 跨站点请求伪造
服务器端安全问题:通过发送请求到服务器进行攻击的安全问题
- SQL注入
- 基于约束条件的SQL攻击
- DDOS攻击
- Session fixation
跨站点脚本攻击XSS
- 一个Java应用提供一个接口可以上传个人动态,动态内容是富文本
- 攻击者上传
- 在服务端和客户端程序未做任何过滤的情况下,当其他用户访问这个动态页面就会执行该脚本
防止方式:
- 对任何允许用户输入的地方做检查,防止其提交脚本相关字符串,如script/onload\onerror等客户端和服务端都要做检查
- 使用Apache Common-Lang中的StringEscapeUtils的带escape前缀的方法来做转义,过滤掉特殊字符或者替换成HTML转义后的字符
- 给Cookie属性设置上HttpOnly,可以防止脚本获取Cookie
- 对输出内容做过滤做过滤,可以在客户端做或者服务端做,服务端主要就是转义HTML字符,客户端可以使用escape方法来过滤
跨站点请求伪造CSRF
- 一个用户登录了一个站点,访问https://img-home.csdnimg.cn/images/20230724024159.png?be=1&origin_url=http://xx/delete_notes?id=xx即可删除一个笔记
- 一个攻击者在它的站点中构造一个页面,HTML内容
- 当用户被有道访问攻击者的站点时就发起了一个删除笔记的请求
防止方式:
- 对重要请求需要验证码,防止用户不知情的情况下被发送请求
- 使用类似防盗链的机制,对header的refer进行校验以确认请求来自合法的源
- 对重要请求都附带一个服务端生成的随机Token,提交时对此Token进行验证。
SQL注入
- 服务器端登录验证使用如下方式,其中userName和userPwd都是用户直接上传的参数
- 用户提交userName为admin’-,userPwd为字符串xxx(用户自己决定)
- 拼接好select * from user where user_name=‘admin’-'and pwd=‘xxx’(–为SQL语句的注释),这样只要存在admin用户,返回的就是admin信息
如果服务器的请求错误没有进一步封装,直接把原始数据错误返回,那么有经验的攻击者通过返回结果多次尝试就能找出SQL注入漏洞
防止方式:
- 构造sql杜绝拼接用户参数,全部使用PreParedStatement预编译语句,通过?来传递参数
- 业务层面过滤,转义SQL特殊字符,Apache Commons-Lang中的StringEscapeUtil提供了escapeSQL功能
基于约束条件的SQL攻击
处理SQL中的字符串时,字符串末尾的空格字符都会被删除,包括WHERE子句和INSERT子句,但LIKE子句除外
在任意的INSERT查询中,SQL会根据varchar(n)来限制字符串最大长度,超过n个字符串的字符只保留前n个字符
防止方式:
- 为具有唯一性的那些列添加UNIQUE索引
- 在数据库操作前先将输入参数修剪为特定长度
分布式拒绝服务攻击DDOS攻击
攻击者利用多台机器同时向某个服务发送大量请求,人为构造并发压力,冲垮服务器
- SYN flood
- UDP flood
- ICMP flood
SYN flood利用了TCP连续三次握手先发送SYN机制,通过发送大量的SYN包使得服务器建立大量半连接消耗资源
解决方式:
- 合理使用缓存、异步提高应用性能
- 合理使用云计算相关组件,自动识别高峰流量并自动扩容
- 应用限制某一IP频繁的请求,黑名单。通过redis中incr和expire实现
String ip NetworkUtil.getClientIP(request,false);//获取客户端IP地址
String key = "ddos." + ip;
long count = suishenRedisTemplate.incr(key);
if(count >1000){
throw new AccessException("access too frequently with id:" + StringUtils.defaultString(ip));
}else{
if(count == 1){
suishenRedisTemplate.expire(key,10);
}
return true;
}
上述代码即可将同一IP的请求限制在10秒10000次
此逻辑越靠近链路前面效果越好,比如直接在NGINX中拦截,比业务中更好
会话固定Session fixation
基于Session做用户会话管理,浏览器中Session Id存储在Cookie中,甚至直接附带在query参数中
如果Session在未登录的情况下不发生改变的话,Session fixation就形成了。
- 攻击者进入网站http://xxx.com
- 攻击者发送http://xx.com?JESSIONID=123456给一个用户
- 用户单击此链接进入网站,由于URL后面带有JESSIONID,因此直接作为Session的id
- 用户成功登录后,攻击者就可以利用伪造的Session ID获取用户的各种操作权限
攻击的关键点就是Tomcat使用了JSESSIONID作为SessionID
因此防范核心之一就是不能使用客户端传来的SessionID
- 不要接受由GET或者POST参数指定的Session ID值
- 针对每一个请求都生成新的Session
- 只接受服务器端生成的Session ID
- 为Session指定过期时间
Java Web项目可以通过实现一个拦截器,将使用query参数传递JESSIONID的请求的Session删除
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws IOException,ServletException{
if(httpRequest.isRequestedSessionIdFromURL()){
HttpSession session = httpRequest.getSession();
if(session != null){
session.invalidate();
}
}
}
此外,每一次登录后的Session都重新生成ID,并设置合理的失效期
public JSONResult login(@RequestBody LoginRequestBody requestBody,ServletRequest request){
boolean loginResult = doLogin();
if(loginResult){
request.changeSessionId();//重新生成SessionID
request.getSession().setMaxInactiveInterval(1800);//30分钟失效
}
}