正确使用AES对称加密
正确使用AES对称加密
经常我看到项目中有人使用了对称加密算法,用来加密客户或项目传输中的部分数据。但我注意到开发 人员由于不熟悉原理,或者简单复制网上的代码示例,有导致代码存在安全风险。
我经常遇到的问题,有如下:
- 如使用了过时的加密算法(如DES)
- 设置了不安全的加密模式(ECB)
- 不正确地处理初始向量(IV)
对称加密算法
算法 | 位长 | 建议 |
---|---|---|
RC4 | 40 | |
DES | 56 | |
3DES | 112 | |
AES | 128 | ✔ |
TL;DR:
RC4/DES/3DES都 不符合 加密/破解的安全性要求。
DES是56位加密,听起来感觉3DES应该是168位,但实际上其有效加密位长只有112位。
其它更长的加密算法,如AES 192位/AES 256位也符合要求。
加密模式
TL;DR: 不要使用ECB。
ECB不需要初始向量(IV),这个“惊人”的发现常常让开发简单粗暴地设计为ECB。ECB的问题在于输入和输出存在非常明显的关联,攻击者可以从输出轻松地猜出输入数据。
C#的AES算法默认模式为CBC,该算法没有上述的安全问题,而且最为通用,可以使用该模式。
初始向量
TL;DR:
初始向量 必须 为完全随机数,完全随机数应该使用RandomNumberGenerator
进行加密。
回想这个问题,数据加密完后,该发送什么给接收方?仅数据?那么初始向量(IV)怎么办?
大多数开发选择的办法是,写一个固定的初始向量(IV)用于加密,然后解密时,也使用相同的初始向量。这样就导致相同的输入会产生相同的输出。
为什么相同的输入应该产生不同的输出?因为根据历史经验,攻击者可以获取一些信息,知道某个确定输入的含义。一旦再次捕获到相同的加密数据,就能轻易破解。
所以,发送数据应该包含:版本+初始向量+数据。
面向字符串
加密是面向字节还是字符串?我认为应该面向字节。如果面向字符串,那么很多问题很难受到重视。
试着回答这个问题:
- 用户的密码是什么样子的?
- 是长度为固定32位的HEX字符吗?如
1C8F7B2C9759209C6ACC3C105D39BBAC
? - 还是用户想输入什么就输入什么?如
My-Super-Str0ng-Password!!
?
我认为加密算法应该面向字节流/字节数据,而不是字符串。将字符串发送给客户、放在JSON中进行端对端传输,是没什么毛病的做法。但基于以下原因,我强烈建议加密/解密算法要基于字节数据:
- 避免密码太长或太短的问题
- 来回转换为字符串效率低下
- 字符串转换为字节数组容易,其它数据序列化为字节数据也容易
我的加密/解密方法
// 代码按原样提供,可随意使用,但不对其安全性作任何保证。
string Encrypt(string password, string purpose, byte[] plainBytes)
{
byte[] key = PasswordToKey(password, purpose);
using (var aes = Aes.Create())
{
aes.Key = key;
using (ICryptoTransform encryptor = aes.CreateEncryptor())
{
byte[] cipherBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
byte[] packedBytes = Pack(
version: 1,
iv: aes.IV,
cipherBytes: cipherBytes);
return Base64UrlEncode(packedBytes);
}
}
}
byte[] Decrypt(string packedString, string password, string purpose)
{
byte[] key = PasswordToKey(password, purpose);
byte[] packedBytes = Base64UrlDecode(packedString);
(byte version, byte[] iv, byte[] cipherBytes) = Unpack(packedBytes);
using (var aes = Aes.Create())
{
using (ICryptoTransform decryptor = aes.CreateDecryptor(key, iv))
{
return decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
}
}
}
其中公共方法:
// 代码按原样提供,可随意使用,但不对其安全性作任何保证。
byte[] PasswordToKey(string password, string purpose)
{
using (var hmac = new HMACMD5(Encoding.UTF8.GetBytes(purpose)))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
}
}
string Base64UrlEncode(byte[] bytes)
{
return Convert.ToBase64String(bytes)
.Replace("/", "_")
.Replace("+", "-")
.Replace("=", "");
}
byte[] Base64UrlDecode(string base64Url)
{
return Convert.FromBase64String(base64Url
.Replace("_", "/")
.Replace("-", "+"));
}
(byte version, byte[] iv, byte[] cipherBytes) Unpack(byte[] packedBytes)
{
if (packedBytes[0] == 1)
{
// version 1
return (1, packedBytes[1..1 + 16], packedBytes[1 + 16..]);
}
else
{
throw new NotImplementedException("unknown version");
}
}
byte[] Pack(byte version, byte[] iv, byte[] cipherBytes)
{
return new[] { version }.Concat(iv).Concat(cipherBytes).ToArray();
}
解释:
- Base64UrlEncode/Decode:用于将字符串在Url上传输,将
+/=
转换成:-_
- Pack/Unpack:将版本/初始向量/密文打包/解包
- PasswordToKey:将长度不一样密码,加上
purpose
,转换为长度一样的key
,其中改成HMACSHA256可以使用256位的AES算法。
测试代码:
// 代码按原样提供,可随意使用,但不对其安全性作任何保证。
string purpose = "这个算法是用来搞SSO的";
// 返回:AcfCe3AQcmNkeNThv-u09H_HyGKy_iRy-7uGiW0IZOHI
Encrypt("密码here", purpose, Encoding.UTF8.GetBytes("Hello World"));
// 返回:Hello World
Encoding.UTF8.GetString(Decrypt("AcfCe3AQcmNkeNThv-u09H_HyGKy_iRy-7uGiW0IZOHI", "密码here", purpose));
正确使用AES对称加密的更多相关文章
- AES对称加密和解密
package demo.security; import java.io.IOException; import java.io.UnsupportedEncodingException; impo ...
- AES对称加密
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.Secre ...
- java的AES对称加密和解密,有偏移量
import java.math.BigDecimal; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; i ...
- AES对称加密和解密(转)
AES对称加密和解密 package demo.security; import java.io.IOException; import java.io.UnsupportedEncodingExce ...
- JAVA中AES对称加密和解密
AES对称加密和解密 package demo.security; import java.io.IOException; import java.io.UnsupportedEncodingExce ...
- AES对称加密解密类
import java.io.UnsupportedEncodingException; import javax.crypto.Cipher; import javax.crypto.spec.Se ...
- 使用Aes对称加密解密Web.Config数据库连接串
现在很多公司开始为了保证数据库的安全性,通常会对Web.Config的数据库连接字符串进行加密.本文将介绍学习使用Aes加密解密数据库连接字符串.本文采用MySql数据库. AES概念简述 AES 是 ...
- 17.app后端如何保证通讯安全--aes对称加密
在上文<16.app后端如何保证通讯安全--url签名>提到,url签名有两个缺点,这两个缺点,如果使用对称加密方法的话,则完全可以避免这两个缺点.在本文中,会介绍对称加密的具体原理,和详 ...
- openssl之aes对称加密
AES:密码学中的高级加密标准(Advanced Encryption Standard,AES),又称 Rijndael加密法. 对称加密:用同一个密码 加密/解密 文件. 使用openssl中 ...
随机推荐
- java导出excel模板数据
Java导出excel数据模板,这里直接贴代码开发,流程性的走下去就是步骤: String[] colName=new String[]{"期间","科目代码" ...
- js实现图片上传预览功能,使用base64编码来实现
实现图片上传的方法有很多,这里我们介绍比较简单的一种,使用base64对图片信息进行编码,然后直接将图片的base64信息存到数据库. 但是对于系统中需要上传的图片较多时并不建议采用这种方式,我们一般 ...
- IntelliJ IDEA——SVN的配置及使用
服务端:VisualSVN-Server-3.9.1-x64 下载地址:https://www.visualsvn.com/server/download/ TortoiseSVN 安装 下载地址:h ...
- jQuery的appendTo案例
案例要求:点击双击第一个下拉列表框的选项可以把对应选项移到第二个下拉列表框中,选中第一个列表框的选项(可多选)单击-->按钮可使被选中项移动到右边下拉列表框中,单击==>按钮时将左边的所有 ...
- http://ctf.bugku.com/challenges#love:bugku--love
做了一道逆向题目,主要关联到base64编码的知识点.下面做一分析. 题目如下: 通过测试,可知它没有加壳.尝试使用IDA进行分析. 1.IDA分析文件 打开文件后,按[shift+F12 ...
- thinkphp5中使用excel导出数据表格(包涵图片)
首先使用composer require phpoffice/phpexcel下载安装phpexcel包. 将包放入extend下面. 不附加图片的导出 /** * 导出excel(不带图片) * @ ...
- 获奖感想和Java学习总结
获奖感想和Java学习总结 一.获奖感想 能成为小黄衫第二批的成员之一,我感到非常荣幸.我在对老师给予我的鼓励与肯定感到欣喜之余,更多的是感受到了一种鞭策与期望.小黄衫不仅仅是对我的一种奖励,更是激励 ...
- CSS学习总结4:派生选择器学习总结
派生选择器:通过依据元素在其位置的上下文关系来定义样式,你可以使标记更加简洁.派生选择器中一共分为三种:后代选择器.子元素选择器.相邻兄弟选择器. 1.初识派生选择器 实例:你希望列表中的 stron ...
- 如何学好游戏3D引擎编程
注:本文是网上看到的一篇文章,感觉写的很好,因此收藏了下来 <如何学好游戏3D引擎编程>此篇文章献给那些为了游戏编程不怕困难的热血青年,它的神秘要我永远不间断的去挑战自我,超越自我,这样才 ...
- typescript里面调用javasript
index.html 里面加入函数: function tellYou() { egret.log("tell you."); javascript:android.funA(); ...