一、背景和意义
在Web开发中,有时候为了提升安全性需要对数据进行加密。由于js代码相对比较易读,直接在js中做加密安全性较低,而WebAssembly代码不如js易读,使用WebAssemply做数据加密对安全性有一定的提升(不过熟悉WebAssembly的人还是能看懂加解密过程)。本文提供一个用go语言实现的WebAssembly数据加密示例。
二、创建WebAssembly文件
创建一个空目录,执行如下命令初始化go模块:
bash
go mod init wasm-demo
接下来在当前目录下创建main.go文件,提供数据加密与解密的方法:
go
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"fmt"
"syscall/js"
)
var aesCode = "0000000000000000" // 作为demo,这里只用16个0作为密钥
// pkcs5Padding 对数据进行填充,使其长度为块大小的倍数。
func pkcs5Padding(cipherText []byte, blockSize int) []byte {
padding := blockSize - len(cipherText)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(cipherText, padText...)
}
// pkcs5UnPadding 移除填充数据。
func pkcs5UnPadding(decrypted []byte) []byte {
length := len(decrypted)
unPadding := int(decrypted[length-1])
return decrypted[:(length - unPadding)]
}
// aesDecrypt 加密
func aesEncrypt(encryptStr string) (string, error) {
encryptBytes := []byte(encryptStr)
block, err := aes.NewCipher([]byte(aesCode))
if err != nil {
return "", err
}
blockSize := block.BlockSize()
encryptBytes = pkcs5Padding(encryptBytes, blockSize)
blockMode := cipher.NewCBCEncrypter(block, []byte(aesCode)[:blockSize])
encrypted := make([]byte, len(encryptBytes))
blockMode.CryptBlocks(encrypted, encryptBytes)
return hex.EncodeToString(encrypted), nil
}
// aesDecrypt 解密
func aesDecrypt(decryptStr string) (string, error) {
decryptBytes, err := hex.DecodeString(decryptStr)
if err != nil {
return "", err
}
block, err := aes.NewCipher([]byte(aesCode))
if err != nil {
return "", err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(aesCode)[:block.BlockSize()])
decrypted := make([]byte, len(decryptBytes))
blockMode.CryptBlocks(decrypted, decryptBytes)
decrypted = pkcs5UnPadding(decrypted)
return string(decrypted), nil
}
func jsFunc(handler func(string) (string, error)) js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
var result string
var err error
if len(args) > 0 {
result, err = handler(args[0].String())
}
return js.ValueOf(map[string]interface{}{
"result": result,
"err": err,
})
})
}
func main() {
fmt.Println("Go wasm loaded!")
js.Global().Set("aesEncrypt", jsFunc(aesEncrypt))
js.Global().Set("aesDecrypt", jsFunc(aesDecrypt))
<-make(chan bool)
}
如果是在goland中编辑此代码,需要在Settings中设置一下Build Tags,将OS设置为js,Arch设置为wasm,否则代码编辑器里的syscall/js
会标红:
接下来执行如下命令生成wasm文件main.wasm:
bash
GOOS=js GOARCH=wasm go build -o main.wasm
三、在前端页面中使用WebAssemply的加解密方法
执行如下命令将go目录下的wasm_exec.js复制过来到当前目录:
bash
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
接下来创建前端代码index.html文件:
html
<!DOCTYPE html>
<html lang="utf8">
<head>
<title>wasm demo</title>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then(result => {
go.run(result.instance);
const encrypt = aesEncrypt("HelloWorld");
console.log("encrypt result: ", encrypt);
console.log("decrypt result: ", aesDecrypt(encrypt.result));
});
</script>
</head>
<body></body>
</html>
将当前目录添加到nginx中,然后通过nginx提供的端口访问index.html,可以看到控制台输出加解密的结果如下: