目录
一、RSA简介
RSA公开密钥密码体制是一种使用不同的加密密钥与解密密钥,“由已知加密密钥推导出解密密钥在计算上是不可行的”密码体制 。
在公开密钥密码体制中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。加密算法E和解密算法D也都是公开的。虽然解密密钥SK是由公开密钥PK决定的,但却不能根据PK计算出SK 。
正是基于这种理论,1978年出现了著名的RSA算法,它通常是先生成一对RSA密钥,其中之一是保密密钥,由用户保存;另一个为公开密钥,可对外公开,甚至可在网络服务器中注册。为提高保密强度,RSA密钥至少为500位长。这就使加密的计算量很大。为减少计算量,在传送信息时,常采用传统加密方法与公开密钥加密方法相结合的方式,即信息采用改进的DES或IDEA对话密钥加密,然后使用RSA密钥加密对话密钥和信息摘要。对方收到信息后,用不同的密钥解密并可核对信息摘要 。——摘自百度百科
本文将基于RSA算法实现可视化窗体的加密、解密、签名、校验工具。
二、工程目录
├─cmd 主入口
├─files RSA 密钥、加解密后的文件
├─gui 可视化窗体
├─sys 系统配置
├─test 测试
└─utils 工具
三、RSA Utils
1、加密与解密
需要用到的库
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"log"
"os"
)
首先生成密钥对
//生成RSA私钥和公钥,保存到文件中
func GenerateRSAKey(bits int) {
//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
//Reader是一个全局、共享的密码用强随机数生成器
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
log.Fatalln(err)
}
//保存私钥
//通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
//使用pem格式对x509输出的内容进行编码
//创建文件保存私钥
privateFile, err := os.Create("./files/private.pem")
if err != nil {
log.Fatalln(err)
}
defer privateFile.Close()
//构建一个pem.Block结构体对象
privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
//将数据保存到文件
pem.Encode(privateFile, &privateBlock)
//保存公钥
//获取公钥的数据
publicKey := privateKey.PublicKey
//X509对公钥编码
X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
log.Fatalln(err)
}
//pem格式编码
//创建用于保存公钥的文件
publicFile, err := os.Create("./files/public.pem")
if err != nil {
log.Fatalln(err)
}
defer publicFile.Close()
//创建一个pem.Block结构体对象
publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
//保存到文件
pem.Encode(publicFile, &publicBlock)
}
RSAEncrypt 函数使用公钥对信息进行加密,RSADecrypt 函数使用密钥对信息进行解密,加密和解密都进行一次 BASE64 处理。
//RSA加密
func RSAEncrypt(plainText []byte, saveFilePath, path string) []byte {
//打开文件
file, err := os.Open(path)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
//读取文件的内容
info, _ := file.Stat()
buf := make([]byte, info.Size())
file.Read(buf)
//pem解码
block, _ := pem.Decode(buf)
//x509解码
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
log.Fatalln(err)
}
//类型断言
publicKey := publicKeyInterface.(*rsa.PublicKey)
//对明文进行加密
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
if err != nil {
log.Fatalln(err)
}
//保存密文
encode := Base64Encode(cipherText)
log.Println("base64 ", encode)
saveCipherText(encode, saveFilePath)
//返回密文
return encode
}
保存密文
// 保存密文
func saveCipherText(cipherText []byte, path string) {
cipherFile, err := os.Create(path)
if err != nil {
log.Fatalln(err)
return
}
defer cipherFile.Close()
_, err = cipherFile.Write(cipherText)
if err != nil {
log.Fatalln(err)
return
}
log.Println("CipherText has been save as ", path)
}
解密
//RSA解密
func RSADecrypt(cipherText []byte, path string) []byte {
//打开文件
file, err := os.Open(path)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
//获取文件内容
info, _ := file.Stat()
buf := make([]byte, info.Size())
file.Read(buf)
//pem解码
block, _ := pem.Decode(buf)
//X509解码
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
log.Fatalln(err)
}
//对密文进行解密
decode, err := Base64Decode(cipherText)
if err != nil {
log.Fatalln(err)
}
plainText, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, decode)
//返回明文
return plainText
}
从文件中读取密文并解密
// 从文件中读取密文并解密
func RSADecryptFromFile(cipherFilePath string, path string) []byte {
f, err := os.Open(cipherFilePath)
if err != nil {
log.Fatalln(err)
return nil
}
defer f.Close()
all, err := ioutil.ReadAll(f)
if err != nil {
log.Fatalln(err)
return nil
}
return RSADecrypt(all, path)
}
2、签名与验证签名
需要用到的库
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"io/ioutil"
)
解析密钥对
//读取公钥文件,解析出公钥对象
func ReadParsePublicKey(filename string) (*rsa.PublicKey, error) {
//--1.读取公钥文件,获取公钥字节
publicKeyBytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
//--2.解码公钥字节,生成加密对象
block, _ := pem.Decode(publicKeyBytes)
if block == nil {
return nil, errors.New("公钥信息错误")
}
//--3.解析DER编码的公钥,生成公钥接口
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
//--4.公钥接口转型成公钥对象
publicKey := publicKeyInterface.(*rsa.PublicKey)
return publicKey, nil
}
//读取私钥文件,解析出私钥对象
func ReadParsePrivateKey(filename string) (*rsa.PrivateKey, error) {
//--1.读取私钥文件,获取私钥字节
privateKeyBytes, _ := ioutil.ReadFile(filename)
//--2.对私钥文件进行编码,生成加密对象
block, _ := pem.Decode(privateKeyBytes)
if block == nil {
return nil, errors.New("私钥信息错误")
}
//3.解析DER编码的私钥,生成私钥对象
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return privateKey, err
}
使用 RSASign 函数进行私钥签名
func RSASign(data []byte, filename string) (string, error) {
//1.选择hash算法,对需要签名的数据进行hash运算
myhash := crypto.SHA256
hashInstance := myhash.New()
hashInstance.Write(data)
hashed := hashInstance.Sum(nil)
//2.读取私钥文件,解析出私钥对象
privateKey, err := ReadParsePrivateKey(filename)
if err != nil {
return "", err
}
//3.RSA数字签名
bytes, err := rsa.SignPKCS1v15(rand.Reader, privateKey, myhash, hashed)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(bytes), nil
}
RSAVerify 函数进行公钥验证签名
//公钥验证数据签名是否正确
func RSAVerify(data []byte, base64Sig, filename string) error {
//- 对base64编码的签名内容进行解码,返回签名字节
bytes, err := base64.StdEncoding.DecodeString((base64Sig))
if err != nil {
return err
}
//- 选择hash算法,对需要签名的数据进行hash运算
myhash := crypto.SHA256
hashInstance := myhash.New()
hashInstance.Write(data)
hashed := hashInstance.Sum(nil)
//- 读取公钥文件,解析出公钥对象
publicKey, err := ReadParsePublicKey(filename)
if err != nil {
return err
}
//- RSA验证数字签名
return rsa.VerifyPKCS1v15(publicKey, myhash, hashed, bytes)
}
四、其他
1、不可逆的密码加密工具
通常用于密码的加密保存,使用 CompareHashAndPassword 函数校验密码,而不能解密密码。
package utils
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
// 不可逆的密码加密工具封装
// 加密使用 bcrypt.GenerateFromPassword
// 比对密码时使用 bcrypt.CompareHashAndPassword
//加密处理
func EncodePassword(password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
fmt.Println(err)
return "", err
}
// 保存在数据库的密码,虽然每次生成都不同,只需保存一份即可
return string(hash), nil
}
func ComparePassword(databasePwd, loginPwd string) bool {
// 密码验证
err := bcrypt.CompareHashAndPassword([]byte(databasePwd), []byte(loginPwd))
if err != nil {
fmt.Println("pwd wrong")
return false
} else {
fmt.Println("pwd ok")
return true
}
}
2、JWT
package utils
import (
"encryptTool/sys"
"fmt"
"github.com/dgrijalva/jwt-go"
)
// 定义一个jwt对象
type JWT struct {
// 声明签名信息
SigningKey []byte
}
// 初始化jwt对象
func NewJWT() *JWT {
return &JWT{
[]byte(sys.SigningKey),
}
}
// 自定义有效载荷(这里采用自定义的 UserCode 和 UserRole 作为有效载荷的一部分)
type CustomClaims struct {
UserCode string `json:"userCode"`
UserRole string `json:"userRole"`
// StandardClaims结构体实现了Claims接口(Valid()函数)
jwt.StandardClaims
}
// 调用jwt-go库生成token
// 指定编码的算法为jwt.SigningMethodHS512
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
// https://gowalker.org/github.com/dgrijalva/jwt-go#Token
// 返回一个token的结构体指针
token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
return token.SignedString(j.SigningKey)
}
// token解码
func (j *JWT) ParserToken(tokenString string) (*CustomClaims, error) {
// https://gowalker.org/github.com/dgrijalva/jwt-go#ParseWithClaims
// 输入用户自定义的Claims结构体对象,token,以及自定义函数来解析token字符串为jwt的Token结构体指针
// Keyfunc是匿名函数类型: type Keyfunc func(*Token) (interface{}, error)
// func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
// https://gowalker.org/github.com/dgrijalva/jwt-go#ValidationError
// jwt.ValidationError 是一个无效token的错误结构
if ve, ok := err.(*jwt.ValidationError); ok {
// ValidationErrorMalformed是一个uint常量,表示token不可用
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, fmt.Errorf(sys.TokenDisabled)
// ValidationErrorExpired表示Token过期
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
return nil, fmt.Errorf(sys.TokenExpired)
// ValidationErrorNotValidYet表示无效token
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return nil, fmt.Errorf(sys.TokenInvalid)
} else {
return nil, fmt.Errorf(sys.TokenDisabled)
}
}
}
// 将token中的claims信息解析出来并断言成用户自定义的有效载荷结构
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf(sys.TokenInvalid)
}
五、测试
// SIGN => VERIFY
func TestV1() {
str := "will be best"
base64Sig, _ := utils.RSASign([]byte(str), sys.PRIVATE_KEY_PATH)
fmt.Println("签名后信息", base64Sig)
err := utils.RSAVerify([]byte(str), base64Sig, sys.PUBLIC_KEY_PATH)
if err == nil {
fmt.Println("验证签名ok!")
} else {
fmt.Println("验证失败!")
}
}
// ENCODE => DECODE
func TestV2() {
//生成密钥对,保存到文件;若已有密钥对则无需再生成
// utils.GenerateRSAKey(2048)
// 需要加密的明文
message := []byte("hello,world")
fmt.Println("加密原文:", string(message))
//加密
cipherText := utils.RSAEncrypt(message, sys.CIPHER_FILE_PATH, sys.PUBLIC_KEY_PATH)
fmt.Println("加密后为:")
for _, v := range cipherText {
fmt.Printf("%s", string(v))
}
//解密
plainText := utils.RSADecrypt(cipherText, sys.PRIVATE_KEY_PATH)
fmt.Println("解密后为:", string(plainText))
// 读取加密文件进行解密
msg := utils.RSADecryptFromFile(sys.CIPHER_FILE_PATH, sys.PRIVATE_KEY_PATH)
fmt.Println("文件解密后为:", string(msg))
}
func TestV3() {
loginPassword := "123456"
password, err := utils.EncodePassword(loginPassword)
if err != nil {
fmt.Println(err)
}
fmt.Println("EncodePassword ", password)
if utils.ComparePassword(password, loginPassword) {
fmt.Println("password match")
} else {
fmt.Println("password error")
}
}
func TestV4() {
jwt := utils.NewJWT()
token, err := jwt.CreateToken(utils.CustomClaims{UserCode: "user", UserRole: "role"})
if err != nil {
fmt.Println(err)
}
fmt.Println("CreateToken ", token)
parserToken, err := jwt.ParserToken(token)
if err != nil {
fmt.Println(err)
}
fmt.Println("ParserToken ", parserToken)
}
//aGVsbG8gd29ybGQ=
//hello world <nil>
func TestBase64() {
// 标准Base64编码
src := "hello world"
res := base64.StdEncoding.EncodeToString([]byte(src))
fmt.Println(res) // aGVsbG8gd29ybGQ=
// 标准Base64解码
s, err := base64.StdEncoding.DecodeString(res)
fmt.Println(string(s), err) // hello world <nil>
}
六、GUI
package gui
import (
"encryptTool/sys"
"encryptTool/utils"
"fmt"
"fyne.io/fyne/app"
"fyne.io/fyne/container"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
"log"
"strings"
)
func InitGui() {
application := app.New()
// 输入框
input := widget.NewMultiLineEntry()
// 输出框
output := widget.NewMultiLineEntry()
// 加密
encode := widget.NewButton("Encode", func() {
log.Println("encode", input.Text)
message := []byte(input.Text)
//加密
cipherText := utils.RSAEncrypt(message, sys.CIPHER_FILE_PATH, sys.PUBLIC_KEY_PATH)
fmt.Println("加密后为:")
n := 1
outStr := ""
for _, v := range cipherText {
if n > 50 {
outStr = outStr + "\n"
fmt.Print("\n")
n = 0
}
outStr = outStr +string(v)
fmt.Printf("%s", string(v))
n++
}
output.SetText(outStr)
})
// 解密
decode := widget.NewButton("Decode", func() {
log.Println("decode", input.Text)
//解密
plainText := utils.RSADecrypt([]byte(strings.TrimSpace(input.Text)), sys.PRIVATE_KEY_PATH)
log.Println("解密后为:", string(plainText))
output.SetText(string(plainText))
})
// 签名
sign := widget.NewButton("Sign", func() {
log.Println("sign", input.Text)
base64Sig, _ := utils.RSASign([]byte(input.Text), sys.PRIVATE_KEY_PATH)
log.Println("签名后信息", base64Sig)
n := 1
outStr := ""
for _, v := range base64Sig {
if n > 50 {
outStr = outStr + "\n"
fmt.Print("\n")
n = 0
}
outStr = outStr +string(v)
fmt.Printf("%s", string(v))
n++
}
output.SetText(outStr)
})
// 认证
verify := widget.NewButton("Verify", func() {
log.Println("verify", input.Text)
err := utils.RSAVerify([]byte(strings.TrimSpace(input.Text)), output.Text, sys.PUBLIC_KEY_PATH)
if err == nil {
fmt.Println("验证签名ok!")
output.SetText("验证签名ok!")
} else {
fmt.Println("验证失败!")
output.SetText("验证签名失败!")
}
})
// 新窗口
w := application.NewWindow("EncryptTool")
w.SetContent(container.NewVBox(
widget.NewLabel("Input"),
input,
widget.NewLabel("Output"),
output,
container.NewCenter(
container.NewHBox(
encode,
decode,
sign,
verify,
),
),
layout.NewSpacer(),
widget.NewButton("Quit", func() {
application.Quit()
}),
))
// 居中显示
w.CenterOnScreen()
// 重置窗口大小
w.SetFixedSize(true)
//w.Resize(fyne.NewSize(400, 400))
w.ShowAndRun()
}
七、sys
package sys
const CIPHER_FILE_PATH string = "./files/TestCipherFile"
const PUBLIC_KEY_PATH string = "./files/public.pem"
const PRIVATE_KEY_PATH string = "./files/private.pem"
const SigningKey string = ""
const TokenDisabled string = "token disabled" // token 不可用
const TokenInvalid string = "token invalid" // token 无效
const TokenExpired string = "token expired" // token 过期
八、界面
1、测试加密
2、测试解密
3、测试签名
4、验证签名
九、浅应用
构建一个签证Center、令牌Center、加解密Center,采用MySQL、Redis等中间件,实现定期动态新增密钥对,Center随机调用密钥对,记录密钥对 ID ,以便后续用相同密钥对进行操作,因此每个使用者调用的加密密钥对都是不同的。
十、改进
使用QT、WinForm、Web等前端工具构建更加友好的操作页面;采用多种加密方式供用户选择,而不只是局限于一种加密方式。
十一、参考
Go实现RSA数字签名算法(附代码)
Go语言实现RSA加密解密
golang gui库fyne的简单尝试