一般来说,加密主要用于消息的传递,并且传递的消息只有持有私钥的那个人能解密。

因此, 像 RSA 这种非对称密钥加密算法的常用场景是:

对于加密,公钥加密,私钥解密。

为什么不是 私钥加密,公钥解密呢? 因为使用场景是假定 公钥 都可以获取到,因而公钥解密这种操作也就与明文无异了。

有没有私钥加密的常用场景呢?当然是有,比如常见的签名操作。

明文消息 -> 进行 hash -> 对 hash 后的结果进行私钥加密, 这个加密后的结果就是签名了。

对签名进行验证实际上就是用公钥解密,然后把解密后的hash与原始消息的hash进行对比。

客户端:明文消息 -> 进行 hash -> 对签名使用公钥进行解密,并对比 hash 结果。

the story

早期版本的 Go 标准库 并不提供私钥加密 和 公钥解密功能。但是大家应该能猜想到,

其实要实现 私钥加密, 只需要在原来的签名函数上修改一个 hash 参数,使得这个 hash 啥也不做。

当然,由于明文并不能像 hash 一样是固定位数 的,因此,这里自然免不了要 padding.

Go 的 签名是采用的 PKCS #1 v1.5 padding 方式。

这个提交使得标准库直接支持 私钥加密了:

https://github.com/golang/go/commit/78c16c9b16dc9c64d1ddad6db5afaab12e87e8f2#diff-fd7abb32dfad4ffb52bb17669ce4a8a4114917e7d4a055107a9f1fc9381f1c94R233

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/

https://github.com/nodejs/help/issues/1093