WebAssembly前后端加密实践
WebAssembly字面意思是web的汇编语言, 是⼀种新兴的⽹⻚虚拟机标准。目前已经作为W3C规范在各大主流浏览器得到支持。Go 语言内置了syscall/js包,可以在 Go 语言中直接调用 JavaScript 函数,包括对 DOM 树的操作,能用更高的性能提供更好的用户体验。
前言
本文通过探讨一种前端加解密方式, 前端使用WebAssembly(wasm)提供公钥加密,后端使用私钥解密,能大大提高破解难度。 wasm使用支持多种语言,我们这里使用golang开发,后端解密使用openresty + lua解密,这样后端应用就不需要做任何修改了。
WebAssembly简介
WebAssembly字面意思是web的汇编语言, 是⼀种新兴的⽹⻚虚拟机标准。目前已经作为W3C规范在各大主流浏览器得到支持。
Go 语言内置了 syscall/js 包,可以在 Go 语言中直接调用 JavaScript 函数,包括对 DOM 树的操作,能用更高的性能提供更好的用户体验。
非对称加密
生成公钥和私钥, 我们这里基于openssl来生成,没有的需要安装下。
我们来生成2048位密钥对, 这个密钥对最多加密245字节文本(2048/8-11=245),
openssl genrsa -out rsa_private_key.pem 2048
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
go mod init go-wasm-rsa
GOOS=js GOARCH=wasm vim main.go
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"syscall/js"
)
func init() {
document := js.Global().Get("document")
// 仅支持web
if document.IsUndefined() {
panic("forbid")
}
// 我们可以在这里做一些有意思的事,例如做域名检查等
allowedDomain := "www.mydoman.com"
host := document.Get("location").Get("host").String()
if host != allowedDomain {
panic("forbid")
}
}
func main() {
c := make(chan struct{}, 0)
js.Global().Set("enc", js.FuncOf(enc))
<-c
}
//现实中公钥需要做一个切割,用来隐藏。这里没做处理
const pubKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1MDDhgSkSYKWBXlghk/P
NlAXxxL66zIOTdLnnyDlDcNGFMXVyjXdRPg86rr/MMhxBuzc/amGD4zEzF0xgLxA
2OrqCfHaVmr9M3Lailu1lE9piafUqfoPc65wzo+DdJmM/7xFX1FpaSqaYGsW8pyI
GDF1VGrnckVu7T/t+XEP7KBotEPAj6QCYy1DJRCq0KaThLnpgYv7Qshck/4qgVi7
Itr1KgqrRujSgdCWJy3IV5dU+ryiDr+tzPb47F7lb9cZo7uari9RfJvNwYFIYO75
AmvB90EUe43r2VysWVTPe2diOHNZXMIjGYi48M0HW31rWru7pFy5KBh7UmcZHZ5a
CQIDAQAB
-----END PUBLIC KEY-----`
func enc(this js.Value, args []js.Value) interface{} {
authorization := []byte(args[0].String())
block, _ := pem.Decode([]byte(pubKey))
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic("forbid")
}
//类型断言
publicKey := publicKeyInterface.(*rsa.PublicKey)
//对明文进行加密
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, authorization)
if err != nil {
panic("forbid")
}
return base64.StdEncoding.EncodeToString(cipherText)
}
Makefile
LDFLAGS='-w -s'
build:
CGO_ENABLED=0 GOOS=js GOARCH=wasm go build -trimpath -ldflags $(LDFLAGS) -o enc.wasm
拷贝胶水代码: wasm和js的桥梁, JavaScript 支持文件,加载 wasm 文件时需要,一定要保证编译的wasm和js文件是用的同一个版本golang。
make build
cp $(go env GOROOT)/misc/wasm/wasm_exec.js .
构建后wasm文件差不多400k, nginx需要配置gzip压缩哦,差不多100k了,很满意哦
├── Makefile
├── go.mod
├── go.sum
├── index.html
├── main.go
└── wasm_exec.js
做一个index.html 在浏览器里测试下效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go + WebAssembly</title>
<script src="wasm_exec.js"></script>
</head>
<body>
<h2>待加密字符串 </h2>
<input id="preprocess" type="text" size="200"
value="L1co6ahggacqa$ozhbkhOXI1N5sC/l5KqSZIJu6IBoTyK0R30HWtcVPqlCy8Nm6VZK2kdwMc0eO/yIC6T2lNEX2MHbkI5MMqN6luKeIhNpBvg9EfdsqPkQZjeezJIc/7W/oakpIvtTfA1EsNBiz2Iq5t91o=" />
<br>
<br>
<input type="button" id="btn-input" value="加密Encrypt" size="100">
<br>
<br>
<h2>加密结果</h2>
<textarea cols="80" rows="10" id="result"> </textarea>
<script>
(async function loadAndRunGoWasm() {
const go = new Go();
const response = await fetch("enc.wasm");
const buffer = await response.arrayBuffer();
const result = await WebAssembly.instantiate(buffer, go.importObject);
go.run(result.instance)
})()
const btnInput = document.getElementById('btn-input')
btnInput.addEventListener('click', async () => {
const bearer = document.getElementById("preprocess").value
const s = enc(bearer)
document.getElementById("result").value = s
})
</script>
</body>
</html>
go get -u github.com/shurcooL/goexec
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'
OpenResty
编写Lua解密代码(这里之所以用lua是历史遗留问题,不让后端过多介入,对后端完全透明)
下载:lua-resty-rsa/lib/resty at master · spacewander/lua-resty-rsa · GitHub
编写dec.lua, 配置在access_by_lua_file 里
local require = require
local resty_rsa = require "resty.rsa"
local base64_decode=ngx.decode_base64
local authorization=ngx.req.get_headers()['authorization']
if authorization==nil then
return
end
local rsa_priv_key="LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBc1Z2ZzFoTlFsdncwNTBBQWRwVUJycWJ6TkJGaHl0ZXVoWm9QTVpsZlh6a0Npbm1QCjUwb2tuVzlYU052czRYdGpxR0NsVXhJckozQmFhREZoTzJTN2tuK1N6Wm1iNXVRamxQSGhjNEJwbFRmMGVsUE8KTGRwdUxKcnZ4enVSRlJualU3ZWVvZmVjNDJHVlBad0QydDI5bU1KbkpENzZRMlBqMmxnSldBMHZHM2tkYTdwUwpXbnFDdVVYR2RadVF1VHRneG5ERXVvL1JicDV1MjRwSE82RUg1NUJQRElHY0ZJQUJkZlYyNjBWZlo4aDg2WDhQCjhLbkRlSTZVYStxMmFPaUdhaHI5b25kTHVhL1NuS1BUSVNDZjZCVzdueTRsL1Y0c1JIMm1xWXYySWNkMVF0c1YKNlJqaEh5Tm4yVzkyTlgzT0lsRHo1Tnl0VXU1THlibkhYVHhZa1FJREFRQUJBb0lCQUg3SmthYzMwNHE3N1EzTApnUWxFYUJsMG03T0RJWWVpTzg2aVhXNDFtQ280VlFxczhDU0ZxanNwbHhvc3JlQmJGdGtOamVJZXdON0d3THB2ClluVFZCQW9zVE1QUnBkT2ZENWl3ZVZ6YVZhQW9pZ3JRMGptUlJ1VjROU1VWL2hjNWxIc0tic3FXZW45S0NTZ3IKMmMyaWFxRkRoL3d2VVRUUHVka2l5anM1NFkwZkRUUGYwSEQ3MEQ0ZTB6L2NOb2dKa3RqZnZwb1dMWWlId0lnbAplcjRtUFUzQ0hVY0RtcEJHNWErN3VyVnJHOEVYMmJ1U1R6SnVabUF6d3NhSFVDUHorYU1TSHp2bTlPR3gyQlpwCjJVRDd4WGlwRmpQUHMrazQ3TlNUaHRPemxzUUNLcnBkZ1Awb3V0UC9oTDgvaktVZ0pLYWZTay9pamc3cVhOekwKTW8yakdra0NnWUVBMVQ3djhFeEFCWjNmWGZ1dUQ1Y1orMFJNTlpoMFBFVkR1NGI3V2RNV0ZNaWZIWHUxTW9zZwoybS90NEdEQWl0eUZ3Yk9xRHB2eUttMDNsTGJaZ2xLL3ZHVGVleTBFRkxmUXJPWmladk5sKzl0dEh4TStEbzdZCmhGdmdVVDNIZVBJV1ZZRytYTmdFbkNQVTVGZ1U3K3AwYWR5cjg3c1p5VkxzR0h1WWp2TjV1SjhDZ1lFQTFPc0IKb3NKRFd4NnBiTkp6bFlDdTU1N2UzdmVCRStKdXFUdWhYcXhlM2Z3MElzdUl5ZjMrRzR1UmNBdHVzWkphL08vNgpGTldRKzZqbExGaysyUjhzdWNPWjVGV1NFcG5XNENsZG9YNkVrR3RsQy8yU3A0ZWhscjRpTWRUQlhHUmZnWW9qCi9tL3JEdVIvUFdKRmQwamJqRUZXeW8xc3AzaFJidWNqcVRabDhNOENnWUFGN29WQUd1N2crUjVBZ0FLOGZraUQKdThlZTZnbTVyM2VOM05oYkRFc2Q4dUt5TUVHL0VTMnR4ZFZKRzRmZmxQakhoWmJpWnlZYVZnVm94cGxRVGJyMQpvNXlvc256ZGtxdGtVOWhDNHR4Z1lCOHQ4UndWelpWcVFTQUJRb1dzOEpiOGMrcDJySytjSkVjRXZ3cCtEZmlGCkJWVm5Kem8xWm5BWTBqOVJJcWF0SXdLQmdRQ05BVjNCOWprNVBTTWpDSFMzaTlOSlhYTm40aTIvaDNPVjdBSEEKZXhNUW5CZkMrMXdKdVlYeHBBcWJVMWJwam0xbm1WM2JNbHlqN1lSb1RHcE16RktJYTd1YzlmYVpEdnk0MDJ4SQpxVXNOZ2JJWHNNVFE0Z2ZubHQ5NmROWGhaQy9EMEVKcUhLQms2bnBCb3JVeWZETzV2UVBIZk1WNld6cEM0aHhCCjBkN05EUUtCZ1FDN3kzM0RCTkRteHZ5eEJUdksrWE5BaHVwSU5MU296STMrbUthaG1IS1QrdVplK3hZK21hdUcKb0hUMUZYTFdrTWd5eGF4SEE0SkQzTk95cDJBYTl0YVMvQjM2dVN4RjJoRFJNUmpNZnkvQVVRSG4yNUVncjdBZgpISW9iTnQyc3VVY2QrVjNtSHRIZ1ZwWlR5T1gzQzF6ZlgweTlIKzZ3TUVIbitTZ1lQOC9wTFE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ=="
local encrypted=base64_decode(authorization)
if not encrypted then
ngx.log(ngx.ERR, "careful dec not work")
ngx.status = ngx.HTTP_FORBIDDEN
ngx.exit(ngx.HTTP_OK)
end
local priv, err = resty_rsa:new({ private_key = base64_decode(rsa_priv_key) })
if not priv then
ngx.status = ngx.HTTP_UNAUTHORIZED
ngx.exit(ngx.HTTP_OK)
return
end
local decrypted = priv:decrypt(encrypted)
ngx.req.set_header("authorization", decrypted)
Nginx 压缩wasm
mime.types
application/wasm wasm;
nginx.conf
gzip_types ...省略其他.. application/wasm;
更多推荐
所有评论(0)