密码学03--go语言与非对称加密RSA算法的实现

目录

1.对称加密的弊端

2.非对称加密

2.1 非对称加密使用场景

2.2 区分公私钥

2.3 非对称加密通信流程

2.4 非对称加密与对称加密

3.非对称加密RSA算法

3.1 RSA算法

3.2 RSA原理

3.3 RSA生成密钥对流程

3.3.1 RSA生成私钥

3.3.2 RSA生成公钥

3.4 RSA生成密钥对模板

3.5 使用RSA密钥对加密-解密流程

3.5.1 使用RSA公钥加密流程

3.5.2 使用RSA私钥解密流程

3.6 使用RSA密钥对加密-解密模板

1.对称加密的弊端

对称加密的弊端在于密钥分发困难,无法保证密钥的发送是绝对安全,因此一旦密钥丢失会导致数据信息极易被攻破。一般会通过非对称加密的方式来对对称加密的密钥进行分发保护。例如:

在图中步骤1、2、3都是为了能够将对称加密的key以一种相对安全的方式传输给Alex的一种手段。因为与Alex公钥对应的私钥仅仅在Alex本地才持有,这就意味着Frank处通过Alex公钥加密的内容其他人即便截获也无法读取。这样就保证了对称加密key的安全性。

2.非对称加密

非对称加密不存在密钥分发困难的问题,但需要对非对称加密的公私钥作出一个简单的说明:公钥和私钥都能够加密数据,公钥和私钥也都能解密数据,并不存在说只能使用公钥加密数据,只能使用私钥解密数据的说法。但是不同的是如果使用私钥加密数据,那么与私钥对应的所有公钥都能解密数据,由于公钥是公开的,所以所有持有公钥的用户都能够破解数据的加密,那么数据就无法做到限制某一个用户读取,换言之这种加密方式基本就等于没有加密。所以对于非对称加密来说:通常都是使用公钥对数据加密,然后只有持有私钥的用户可以对数据解密,保证数据安全。

2.1 非对称加密使用场景

数据对谁更重要,谁就持有私钥。

  • (1)通信过程:A将数据传输给B,信息显然只允许B读取(A持有公钥加密数据,B持有私钥解密数据)
  • (2)登录认证:客户端需要向服务器发送登录请求(服务器持有公钥加密登录请求响应,客户端持有私钥解密响应数据)
  • (3)网银U盾:银行向个人发起认证信息只能允许个人读取(银行中持有公钥加密,个人持有的U盾中持有私钥解密)
  • (4)数字签名:附在信息原文后面的表明信息传输过程中是否受到伪造、用来确认的确由信息拥有者发出的验证信息
  •                        (比较特殊,由发送者持有私钥,接受者持有公钥。)

2.2 区分公私钥

  • 直接观察区别区分:一般情况下,私钥长度大于公钥长度。
  • 第三方工具生成的:公钥结构都是xxx.pub带有.pub的后缀,而私钥则是xxx这种不带有后缀的结构。

2.3 非对称加密通信流程

具体细节参见1中的图示内部步骤1、2、3即可。

2.4 非对称加密与对称加密

非对称加密尽管看起来简洁清晰、安全性高、似乎各方面都完爆对称加密,但是非对称加密无法替代对称加密!因为非对称加密尽管加密过程简单,但是非对称加密也存在很大的局限性

  • 非对称加密的执行效率要低于对称加密,同样数量级的数据加密操作对称加密速度更快
  • 非对称加密无法对大结构数据进行加密,因为非对称加密不存在分组加密操作,因此密钥的长度决定了加密数据长度有限

所以在使用加密的时候都会将对称加密的key通过非对称加密先行发送,毕竟对称加密的key都不会很大。然后在将本体数据经过对称加密后发送。也就是1中图示的内容。

3.非对称加密RSA算法

3.1 RSA算法

RSA是一种非对称加密算法,它的名字是由它的三位开发者,即RonRivest、AdiShamir和LeonardAdleman 的姓氏的首字母组成的,即Rivest-Shamir-Leonard(来威斯特-夏米尔-莱昂纳德)。它是目前使用最广泛的一种非对称加密算法,RSA不仅可以被用于非对称加密还能够用于生成数字签名。1983年,RSA公司为RSA算法在美国取得了专利,但现在该专利于2000年9月21日过期。

再次强调:非对称加密的密钥分为加密密钥解密密钥两部分

