- Published on
Golang RSA 如何私钥加密公钥解密
- Authors
- Name
- ttyS3
一般来说,加密主要用于消息的传递,并且传递的消息只有持有私钥的那个人能解密。
因此, 像 RSA 这种非对称密钥加密算法的常用场景是:
对于加密,公钥加密,私钥解密。
为什么不是 私钥加密,公钥解密呢? 因为使用场景是假定 公钥 都可以获取到,因而公钥解密这种操作也就与明文无异了。
有没有私钥加密的常用场景呢?当然是有,比如常见的签名操作。
明文消息 -> 进行 hash -> 对 hash 后的结果进行私钥加密, 这个加密后的结果就是签名了。
对签名进行验证实际上就是用公钥解密,然后把解密后的hash与原始消息的hash进行对比。
客户端:明文消息 -> 进行 hash -> 对签名使用公钥进行解密,并对比 hash 结果。
the story
早期版本的 Go 标准库 并不提供私钥加密 和 公钥解密功能。但是大家应该能猜想到,
其实要实现 私钥加密, 只需要在原来的签名函数上修改一个 hash 参数,使得这个 hash 啥也不做。
当然,由于明文并不能像 hash 一样是固定位数 的,因此,这里自然免不了要 padding.
Go 的 签名是采用的 PKCS #1 v1.5 padding 方式。
这个提交使得标准库直接支持 私钥加密了:
crypto/rsa: support unpadded signatures.
Usually when a message is signed it's first hashed because RSA has low limits on the size of messages that it can sign. However, some protocols sign short messages directly. This isn't a great idea because the messages that can be signed suddenly depend on the size of the RSA key, but several people on golang-nuts have requested support for this and it's very easy to do.
R=golang-codereviews, rsc CC=golang-codereviews golang.org/cl/44400043
the solution
Since go 1.3
, you can easily do this using SignPKCS1v15
rsa.SignPKCS1v15(nil, priv, crypto.Hash(0), plainData)
refer: https://groups.google.com/forum/#!topic/Golang-Nuts/Vocj33WNhJQ
所以,现在的 SignPKCS1v15
方法是这样的:
// SignPKCS1v15 calculates the signature of hashed using
// RSASSA-PKCS1-V1_5-SIGN from RSA PKCS #1 v1.5. Note that hashed must
// be the result of hashing the input message using the given hash
// function. If hash is zero, hashed is signed directly. This isn't
// advisable except for interoperability.
//
// If rand is not nil then RSA blinding will be used to avoid timing
// side-channel attacks.
//
// This function is deterministic. Thus, if the set of possible
// messages is small, an attacker may be able to build a map from
// messages to signatures and identify the signed messages. As ever,
// signatures provide authenticity, not confidentiality.
func SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
if err != nil {
return nil, err
}
tLen := len(prefix) + hashLen
k := priv.Size()
if k < tLen+11 {
return nil, ErrMessageTooLong
}
// EM = 0x00 || 0x01 || PS || 0x00 || T
em := make([]byte, k)
em[1] = 1
for i := 2; i < k-tLen-1; i++ {
em[i] = 0xff
}
copy(em[k-tLen:k-hashLen], prefix)
copy(em[k-hashLen:k], hashed)
m := new(big.Int).SetBytes(em)
c, err := decryptAndCheck(rand, priv, m)
if err != nil {
return nil, err
}
return c.FillBytes(em), nil
}
如果给 rand 传 nil 则每次生成的加密结果都是一样的。
如果给 crypto.Hash() 传0, 则表示不进行任何 hash
因此,私钥加密实际上只是一个特殊参数的调用:
encrypted, err := rsa.SignPKCS1v15(nil, priv, crypto.Hash(0), []byte(message))
troubleshoot
Golang 加密后的消息给 node.js 解密时,node.js 报错:
rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01
不要被错误信息迷惑了。这种错误跟 block type (padding) 没有什么关系。
错误的根本原因是,要么是加密的人私钥用错了,要么是解密的人公钥用错了。(没错,我在写某知名软件的注册机时,因为这个错误提示浪费了很多时间 ,直到后面意识到了问题的真正原因。 另外,我还要在这里吐槽一下某公司,你们的注册算法写得太烂了,有标准的签名算法不用,直接用的私钥加密/公钥解密这一套。还害我写这么一篇文章 。另外,都2021年了,你们还在用 RSA 1024)
refs
https://golang.org/src/crypto/rsa/pkcs1v15.go?s=8774:8874#L221
https://gist.github.com/kkHAIKE/be3b8d7ff8886457c6fdac2714d56fe1
https://github.com/buf1024/golib/blob/master/crypt/rsa.go
https://topic.alibabacloud.com/a/golang-private-key-encrypt-public-key-decrypt_1_38_30920103.html
node.js 类似 https://www.geeksforgeeks.org/node-js-crypto-publicdecrypt-method/