前端加密的意义
有意义:
没有意义:
这里简单说下:
加密了也无法解决重放的问题,你发给服务器端的虽然是加密后的数据,但是黑客拦截之后,把加密之后的数据重发一遍,依然是验证通过的。直接监听到你所谓的密文,然后用脚本发起一个http请求就可以登录上去了。 http在网络上是明文传输的,代理和网关都能够看到所有的数据,在同一局域网内也可以被嗅探到。加密也没有提高什么攻击难度,因为攻击者就没必要去解密原始密码,能登录上去就表示目标已经实现了,所以,难度没有提高。
既然是加密,那么加密用的密钥和算法肯定是保存在前端的,攻击者通过查看源码就能得到算法和密钥。除非你是通过做浏览器插件,将算法和密钥封装在插件中,然后加密的时候明文混淆上时间戳,这样即使黑客拦截到了请求数据,进行重放过程时,也会很快失效。
总结一下:
- 1. 安全是前后端都需要做的事,不能前端加密了,后端就不管了.
- 2. HTTPS还是有必要的,只要正确使用了HTTPS连接和服务器端安全的哈希算法,密码系统都可以是很安全的。
如果还有疑惑想深入探讨的,可以看下某乎上的这篇文章
前端加密的几种做法
• JavaScript 加密后传输(具体可以参考后面的常见加密方法)
• 浏览器插件内进行加密传输 (这个用得不是很多,这里暂不细究)
• Https 传输
加密算法
(一)对称加密(Symmetric Cryptography)
加密过程:
private string myData = "hello";
private string myPassword = "OpenSesame";
private byte[] cipherText;
private byte[] salt = {
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0
};
private void mnuSymmetricEncryption_Click(object sender, RoutedEventArgs e){
var key = new Rfc2898DeriveBytes(myPassword, salt);
// Encrypt the data.
var algorithm = new RijndaelManaged();
algorithm.Key = key.GetBytes(16);
algorithm.IV = key.GetBytes(16);
var sourceBytes = new System.Text.UnicodeEncoding().GetBytes(myData);
using (var sourceStream = new MemoryStream(sourceBytes))
using (var destinationStream = new MemoryStream())
using (var crypto = new CryptoStream(sourceStream, algorithm.CreateEncryptor(), CryptoStreamMode.Read)){
moveBytes(crypto, destinationStream);
cipherText = destinationStream.ToArray();
}
MessageBox.Show(String.Format(
"Data:{0}{1}Encrypted and Encoded:{2}",myData,Environment.NewLine,Convert.ToBase64String(cipherText)
));
}
private void moveBytes(Stream source, Stream dest){
byte[] bytes = new byte[2048];
var count = source.Read(bytes, 0, bytes.Length);
while (0 != count){
dest.Write(bytes, 0, count);
count = source.Read(bytes, 0, bytes.Length);
}
}
解密过程:
private void mnuSymmetricDecryption_Click(object sender, RoutedEventArgs e){
if (cipherText == null){
MessageBox.Show("Encrypt Data First!");
return;
}
var key = new Rfc2898DeriveBytes(myPassword, salt);
// Try to decrypt, thus showing it can be round-tripped.
var algorithm = new RijndaelManaged();
algorithm.Key = key.GetBytes(16);
algorithm.IV = key.GetBytes(16);
using (var sourceStream = new MemoryStream(cipherText))
using (var destinationStream = new MemoryStream())
using (var crypto = new CryptoStream(sourceStream, algorithm.CreateDecryptor(),CryptoStreamMode.Read)){
moveBytes(crypto, destinationStream);
var decryptedBytes = destinationStream.ToArray();
var decryptedMessage = new UnicodeEncoding().GetString(decryptedBytes);
MessageBox.Show(decryptedMessage);
}
}
常见的对称加密算法有DES、3DES、Blowfish、IDEA、RC4、RC5、RC6和AES
注意: 因为前端的透明性,对于登录密码等敏感信息,就不要使用JavaScript来进行对称加密. 因为别人可以从前端得到密匙后,可以直接对信息进行解密!
- 对称加密的一大缺点是密钥的管理与分配,换句话说,如何把密钥发送到需要解密你的消息的人的手里是一个问题。在发送密钥的过程中,密钥有很大的风险会被黑客们拦截。现实中通常的做法是将对称加密的密钥进行非对称加密,然后传送给需要它的人。。
(二)非对称加密(Asymmetric Cryptography)
加密过程:
private byte[] rsaCipherText; private void mnuAsymmetricEncryption_Click(object sender, RoutedEventArgs e){
var rsa = 1;
// Encrypt the data.
var cspParms = new CspParameters(rsa);
cspParms.Flags = CspProviderFlags.UseMachineKeyStore;
cspParms.KeyContainerName = "My Keys";
var algorithm = new RSACryptoServiceProvider(cspParms);
var sourceBytes = new UnicodeEncoding().GetBytes(myData);
rsaCipherText = algorithm.Encrypt(sourceBytes, true);
MessageBox.Show(String.Format(
"Data: {0}{1}Encrypted and Encoded: {2}",myData,Environment.NewLine,Convert.ToBase64String(rsaCipherText)
));
}
解密过程:
private void mnuAsymmetricDecryption_Click(object sender, RoutedEventArgs e){
if(rsaCipherText==null){
MessageBox.Show("Encrypt First!");
return;
}
var rsa = 1;
// decrypt the data.
var cspParms = new CspParameters(rsa);
cspParms.Flags = CspProviderFlags.UseMachineKeyStore;
cspParms.KeyContainerName = "My Keys";
var algorithm = new RSACryptoServiceProvider(cspParms);
var unencrypted = algorithm.Decrypt(rsaCipherText, true);
MessageBox.Show(new UnicodeEncoding().GetString(unencrypted));
}
常见的非对称加密算法有:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
虽然非对称加密很安全,但是和对称加密比起来,它非常的慢,所以我们还是要用对称加密来传送消息,但对称加密所使用的密钥我们可以通过非对称加密的方式发送出去。为了解释这个过程,请看下面的例子:
- Alice需要在银行的网站做一笔交易,她的浏览器首先生成了一个随机数作为对称密钥。
- Alice的浏览器向银行的网站请求公钥。
- 银行将公钥发送给Alice。
- Alice的浏览器使用银行的公钥将自己的对称密钥加密。
- Alice的浏览器将加密后的对称密钥发送给银行。
- 银行使用私钥解密得到Alice浏览器的对称密钥。
- Alice与银行可以使用对称密钥来对沟通的内容进行加密与解密了。
登陆时:
- 系统根据用户名找到与之对应的密码Hash。
- 将用户输入密码和salt值进行散列。
- 判断生成的Hash值是否和数据库中Hash相同。
PS: 其实图中的这种登录也是不安全的. 原因是后面要提到的盐值复用
使用加盐加密时需要注意以下两点:
- 短盐值(Short Slat)
- 盐值复用(Salt Reuse)
所以正确的加盐方法如下:
(1)盐值应该使用加密的安全伪随机数生成器( Cryptographically Secure Pseudo-Random Number Generator,CSPRNG )产生,比如 C 语言的 rand() 函数,这样生成的随机数高度随机、完全不可预测;
(2)盐值混入目标文本中,一起使用标准的加密函数进行加密;
(3)盐值要足够长(经验表明:盐值至少要跟哈希函数的输出一样长)且永不重复;
(4)盐值最好由服务端提供,前端取值使用。
慢哈希函数(Slow Hash Function)
密钥哈希
XOR
1 ^ 1 // 0
0 ^ 0 // 0
1 ^ 0 // 1
0 ^ 1 // 1
XOR 运算有一个特性:如果对一个值连续做两次 XOR,会返回这个值本身。这也是其可以用于信息加密的根本。
message XOR key // cipherText
cipherText XOR key // message
目标文本 message,key 是密钥,第一次执行 XOR 会得到加密文本;在加密文本上再用 key 做一次 XOR 就会还原目标文本 message。为了保证 XOR 的安全,需要满足以下两点:
(1)key 的长度大于等于 message ;
(2)key 必须是一次性的,且每次都要随机产生。
下面以登录密码加密为例介绍下 XOR 的使用:
- 第一步:使用 MD5 算法,计算密码的哈希;
const message = md5(password);
第二步:生成一个随机 key 值;
第三步:进行 XOR 运算,求出加密后的 message。
function getXOR(message, key) {
const arr = [];
//假设 key 是32位的
for (let i = 0; i < 32; i++) {
const m = parseInt(message.substr(i, 1), 16);
const k = parseInt(key.substr(i, 1), 16);
arr.push((m ^ k).toString(16));
}
return arr.join('');
}
如上所示,使用 XOR 和一次性的密钥 key 对密码进行加密处理,只要 key 没有泄露,目标文本就不会被破解。
上面说了那么多,问题就来了:我们应该使用什么样的哈希算法呢?
(1)选择经过验证的成熟算法,如 PBKDF2 等 ;
(2)crypt 的安全版本;
(3)避免使用自己设计的加密算法。
HMAC
关于HMAC算法部分可以详细看这篇文章,我是学渣,看了半天也不是太懂.=.=
大概过程如下:
- 1.客户端发出登录请求
- 2.服务器返回一个随机值,在会话记录中保存这个随机值
- 3.客户端将该随机值作为密钥,用户密码进行 hmac 运算,递交给服务器
- 4.服务器读取数据库中的用户密码,利用密钥做和客户端一样的 hmac运算,然后与用户发送的结果比较,如果一致,则用户身份合法。
好处:
- 与自定义的加salt算法不同,Hmac算法针对所有哈希算法都通用,无论是MD5还是SHA-1。采用Hmac替代我们自己的salt算法,可以使程序算法更标准化,也更安全。(摘自雪峰大佬的这篇文章)
- 另外一个就是密码的安全性,由于不知道密钥,所以不可能获取到用户密码
补充1: 结合验证码进行前端加密 (其实就是一种动态加盐哈希)
前端先将密码哈希,然后和用户输入的验证码进行哈希,得到的结果作为密码字段发送给服务器。服务器先确认验证码正确,然后再进行密码验证,否则直接返回验证码错误信息。
这种实践保证了密码在传输过程中的资料安全,即使攻击者拿到了数据也无法重放。图形化验证码更是增加了难度。另一方面该实践大大增加了撞库的成本。
前端加密一定程度保障了传输过程中的资料安全,那么会不会有对两端(客户端和服务器)有安全帮助呢?
有帮助,使用一些前端加密手段,可以增加拖库后的数据破解难度。但是验证码方法不具有这样的功能,因为数据库存的仍是明文密码哈希后的结果,那么攻击者可以绕过前端加密,可以直接暴力破解。
补充2: Base64 编码
大家经常说的是 Base64 加密,有 Base64 加密吗?真木有,只有 Base64 编码。
//1.编码
var result = Base.encode('shotCat好帅!'); //--> "c2hvdENhdOWlveW4hSE="
//2.解码
var result2 = Base.decode(result); //--> 'shotCat好帅!' 没错,我就是这么不要脸!!!
因此,Base64 适用于小段内容的编码,比如数字证书签名、Cookie的内容等;而且 Base64 也是一种通过查表的编码方法,不能用于加密,如果需要加密,请使用专业的加密算法。
PS: 对于前端来说,base64用得最多的也就是图片转码吧.
补充3: 数字签名
数字签名主要用于:确认信息来源于特定的主体且信息完整、未被篡改,发送方生成签名,接收方验证签名。
发送方: 首先计算目标文本的摘要(哈希值),通过私钥对摘要进行签名,将目标文本和电子签名发送给接收方。
接收方: 验证签名的步骤如下:
- 1,通过公钥破解电子签名,得到摘要 D1 (如果失败,则信息来源主体校验失败);
- 2,计算目标文本摘要 D2;
- 3,若 D1 === D2,则说明目标文本完整、未被篡改。
数字签名与非对称加密区别:
- 非对称加密(加密/解密):公钥加密,私钥解密。
- 数字签名(签名/验证):私钥签名,公钥验证。
HTTPS加密
为了避免重复,这部分内容在本系列HTTP与HTTPS有详细介绍