3.2 RSA原理

与逻辑原理非常复杂的对称加密相比较,非对称加密中的RSA逻辑原理简单到让人不可思议,因为RSA的逻辑原理仅仅只用到了简单的乘方与取模操作。在RSA算法中明文、密文还有密钥都是数字,那么他的逻辑原理就能够用下面两个公式来表示:

其中E代表单词Encryption加密、D代表Decryption解密、N代表Number数字,而{E,N}组合就是公钥,{D,N}组合就是私钥。

3.3 RSA生成密钥对流程

3.3.1 RSA生成私钥

(1)使用rsa包中的GenerateKey方法生成私钥对象

/*
    参数random是随机数(就是私钥本身),bits是密钥位数
    参数一:random不再使用Math中的方法,而是选择crypto/rand包中的一个io.Reader类型的Reader固有对象。
    参数二:最好是1024的整数倍
    返回值:返回一个PrivateKey类型的私钥结构体
        type PrivateKey struct {
            PublicKey                        // 公钥--公钥是被包含在私钥中,一起生成的
            D         *big.Int               // 私有的指数
            Primes    []*big.Int             // (暂时忽略)N的素因子,至少有两个
            Precomputed PrecomputedValues    // (暂时忽略)包含预先计算好的值,可在某些情况下加速私钥的操作
        }
        type PublicKey struct {
            N   *big.Int                     // 模
            E   int                          // 公开的指数
        }
*/
func GenerateKey(random io.Reader, bits int) (priv *PrivateKey, err error)

(2)通过x509标准包将得到的rsa私钥对象序列化为的ASN.1的DER编码字符串

  • x509:是一种本地存储的证书规范,一般规范要求公钥存放在数字签名之中。
  • ASN.1抽象语法标记:是一种独立于计算机架构和语言的描述数据结构的语法标记
  • ASN.1的编码规范有:BER、CER、DER、PER、XER等,其中DER是一种对数据的编码规范(二进制编码)
/*
    使用crypto/x509包中的MarshalPKCS1PrivateKey方法,生成一个ASN.1标准的DER编码字符串
    而需要的参数就是(1)中生成的私钥对象*PrivateKey
*/
func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte

(3)构建一个pem.Block数据块,保存DER编码字符串

此时作出一个声明:DER编码字符串中的内容并不是么一个字符都对用户是可见的,在ASCII表中有相当一部分字符是没有显示方式的因此直接存储或显示DER编码字符串会造成不可预知的麻烦

那么显然就需要一种办法将内容并不都可见的DER编码字符串转换成均可见内容的字符串,这时我们找到了pem编码规范。由于pem包中提供的Encode编码方法能够用来对字符串序列进行编码操作,并且会最终将序列化的内容以base64的编码格式呈现,所以选择使用pem规范对生成的DER编码字符串进行base64编码序列化。但是由于pem包中的Encode方法需要一个pemBlock类型的参数,因此需要先构建一个pem.Block数据块,然后将待序列化的DER编码字符串存入。

/*
    Block结构是存在于encoding/pem包中定义的结构体
*/
type Block struct {
    Type    string                //相当于是标题(例如:私钥就写”RSA PRIVATE KEY”/公钥就写”RSA PUBLICK KEY“)
    Headers map[string]string     //可选的头项,省略即可
    Bytes   []byte                //内容解码后的数据,一般是ASN.1标准的DER编码字符串,即(2)中生成的字符串
}

(4)通过encoding/pem包中的包的Encode方法将构建好的pem.Block数据块base64编码,并写入本地文件中。

/*
    第一个参数就是随便一个文件指针,file就可以
    第二个参数是(3)中生成的pem.Block块。
*/
func Encode(out io.Writer, b *Block) error

3.3.2 RSA生成公钥

(1)从生成的私钥对象中取出公钥对象,原因参考3.3.1(1)代码注释

publicKey := PrivateKey.PublicKey

(2)通过x509标准包将得到的rsa私钥对象序列化为的ASN.1的DER编码字符串

/*
    注意私钥的x509方法是PKCS1Private
    func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte
    而公钥的x509方法是PKIXPublic
    func MarshalPKIXPublicKey(pub interface{}) ([]byte, error)

    特别的是公钥这个方法的返回值并不是*PublicKey,而是一个interface{}
    事实上其实是因为x509包内对于*PublicKey有*rsa.PublicK和*ecdsa.PublicKey两种规格
    所以这个位置的参数在传递的时候,添加一个&符号即可。具体归属哪一种类型x509包方法会自行判断
*/
func MarshalPKIXPublicKey(pub interface{}) ([]byte, error)

