截取自我的博客:https://chunlife.top/2018/07/29/RSA%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86/
因为项目需要,最近做一个RSA加密解密的接口,使用Go进行开发,接口使用jsonrpc,go 对RSA加密解密有很好的支持,不过由于受限于底层单片机,所以上层应用需要做一些稍微的调整。
RSA是一种非对称加密算法,什么是非对称加密算法呢,那就是公钥、私钥可互相进行加密解密:公钥加密—私钥解密,私钥加密—公钥解密。
了解RSA算法的实现原理,可参考:非对称加密过程详解(基于RSA非对称加密算法实现)
什么是PKCS#1,PKCS(公钥密码标准),而#1就是RSA的标准。
PEM文件,也就是公私钥的编码格式。
RSA算法的原理:RSA算法详解,从这篇博客主要是提取出RSA算法的公式。
1 |
C=(P^e)%n |
N是公钥私钥共同使用的,其为模数。另外还有公钥的指数E,私钥的指数E。
公钥的指数一般是65537,私钥的指数则是一个极大的数,想想一个极大的数作为指数,计算时间是会耗费很长时间的。故公钥加密解密都很快,私钥则会慢很多
RSA标准是通过公钥加密,私钥解密 ,没有私钥加密,公钥解密。为什么会这样呢?
Go设计库一般会严格按照标准来进行设计(在很多地方都见过相似做法),那是RSA标准中没有后一种情况的使用场景吗?其实是有的,只不过这个过程不要加密解密,而是RSA签名与验签。所以按照标准,RSA标准库也就不会有私钥加密,公钥解密的方法了。
这个问题想想应该是很多人的问题了,那么在Google上进行搜索了下,还是发现了解决方法。
https://github.com/wenzhenxi/gorsa
库中实现了公钥加解密的方法。
还有其他解决方法吗?是有的,在Google上进行搜索就可以找到,记得还有人使用CGO调用C库来解决。
标准库的使用者很多,博客也很多,这里不做多的介绍,放上一个博主的链接。
GO加密解密之RSA
此处,由于我的问题比较特殊,所以到此并没完全解决我的问题,还记得上面说的RSA指数与模数的东西吗,因为上层是与stm32进行通信,32RSA的库是需要自己手动将指数与模数填入结构体中的,那么上层就应该将生成的私钥进行分解开来,得到stm32所需要的指数和模数,那么怎么得到这些数据呢。
我使用的方法是借用OpenSSL,理论上来说像Python就可以做到,但是我并不想将事情复杂化,直接借用现有的工具是最省事的。
参考:如何用 openssl 生成RSA双密匙;签名证书;加密文件邮件
1 |
openssl rsa -in private.pem -text -noout |
-noout : 表示不显示密钥
运行结果:
modulus、publicExponent、privateExponent,这三个数就是我们所需要的数据(publicExponent一般算法会设为65537)。
那么此处就很简单的进行字符串截取就可以做到拿出这三个数据了。
既然提到了RSA,不对称加密算法了,那么也去了解了解AES对称加密算法吧。
golang实现AES ECB模式的加密和解密
Go的实现可参考贴出来的链接,不过此处给出我遇到的一个问题。
截取自博客中的原文:
标记出来的话,其实是有问题的,AES算法,区块长度是固定的,为128bit。
摘抄自百度百科:严格地说,AES和Rijndael加密法并不完全一样(虽然在实际应用中二者可以互换),因为Rijndael加密法可以支持更大范围的区块和密钥长度:AES的区块长度固定为128比特,密钥长度则可以是128、192、256比特;而Rijndael使用的密钥和区块长度可以是32位的整数倍,以128位为下限,256比特为上限。加密过程中使用的密钥是由Rijndael密钥生成方案产生。
在golang的源码设计中也可以证明这点,其blocksize设定为const,其值为16(byte),显然,标准库是并不允许使用者去修改这个值的,那么AES-128/192/256,其实是针对的密钥长度来说的。
另外,使用go AES库需要注意的是,go aes输入的密钥不满足16、24、32的要求,会直接返回错误,其并没有设计补全机制,需要自己实现。
数据块长度不足128bit,其同样也需要补全;很遗憾的是go依然没有帮助自动补全。补全方式有多种,一般常见的是zeropadding,pkcs5padding,pkcs7padding。
参考golang AES/ECB/PKCS5 加密解密 url-safe-base64
博客使用的是pkcs5padding,这里补上zeropadding。
1 2 3 4 5 6 7 8 9 10 11 12 |
func ZeroPadding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{0}, padding) return append(ciphertext, padtext...) } func ZeroUnPadding(origData []byte) []byte { return bytes.TrimFunc(origData, func(r rune) bool { return r == rune(0) }) } |
附上我写的填充key的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
func KeyPadding(key string) (keyByte []byte) { keyLen := len(key) switch { case keyLen < 16: keyByte = ZeroPadding([]byte(key), 16) case keyLen > 16 && keyLen < 24: keyByte = ZeroPadding([]byte(key), 24) case keyLen > 24 && keyLen < 32: keyByte = ZeroPadding([]byte(key), 32) case keyLen > 32: keyByte = []byte(key)[:32] default: keyByte = []byte(key) } return keyByte } |
1 |
bytes.Join(pBytes, []byte("")) |
第二个参数表示数组间用什么去间隔
binary.BigEndian.PutUint64
https://blog.csdn.net/coledaddy/article/details/71195528
例如strings.Index(str, “modules”),返回的是开始出现”modules”的位置,即”m”。
1 |
strings.Replace(strings.Trim(fmt.Sprint(byteArr),"[]"), " ", ",", -1) |
1 |
strconv.ParseUint(data, 16, 8) |
data:字符,16:进制,8:转换数据的大小,8则是8bit。