Java 岗大厂面试百日冲刺 - 日积月累,每日三题【Day36】—
通过输出,可以看到密码是 32 位的 MD5:
"password": "325a2cc052914ceeb8c19016c091d2ac"
然后,再去破解网站试一下这个 MD5,就可以得到原始密码是 salt
,也就知道了盐值是 salt
:
其实,知道盐是什么没什么关系,关键的是我们是在代码里写死了盐,并且盐很短
、所有用户都是这个盐。这么做就会有三个问题:
因为盐太短、太简单了,如果用户原始密码也很简单,那么整个拼起来的密码也很短,这样一般的
MD5 破解网站都可以直接解密这个 MD5
,除去盐
就是原始密码
了。相同的盐,意味着使用相同密码的用户 MD5 值是一样的,
知道了一个用户的密码就可能知道了多个
。
黑客也可以使用这个盐来构建一张
彩虹表
,也就是字典表
,虽然会花不少代价,但是一旦构建完成,所有人的密码都可以被破解。
所以,最好是每一个密码都有独立的盐
,并且盐要长一点,比如超过 20 位。
第二,虽然说每个人的盐最好不同,但也不建议将一部分用户数据作为盐。比如,使用用户名作为盐:
userData.setPassword(DigestUtils.md5Hex(name + password));
如果世界上所有的系统都是按照这个方案来保存密码,那么 root、admin 这样的用户使用再复杂的密码也总有一天会被破解,因为黑客们完全可以针对这些常用用户名来做彩虹表
。所以,盐最好是随机的值,并且是全球唯一的,意味着全球不可能有现成的彩虹表给你用。
正确的做法是,使用全球唯一的、和用户无关的、足够长的随机值作为盐。比如,可以使用 UUID 作为盐,把盐一起保存到数据库中:
userData.setSalt(UUID.randomUUID().toString());
userData.setPassword(DigestUtils.md5Hex(userData.getSalt() + password));
并且每次用户修改密码的时候都重新计算盐,重新保存新的密码。
需要注意的是,这么做虽然黑客已经很难通过彩虹表来破解密码了,但是仍然有可能暴力破解
密码,也就是对于同一个用户名使用常见的密码逐一尝试登录。因此,除了做好密码哈希保存的工作外,我们还要建设一套完善的安全防御机制,在感知到暴力破解危害的时候,开启短信验证、图形验证码、账号暂时锁定等防御机制来抵御暴力破解
。
那么姓名和身份证又是怎么保存的?
我们把姓名和身份证,叫做二要素。
现在互联网非常发达,很多服务都可以在网上办理,很多网站仅仅依靠二要素来确认你是谁。所以,二要素是比较敏感的数据,如果在数据库中明文保存,那么数据库被攻破后,黑客就可能拿到大量的二要素信息。如果这些二要素被用来申请贷款等,后果不堪设想。
之前我们提到的单向散列算法(MD5),显然不适合用来加密保存二要素,因为数据无法解密。这个时候,我们需要选择真正的加密算法。可供选择的算法,包括对称加密
和非对称加密算法
两类。
对称加密算法
:是使用相同的密钥进行加密和解密。使用对称加密算法来加密双方的通信的话,双方需要先约定一个密钥,加密方才能加密,接收方才能解密。如果密钥在发送的时候被窃取,那么加密就是白忙一场。因此,这种加密方式的特点是,加密速度比较快,但是密钥传输分发有泄露风险。
非对称加密算法
:或者叫公钥密码算法。公钥密码是由一对密钥对构成的,使用公钥或者说加密密钥来加密,使用私钥或者说解密密钥来解密,公钥可以任意公开,私钥不能公开。使用非对称加密的话,通信双方可以仅分享公钥用于加密,加密后的数据没有私钥无法解密。因此,这种加密方式的特点是,加密速度比较慢,但是解决了密钥的配送分发安全问题。
但是,对于保存敏感信息的场景来说,加密和解密都是我们的服务端程序,不太需要考虑密钥的分发安全性,也就是说使用非对称加密算法没有太大的意义。我们一般会使用对称加密算法来加密数据。常见的对称加密算法有:DES
、3DES
和 AES
。
课间休息,欣赏一下来自咱们群里同学的搬砖工地附近,坐标:成都 太古里。
作者:唐伯虎点香烟
==================================================================================
幂等性:对于同一笔业务操作,不管调用多少次,得到的结果都是一样的。
当然,有些操作是天然幂等的,如:
查询操作:查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select 是天然的幂等操作;
删除操作:删除操作也是幂等的,删除一次和多次删除都是把数据删除。
后台控制幂等性的几种途径:
设置唯一索引:防止新增脏数据
比如支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户 ID 加唯一索引,所以一个用户新增成功一个资金账户记录。要点:唯一索引或唯一组合索引来防止新增数据存在脏数据(当表存在唯一索引,并发时新增报错时,再查询一次就可以了,数据应该已经存在了,返回结果即可);
token机制:防止页面重复提交
原理上通过 session token 来实现的(也可以通过 redis 来实现)。当客户端请求页面时,服务器会生成一个随机数 Token,并且将Token放置到session当中
,然后将 Token 发给客户端(一般通过构造 hidden 表单)。下次客户端提交请求时,Token会随着表单一起提交到服务器端
。
服务器端第一次验证相同过后
,会将session中的Token值更新下
,若用户重复提交,第二次的验证判断将失败,因为用户提交的表单中的 Token 没变,但服务器端 session 中 Token 已经改变了。
悲观锁
获取数据的时候加锁获取。
select * from table_xxx where id='xxx' for update;
注意:id 字段一定是主键或者唯一索引,不然是锁表,会死人的;悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用;
乐观锁
乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高。乐观锁的实现方式多种多样,可以通过 version 或者其他状态条件判断:
通过版本号实现
update table_xxx set name=#name#,version=version+1 where version=#version#;
通过条件限制
update table_xxx set avai_amount=avai_amount where avai_amount >= 0
分布式锁
如果是分布式系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统(redis 或 zookeeper),在业务系统插入数据或者更新数据,获取分布式锁
,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路。
要点:某个长流程处理过程要求不能并发执行,可以在流程执行之前根据某个标志(用户 ID+后缀等)获取分布式锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放分布式锁(分布式锁要第三方系统提供);
北京 西二旗早高峰
====================================================================================
SQL 注入攻击的总体思路
寻找到 SQL 注入的位置
判断服务器类型和后台数据库类型
针对不通的服务器和数据库特点进行 SQL 注入攻击
预防方式:
1、PreparedStatement(简单有效)
采用预编译语句集,它内置了处理 SQL 注入的能力,只要使用它的 setXXX 方法传值即可。
sql 注入只对 sql 语句的准备(编译)过程有破坏作用,而PreparedStatement在执行阶段只是把输入串作为数据处理,不再对sql语句进行解析
,因此也就避免了 sql 注入问题。
2、使用正则表达式过滤传入的参数
要引入的包:
import java.util.regex.*;
正则表达式:
private String CHECKSQL = “^(.+)\sand\s(.+)|(.+)\sor(.+)\s$”;
判断是否匹配:
Pattern.matches(CHECKSQL,targerStr);
下面是常用来过滤参数是否存在SQL注入
的正则表达式:
检测 SQL meta-characters 的正则表达式 :
/(\%27)|(\')|(\-\-)|(\%23)|(#)/ix
修正检测 SQL meta-characters 的正则表达式 :
/((\%3D)|(=))[^\n]*((\%27)|(\')|(\-\-)|(\%3B)|(:))/i
典型的 SQL 注入攻击的正则表达式 :
/\w*((\%27)|(\'))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix
评论