(3)构建一个pem.Block数据块,保存DER编码字符串

/*
    这玩意和生成私钥的Block一样的,都是pem标准的Block数据类型
*/
type Block struct {
    Type    string                //相当于是标题(例如:私钥就写”RSA PRIVATE KEY”/公钥就写”RSA PUBLICK KEY“)
    Headers map[string]string     //可选的头项,省略即可
    Bytes   []byte                //内容解码后的数据,一般是ASN.1标准的DER编码字符串,即(2)中生成的字符串
}

(4)通过encoding/pem包中的Encode方法将构建好的pem.Block数据块base64编码,并写入本地文件中。

/*
    同理,与私钥生成方法一致
    第一个参数就是随便一个文件指针,file就可以
    第二个参数是(3)中生成的pem.Block块。
*/
func Encode(out io.Writer, b *Block) error

3.4 RSA生成密钥对模板

//创建工具函数生成RSA密钥对,并保存到本地
func GenerateRSAkey(keySize int){
	//------------------------私钥--------------------------
	//1.使用rsa包中的GenerateKey方法来生成私钥对象
	privateKey,err := rsa.GenerateKey(rand.Reader, keySize)
	if err!=nil{
		panic(err)
	}
	//2.通过x509标准将得到的rsa私钥对象序列化为ASN.1标准的DER编码字符串
	derPrivateCode := x509.MarshalPKCS1PrivateKey(privateKey)
	//3.构建一个pem.Block块,保存私钥DER编码字符串
	peoPrivateBlock := pem.Block{Type:"FRANK RSA PRIVATE KEY",Bytes:derPrivateCode}
	//4.通过pem的Encode方法将构建好的pem.Block数据块编码(base64编码格式),并保存至本地
	filePrivate,err := os.Create("frankPrivate.pem")
	err = pem.Encode(filePrivate,&peoPrivateBlock)
	if err!=nil{
		panic(err)
	}
	//5.私钥写入文件关闭
	filePrivate.Close()

	//------------------------公钥--------------------------
	//1.从得到的私钥对象中取出公钥对象取出
	publickKey := privateKey.PublicKey
	//2.通过x509标准将得到的rsa公钥对象序列化为ASN.1标准的DER编码字符串
	derPublicCode,pubErr := x509.MarshalPKIXPublicKey(&publickKey)
	if pubErr!=nil{
		panic(pubErr)
	}
	//3.构建一个pem.Block块,保存公钥DER编码字符串
	peoPublicBlock := pem.Block{Type:"FRANK RSA PUBLIC KEY",Bytes:derPublicCode}
	//4.通过pem的Encode方法将构建好的pem.Block数据块编码(base64编码格式),并保存至本地
	filePublic,err := os.Create("frankPublic.pem")
	err = pem.Encode(filePublic,&peoPublicBlock)
	if err!=nil{
		panic(err)
	}
	//5.公钥写入文件关闭
	filePublic.Close()
}

3.5 使用RSA密钥对加密-解密流程

3.5.1 使用RSA公钥加密流程

此时作出一个声明:不管是对称加密密钥也好还是非对称加密密钥、亦或者是RSA算法也好还是ECDSA椭圆算法也好,最终使用密钥并对文件进行加密操作时密钥必须是一个已知的明文字符串序列!所以我们在3.4中的确将公钥和私钥生成并编码存入了本地文件,但是如果需要使用他们则必须在使用的时候先将公私钥文件反向解密为明文字符串序列,然后使用这个明文字符串序列对真实内容加密。由此可知如果想要使用RSA密钥的公钥对数据加密,则必须先将生成公钥文件的操作反向才行。

(1)将公钥base64编码字符串从本地公钥文件中取出

(2)通过encoding/pem包中的Decode方法解码,将pem构建的base64编码反向解密,得到pem编码之前的pem.Block块

(3)使用x509包中提供的数据解析方法,将pem.Block中Byte字段内的公钥对象解析出来

func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error) 
//不要忘记本方法的返回值是interface{}接口类型,因为存在*rsa.PublicKey和*ecdsa.PublicKey两种公钥类型
publickKey := pub.(*rsa.PublicKey)
//使用断言,将公钥类型确定为RSA类型

