AES对称加密相比大家都很熟悉, 今天我主要是想实现在服务器数据加密, 然后在客户端进行解密操作,研究了 go 和 rust 的 wasm 编译, 奈何 rust 语法太难了, 之前学过一段时间 go 语言, 就用 go 简单做了个加解密操作, 然后编译为 wasm 给 js 调用。
实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
package main // 声明 main 包,表明当前是一个可执行程序 import ( "bytes" "crypto/aes" "crypto/cipher" "encoding/base64" "fmt" "syscall/js" ) // 导入内置 fmt // 全局的key const useKey string = "9876787656785679" func main() { // main函数,是程序执行的入口 message := "Hello World 🌍" fmt.Println(message) // 在终端打印 Hello World! js.Global().Set("go__encrypt", js.FuncOf(Encrypt)) js.Global().Set("go__decode", js.FuncOf(Decode)) <-make(chan bool) } // 加密数据后返回 func Encrypt(this js.Value, args []js.Value) interface{} { input := args[0].String() // get the parameters origData := []byte(input) // 待加密的数据 key := []byte(useKey) // 加密的密钥 //log.Println("原文:", string(origData)) //log.Println("------------------ CBC模式 --------------------") encrypted := AesEncryptCBC(origData, key) //log.Println("密文(hex):", hex.EncodeToString(encrypted)) var baseData = base64.StdEncoding.EncodeToString(encrypted) //log.Println("密文(base64):", baseData) return baseData } // 解密数据 func Decode(this js.Value, args []js.Value) interface{} { input := args[0].String() key := []byte(useKey) // 加密的密钥 b1, _ := base64.StdEncoding.DecodeString(input) decrypted := AesDecryptCBC(b1, key) return string(decrypted) } // 加密 func AesEncryptCBC(origData []byte, key []byte) (encrypted []byte) { // 分组秘钥 // NewCipher该函数限制了输入k的长度必须为16, 24或者32 block, _ := aes.NewCipher(key) blockSize := block.BlockSize() // 获取秘钥块的长度 origData = pkcs5Padding(origData, blockSize) // 补全码 blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) // 加密模式 encrypted = make([]byte, len(origData)) // 创建数组 blockMode.CryptBlocks(encrypted, origData) // 加密 return encrypted } // 解密 func AesDecryptCBC(encrypted []byte, key []byte) (decrypted []byte) { block, _ := aes.NewCipher(key) // 分组秘钥 blockSize := block.BlockSize() // 获取秘钥块的长度 blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // 加密模式 decrypted = make([]byte, len(encrypted)) // 创建数组 blockMode.CryptBlocks(decrypted, encrypted) // 解密 decrypted = pkcs5UnPadding(decrypted) // 去除补全码 return decrypted } func pkcs5Padding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } func pkcs5UnPadding(origData []byte) []byte { length := len(origData) unpadding := int(origData[length-1]) return origData[:(length - unpadding)] } |
打包命令: GOOS=js GOARCH=wasm go build -o static/main.wasm
注意如果是第一次使用, 需要 拷贝引用的 js 文件
cp “$(go env GOROOT)/misc/wasm/wasm_exec.js” static
浏览器端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="wasm_exec.js"></script> <script> const go = new Go(); WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject) .then((result) => { const wasm = result.instance; go.run(wasm) }); </script> </body> </html> |
go 已经将两个方法写入到了浏览器的全局方法里
1 2 |
js.Global().Set("go__encrypt", js.FuncOf(Encrypt)) js.Global().Set("go__decode", js.FuncOf(Decode)) |
所以我们在浏览器里直接调用 go__enrypt(‘xxx’) 就可以实现加密。
存在的问题
这个打包出来的 wasm 文件有些大, 目前是 2M 左右, 在浏览器里直接使用还是不太现实的, 太大了。
后来查了资料, 说有一个 go 的子集叫: tinygo
打包出来的 wasm 文件体积小, 安装 tinygo 需要 xcode 13, 我电脑没有升级, 所以没有尝试。