当我们希望数据不要直接呈现出其结果时我们会选择做加密处理,比如:用户账号的密码、手机号、身份证号等敏感信息通常都是经过加密处理后再写入数据库进行存储的,否则这些信息可能会被拦截而导致信息泄露,因此这就涉及到一个加密的问题。
在讲加密之前,我们要提一下“加盐”的问题。这里的“盐”指的是混淆串,比如:需要加密的数据是“abc123”,我给它拼接上其他的字符串,让它变成“2006-01-02 15:04:05abc1232006-01-02 15:04:05”,可以看出在原串的开头和结尾我都加了其他字符串进去,这些字符串就是混淆串,也就是所谓的“盐”。那么为什么要“加盐”呢?很简单的一个道理,万一我们的加密数据被破解了,我们的数据在没有“加盐”的情况下不就直接暴露其原值了吗?这个数据就这样直接泄露出去了。但如果我们做了“加盐”处理,即使数据被成功解密出来了,要得到真正的结果还得废很大的功夫,拿上面的例子,“2006-01-02 15:04:05abc1232006-01-02 15:04:05”,假设这个串是被破解出来后的结果,距离要得到“abc123”还差得远,考虑到效率的问题,也就不会有人花那么多精力在破解这个数据上。上面这个例子其实还是很有规律可言的,当然实际开发中我们“加盐”肯定不会这么有规律,我们可以在开头、串中间、结尾插入任意长度的任意字符串构成混淆,就目前技术而言要从这个加了盐的混淆串中提取出真实有效的数据基本就不可能了。
在密码学中常用的加密算法有3种:Hash算法(消息摘要Message Digest)、编码解码算法、加密解密算法。
Hash算法无需秘钥,并且过程是不可逆的。也就是说一旦经过Hash之后再也没办法转回Hash前的串,因此比较适合对那么只用来验证的数据做加密,例如:密码。
//常见的Hash算法:md4(128位)、md5(128位)、sha1(160位)、sha256(256位)、sha512(512位)
/**
md5、sha1、sha256、sha512加密算法
@param src [string] 原始待加密数据
@param CryptTool [string] 加密类型
@param isHex [bool] 数据是否为16进制串,true表示16进制串,需要进行解析,false表示非16进制串
@param isDoubleCrypt [bool] 判断是否做二次加密
@return [string] Hash加密后的串
*/
func Hash(src,CryptTool string,isHex bool,isDoubleCrypt bool)string{
var hash hash.Hash
switch CryptTool {
case "md5":
hash = md5.New()
case "sha1":
hash = sha1.New()
case "sha256":
hash = sha256.New()
case "sha512":
hash = sha512.New()
}
if isHex {
//如果加密串本身是16进制串需要做解析
csrc,_ := hex.DecodeString(src)
hash.Write(csrc)
}else {
hash.Write([]byte(src))
}
//一次加密后的结果
cryptStr := fmt.Sprintf("%x",hash.Sum(nil))
if isDoubleCrypt {
//做二次加密
hash.Reset()
hash.Write([]byte(cryptStr))
cryptStr = fmt.Sprintf("%x",hash.Sum(nil))
}
return cryptStr
}
编码解码严格来说不能算加密,只是对数据做16进制的编码而已,编码解码无需秘钥,并且其过程是可逆的,可以将原串进行编码,同样可以以相反的过程进行解码。
//常见编码解码算法:base64、base58
/**
base64加密算法
@param src [string] 原始待编码数据
@param isEncode [bool] 判读是否进行编码,true表示编码,false表示解码
@return [string] 编码或解码后的串
*/
func EnDecode(src string,isEncode bool)string{
var cryptStr string
if isEncode {
//编码
cryptStr = base64.StdEncoding.EncodeToString([]byte(src))
}else {
//解码
tmp,err := base64.StdEncoding.DecodeString(src)
CheckError(err,BASE64_DECODE_ERROR,"Base64解码失败---来自CryptTool.go的EnDecode函数")
cryptStr = string(tmp)
}
return cryptStr
}
加密解密算法需要秘钥,并且过程是可逆的(这里解释一下秘钥的概念:秘钥就是秘密的钥匙,在这里可以认为是对数据做加密和解密的钥匙,下面提到的公钥和私钥都统称为秘钥)。加密解密这里只讨论两种:对称加密和非对称加密。对称加密使用的是对称秘钥(即私钥),在加密和解密过程中使用的是相同的秘钥。非对称加密使用的是非对称秘钥(即公钥),用公钥加密,用私钥解密。
加密解密算法的加解密模式有很多种,这里只讨论使用最多的CBC(密码分组链接)模式。
对称加密的典型算法有很多,比较常见的有DES、3DES、AES。
/**
des加密算法,秘钥长度为8B,故秘钥字符串长度为8
@param src [string] 原始待加密数据
@return [string] DES加密后的串
*/
func DESEncrypter(src,key string)string{
if len(key) != 8 {
//des算法秘钥长度必须是长度为8的串,因为构建分组块的时候每个分组块的长度必须等于秘钥长度
panic("秘钥长度不是8---来自CryptTool.go的DESCrypter函数")
}
//秘钥(此处为私钥)
//分组块
block,err := des.NewCipher([]byte(key))
CheckError(err,BLOCK_GENERATE_ERROR,"分组块构建失败---来自CryptTool.go的DESCrypter函数")
//块长度(即秘钥长度)
blockSize := block.BlockSize()
//补全码
//加密的数据长度必须是秘钥程度的整数倍,将数据分成若干块,最后一块长度不够的需要补齐长度
nsrc := padding([]byte(src),blockSize)
//加密模式;CBC(密码分组链接模式)
blockMode := cipher.NewCBCEncrypter(block,[]byte(key)[:blockSize])
dst := make([]byte,len(nsrc))
blockMode.CryptBlocks(dst,nsrc)
return base64.StdEncoding.EncodeToString(dst)
}
/**
des解密算法
@param src [string] 待解密数据
@return [string] DES解密后的串
*/
func DESDecrypter(src,key string)string{
if len(key) != 8 {
//des算法秘钥长度必须是长度为8的串,因为构建分组块的时候每个分组块的长度必须等于秘钥长度
panic("秘钥长度不是8---来自CryptTool.go的DESDecrypter函数")
}
nsrc,err := base64.StdEncoding.DecodeString(src)
CheckError(err,BASE64_DECODE_ERROR,"Base64解码失败---来自CryptTool.go的DESDecrypter函数")
//分组块
block,err := des.NewCipher([]byte(key))
CheckError(err,BLOCK_GENERATE_ERROR,"分组块构建失败---来自CryptTool.go的DESCrypter函数")
//分组块长度
blockSize := block.BlockSize()
//解密模式;CBC(密码分组链接模式)
blockMode := cipher.NewCBCDecrypter(block,[]byte(key)[:blockSize])
dst := make([]byte,len(nsrc))
blockMode.CryptBlocks(dst,nsrc)
//去掉填充串
dst = unPadding(dst)
return string(dst)
}
//填充原串
func padding(src []byte,blockSize int)[]byte{
//需要补齐填充的长度
length := blockSize - len(src) % blockSize
//用来补齐填充的字符切片,里面的内容清一色是补齐长度的byte类型值
paddingStr := bytes.Repeat([]byte{byte(length)},length)
nsrc := append(src,paddingStr...)
return nsrc
}
//去掉填充串
func unPadding(src []byte)[]byte{
//填充长度
length := int(src[len(src) - 1])
src = src[:(len(src) - length)]
return src
}
/**
3des加密算法:就是3倍DES的秘钥长度(24B)
@param src [string] 原始待加密数据
@return [string] 3DES加密后的串
*/
func TripleDESEncrypter(src,key string)string{
if len(key) != 24 {
//des算法秘钥长度必须是长度为8的串,因为构建分组块的时候每个分组块的长度必须等于秘钥长度
panic("秘钥长度不是24---来自CryptTool.go的TripleDESEncrypter函数")
}
//分组块
block,err := des.NewTripleDESCipher([]byte(key))
CheckError(err,BLOCK_GENERATE_ERROR,"分组块构建失败---来自CryptTool.go的TripleDESEncrypter函数")
//秘钥长度
blockSize := block.BlockSize()
//补全码
//加密的数据长度必须是秘钥程度的整数倍,将数据分成若干块,最后一块长度不够的需要补齐长度
nsrc := padding([]byte(src),blockSize)
//加密模式
blockMode := cipher.NewCBCEncrypter(block,[]byte(key)[:blockSize])
dst := make([]byte,len(nsrc))
//加密
blockMode.CryptBlocks(dst,nsrc)
return base64.StdEncoding.EncodeToString(dst)
}
/**
3des解密算法:3就是3倍DES的秘钥长度(24B)
@param src [string] 待解密数据
@return [string] 3DES解密后的串
*/
func TripleDESDecrypter(src,key string)string{
if len(key) != 24 {
//des算法秘钥长度必须是长度为8的串,因为构建分组块的时候每个分组块的长度必须等于秘钥长度
panic("秘钥长度不是24---来自CryptTool.go的TripleDESDecrypter函数")
}
nsrc,err := base64.StdEncoding.DecodeString(src)
CheckError(err,BASE64_DECODE_ERROR,"base64解码失败---来自CryptTool.go的TripleDESDecrypter函数")
//分组块
block,err := des.NewTripleDESCipher([]byte(key))
CheckError(err,BLOCK_GENERATE_ERROR,"分组块构建失败---来自CryptTool.go的TripleDESDecrypter函数")
//秘钥长度
blockSize := block.BlockSize()
//解密模式
blockMode := cipher.NewCBCDecrypter(block,[]byte(key)[:blockSize])
dst := make([]byte,len(nsrc))
//解密
blockMode.CryptBlocks(dst,nsrc)
//去掉填充内容
dst = unPadding(dst)
return string(dst)
}
/**
aes加密算法:秘钥长度可指定(16B、24B、32B)
@param src [string] 原始待加密数据
@return [string] AES加密后的串
*/
func AESEncrypter(src,key string)string{
if len(key) != 16 && len(key) != 24 && len(key) != 32 {
panic("秘钥长度不合法---来自CryptTool.go的AESEncrypter函数")
}
//分组块
block,err := aes.NewCipher([]byte(key))
CheckError(err,BLOCK_GENERATE_ERROR,"分组块构建失败---来自CryptTool.go的AESEncrypter函数")
//秘钥长度
blockSize := block.BlockSize()
nsrc := padding([]byte(src),blockSize)
//加密模式
blockMode := cipher.NewCBCEncrypter(block,[]byte(key)[:blockSize])
dst := make([]byte,len(nsrc))
//加密
blockMode.CryptBlocks(dst,nsrc)
return base64.StdEncoding.EncodeToString(dst)
}
/**
aes解密算法:秘钥长度可指定(16B、24B、32B)
@param src [string] 待解密数据
@return [string] AES解密后的串
*/
func AESDecrypter(src,key string)string{
if len(key) != 16 && len(key) != 24 && len(key) != 32 {
panic("秘钥长度不合法---来自CryptTool.go的AESEncrypter函数")
}
nsrc,err := base64.StdEncoding.DecodeString(src)
CheckError(err,BASE64_DECODE_ERROR,"base64解码失败---来自CryptTool.go的TripleDESDecrypter函数")
//分组块
block,err := aes.NewCipher([]byte(key))
CheckError(err,BLOCK_GENERATE_ERROR,"分组块构建失败---来自CryptTool.go的AESEncrypter函数")
//秘钥长度
blockSize := block.BlockSize()
//解密模式
blockMode := cipher.NewCBCDecrypter(block,[]byte(key)[:blockSize])
dst := make([]byte,len(nsrc))
//解密
blockMode.CryptBlocks(dst,nsrc)
//去掉填充内容
dst = unPadding(dst)
return string(dst)
}
非对称加密因为使用的是公钥加密、私钥解密,因此我们没办法手动指定秘钥,必须通过代码生成。
生成私钥和公钥并写入文件(我们必须要有一种持久化手段来存储私钥和公钥,因为代码生成的私钥和公钥是带有随机性的,即本次运行得到的结果下一次基本再也得不到了,如果不做持久化那么解密方哪来的私钥对加了密的数据解密?)
/**
生成公钥、私钥
*/
func GenerateKey(){
//生成私钥
privateKey,err := rsa.GenerateKey(rand.Reader,1024)
CheckError(err,PRIVATEKEY_GENERATE_ERROR,"私钥生成错误---来自CryptTool.go的GenerateKey函数")
//序列化私钥
priKey := x509.MarshalPKCS1PrivateKey(privateKey)
//将私钥转成pem格式
priBlock := &pem.Block{
Type:"RSA Private Key",
Bytes:priKey,
}
//生成存储私钥的pem文件
priKeyFp,err := os.Create("privateKey.pem")
CheckError(err,FILE_CREATE_ERROR,"文件创建失败---来自CryptTool.go的GenerateKey函数")
defer priKeyFp.Close()
//pem格式数据写入文件
err = pem.Encode(priKeyFp,priBlock)
CheckError(err,WRITE_ON_FILE,"将pem格式私钥写入文件失败---来自CryptTool.go的GenerateKey函数")
//生成公钥
publicKey := &privateKey.PublicKey
//序列化公钥
pubKey,err := x509.MarshalPKIXPublicKey(publicKey)
CheckError(err,PRIVATEKEY_GENERATE_ERROR,"公钥生成错误---来自CryptTool.go的GenerateKey函数")
//将公钥转成pem格式
pubBlock := &pem.Block{
Type:"RSA Public Key",
Bytes:pubKey,
}
//生成存储公钥的pem文件
pubKeyFp,err := os.Create("publicKey.pem")
CheckError(err,FILE_CREATE_ERROR,"文件创建失败---来自CryptTool.go的GenerateKey函数")
defer pubKeyFp.Close()
//pem格式数据写入文件
err = pem.Encode(pubKeyFp,pubBlock)
CheckError(err,WRITE_ON_FILE,"将pem格式公钥写入文件失败---来自CryptTool.go的GenerateKey函数")
}
/**
RSA加密算法:公钥加密
@param src [string] 待加密数据串
@return [string] RSA加密数据串
*/
func RSAEncrypter(src string)string{
fp,err := os.Open("publicKey.pem")
CheckError(err,FIEL_OPEN_ERROR,"文件打开失败---来自CryptTool.go的RSAEncrypter函数")
defer fp.Close()
tmp := make([]byte,1024)
//pem格式私钥
var pubKeyPem string
for {
n,err := fp.Read(tmp)
if err != nil {
if err == io.EOF {
//文件读到末尾
break
}else {
CheckError(err,FIEL_READ_ERROR,"文件读取失败---来自CryptTool.go的RSAEncrypter函数")
}
}
if n == 0 {
//文件读到末尾
break
}
pubKeyPem += string(tmp[:n])
}
//解析pem格式公钥
pubBlock,_ := pem.Decode([]byte(pubKeyPem))
if pubBlock == nil {
panic("pem格式公钥解析失败")
}
//解析公钥
pub,err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
CheckError(err,PUBLICKEY_PARSE_ERROR,"公钥解析失败---来自CryptTool.go的RSAEncrypter函数")
//类型断言,转化类型
publicKey := pub.(*rsa.PublicKey)
//公钥加密
nsrc,err := rsa.EncryptPKCS1v15(rand.Reader,publicKey,[]byte(src))
CheckError(err,PUBLICKEY_ENCRYPT_ERROR,"公钥加密失败---来自CryptTool.go的RSAEncrypter函数")
return base64.StdEncoding.EncodeToString(nsrc)
}
/**
RSA解密算法:私钥解密
@param src [string] 待解密数据串
@return [string] RSA解密数据串
*/
func RSADecrypter(src string)string{
fp,err := os.Open("privateKey.pem")
CheckError(err,FIEL_OPEN_ERROR,"文件打开失败---来自CryptTool.go的RSADecrypter函数")
defer fp.Close()
tmp := make([]byte,1024)
//pem格式私钥
var priKeyPem string
for {
n,err := fp.Read(tmp)
if err != nil {
if err == io.EOF {
break
}else {
CheckError(err,FIEL_READ_ERROR,"文件读取失败---来自CryptTool.go的RSADecrypter函数")
}
}
if n == 0 {
break
}
priKeyPem += string(tmp[:n])
}
//解析pem格式私钥
priBlock,_ := pem.Decode([]byte(priKeyPem))
//解析私钥
privateKey,err := x509.ParsePKCS1PrivateKey(priBlock.Bytes)
CheckError(err,PRIVATEKEY_PARSE_ERROR,"私钥解析失败---来自CryptTool.go的RSADecrypter函数")
nsrc,err := base64.StdEncoding.DecodeString(src)
CheckError(err,BASE64_DECODE_ERROR,"Base64解码失败---来自CryptTool.go的RSADecrypter函数")
//私钥解密
nsrc,err = rsa.DecryptPKCS1v15(rand.Reader,privateKey,nsrc)
CheckError(err,PRIVATEKEY_DECRYPT_ERROR,"私钥解密失败---来自CryptTool.go的RSADecrypter函数")
return string(nsrc)
}
从加密解密算法中可以很清晰地看出,其实加密、解密过程是完全相反的过程,只需要大概记一下流程,就能够推导出相反的过程了。上面代码中加密的最后会用base64进行编码,是因为加密后的数据可能带有类似乱码的符号,所以干脆做一下base64编码(字符集:0-9A-Za-z+/ 共64个字符),免得看起来怪难受的。