(4)核心加密代码:使用rsa包中提供的加密方法,借助得到的公钥来对信息加密

/*
    第一个参数就是rand.Reader那个密码随机数生成器
    第二个参数就是解密公钥文件得到的【公钥明文本体】
    第三个参数就是需要加密的明文信息

    当然返回的就是密文信息
*/
func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) (out []byte, err error)

3.5.2 使用RSA私钥解密流程

显然使用RSA公钥去加密数据的流程和使用RSA私钥去解密数据的流程及户完全一样。

(1)将私钥base64编码字符串从本地私钥文件中取出

(2)通过encoding/pem包中的Decode方法解码,将pem构建的base64编码反向解密,得到pem编码之前的pem.Block块

(3)使用x509包中提供的数据解析方法,将pem.Block中Byte字段内的私钥对象解析出来

func ParsePKCS1PrivateKey(der []byte) (key *rsa.PrivateKey, err error)
//私钥的解密方法返回值就是rsa.PrivateKey类型,不再需要断言。

(4)核心解密代码:使用rsa包中提供的解密方法,借助得到的私钥来对信息解密

/*
    第一个参数就是rand.Reader那个密码随机数生成器
    第二个参数就是解密私钥文件得到的【公钥明文本体】
    第三个参数就是需要解密的密文信息

    当然返回的就是解密后的明文信息
*/
func DecryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) (out []byte, err error)

3.6 使用RSA密钥对加密-解密模板

/*
    使用RSA公钥加密工具函数
    参数一:就是需要加密的明文内容
    参数二:就是公钥文件名
    返回值自然就是RSA公钥文件加密后的加密字符串
*/
func RSAUsePublicKeyEncrypt(plainText []byte, publickeyFileName string) []byte{
	//1.将公钥文件当中的公钥base64编码读出存入缓冲区
	publicFile,err := os.Open(publickeyFileName)
	if err!=nil{
		panic(err)
	}
	defer publicFile.Close()
	//获取文件长度,文件多长就创建多长缓冲区
	fileInfo,err := publicFile.Stat()
	if err!=nil{
		panic(err)
	}
	buffer := make([]byte, fileInfo.Size())
	publicFile.Read(buffer)
	//2.pem解码,将公钥base64解码为一个存有公钥DER字符串的pem.Block数据块
	block,_ := pem.Decode(buffer)
	//3.使用x509包提供的公钥解析方法,对公钥DER字符串解析
	pubInterface,err := x509.ParsePKIXPublicKey(block.Bytes)
	if err!=nil{
		panic(err)
	}
	publickKey := pubInterface.(*rsa.PublicKey)//将公钥断言为rsa类型
	//4.使用rsa包中的公钥加密方法加密数据
	cipher,err := rsa.EncryptPKCS1v15(rand.Reader, publickKey, plainText)
	if err!=nil{
		panic(err)
	}
	return cipher
}
/*
    使用RSA私钥解密的工具函数
    参数一:就是需要解密的密文内容
    参数二:就是私钥文件名
    返回值自然就是RSA私钥文件解密后的原本明文字符串
*/
func RSAUsePrivateKeyDecrypt(cipherText []byte, privateKeyFileName string)[]byte{
	//1.将私钥文件当中的私钥base64编码读出存入缓冲区
	privateFile,err := os.Open(privateKeyFileName)
	if err!=nil{
		panic(err)
	}
	defer privateFile.Close()
	//仍然是文件多大创建多大缓冲区
	fileInfo,err := privateFile.Stat()
	if err!=nil{
		panic(err)
	}
	buffer := make([]byte, fileInfo.Size())
	privateFile.Read(buffer)
	//2.pem解码,将私钥base64编码解码成一个存有私钥DER字符串的pem.Block数据块
	block,_ := pem.Decode(buffer)
	//3.使用x509包提供的私钥解析方法,对私钥DER字符串解析
    //注意本方法返回的就直接是res.PrivateKey类型对象,不用断言
	privateKey,err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err!=nil{
		panic(err)
	}
	//4.使用rsa包中提供的私钥解密方法解密数据
	plainText,err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText)
	if err!=nil{
		panic(err)
	}
	return plainText
}

猜你喜欢

转载自blog.csdn.net/u013792921/article/details/85028934