问题背景:
公司最近对各个系统做了安全漏洞扫描,发现诸多系统均存在不同级别,不同数量的漏洞,我则负责统一认证中心密码明文传输的漏洞。统一认证中心我们使用的是开源项目casdoor,该项目专注于单点登陆,功能强大。
过程及问题:
Go环境的问题:
casdoor服务端采用go语言开发,我在本地验证功能,需要先配置go环境。go环境配置网上教程较成熟,按照GOPATH规范放源码位置。go运行还需要下载一系列go的依赖包,国内网络环境下载十有八九是不成功的,这时我们需要将go的网络代理配置成环境变量,还需要配置一个开关
重试,依赖工具可成功下载。
前端版本问题:
casdoor的前端使用react+js语言开发,依赖node环境运行,一开始我按照网上教程下载按照了最新版本的node环境(本地casdoor测试代码也是最新版),运行casdoor前端正常。于是我把casdoor版本切换到公司测试环境对应的版本,然后发现前端总是启动失败,最终定位发现是node环境和js版本不匹配导致的,于是我打算降低本地node环境,网上搜教程发现了一个非常强大的node版本控制工具--nvm,强烈建议前端node环境使用nvm安装,具体安装使用教程自行百度。nvm可以自由切换不同的node版本,只需要简单的命令执行。
于是我成功安装了nvm工具,同时降低了我的node版本,此后,casdoor前端成功启动。
加密解密问题:
环境问题都ok,项目也可以成功启动,接下来就是处理密码明文传输的问题了,我们团队采用的思路是采用rsa非对称加密算法,前端在发送请求之前将密码使用公钥加密,然后传给后端,后端收到密文之后再通过秘钥解密,得到明文密码处理后续业务逻辑。
密钥对可以在线获取(不安全),也可以go代码在本地生成,秘钥长度越长,加密后的密文就越长,越安全,但是传输和处理代码也越大,可根据实际情况选择适当的秘钥长度。
// 生成密钥对并保存到文件
func GenerateRSAKey() error {
// 1、RSA生成私钥文件的核心步骤:
// 1) 生成RSA密钥对
var bits int
flag.IntVar(&bits, "key flag", 4096, "密钥长度,默认值为1024位")
privateKer, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return err
}
// 2) 将私钥对象转换成DER编码形式
derPrivateKer := x509.MarshalPKCS1PrivateKey(privateKer)
// 3) 创建私钥pem文件
file, err := os.Create("./private.pem")
if err != nil {
return err
}
// 4) 对密钥信息进行编码,写入到私钥文件中
block := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: derPrivateKer,
}
err = pem.Encode(file, block)
if err != nil {
return err
}
生成秘钥对后,在前端用公钥对密码进行加密,
import JSEncrypt from "jsencrypt";
encrypt(txt) {
const publicKey = "自己生成的公钥";
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey); // 设置公钥
return encryptor.encrypt(txt); // 对需要加密的数据进行加密
}
此处需要注意,公钥需和生成的格式保持一致,不能写到一行!
前端加密后,后端开始解密,一开始我在网上找了很多go语言rsa解密教程,大多数都已失败告终,建议大家使用go原生的rsa解密方法进行解密,不要使用个人封装的git库。后端也需要将秘钥按照生成的格式配置,不能放在一行。秘钥是解密密文的关键,不可以泄露。(如果有私钥加密,公钥解密的场景,公钥也不可以泄露)
在解密过程中有一个非常重要的坑,几乎网上所有的帖子都没有提到,那就是,前端加密后的密文是经过base64转换的,后端要解密之前,须先将密文进行base64反转,然后才能解密成功,后端解密核心代码如下:
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
)
func DecryptByPrivateKey(ciphertext string) string {
fmt.Println("开始解密")
if ciphertext == "" {
fmt.Println("解密密文为空!")
return ciphertext
}
//必须先将密文base反转换
decoded, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
fmt.Println("base64反解析前端传入的密文出错:" + err.Error())
return ""
}
block, _ := pem.Decode([]byte(Pirvatekey))
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
fmt.Println("将秘钥转换为*rsa.PrivateKey格式出错:" + err.Error())
return ""
}
decrypted, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, decoded)
if err != nil {
fmt.Println("解密密码失败" + err.Error())
return ""
}
fmt.Println("解密完成")
return string(decrypted)
}
经验证,前端传输的密码为密文传输,后端也可以正常解密,业务正常运行。
总结:
1、go环境须配置国内代理才能在国内网络环境正常下载go依赖;
2、casdoor前端须以来正确的node版本才能正常运行,建议使用nvm工具切换node版本;
3、前端配置公钥和后端配置的私钥都须按照秘钥生成的格式配置(换行),不可以放在一行;
4、前端使用JSEncrypt加密的密文,后端须先进行base64反转才可进行rsa解密。