使用RSA私钥加密消息(类似于OpenSSL的RSA_private_encrypt)

huangapple go评论67阅读模式
英文:

Encrypt message with RSA private key (as in OpenSSL's RSA_private_encrypt)

问题

我正在尝试在Go中实现Chef API客户端,但在尝试创建正确的请求头RSA签名时遇到了困难。根据文档

> 规范头是使用发送请求的客户端机器使用的私钥进行签名的,并且还使用Base64进行编码。

mixlib-authentication gem代码中可以找到以下对OpenSSL::PKey::RSA.private_encrypt()的调用,它使用OpenSSL绑定private_encrypt()方法调用RSA_private_encrypt openssl函数

不幸的是,我在Go的标准库中找不到匹配的函数;crypto/rsa看起来很接近,但它只实现了传统的加密方法:使用公钥进行加密,使用私钥进行哈希签名。OpenSSL的RSA_private_encrypt则相反:它使用私钥对(小)消息进行加密(类似于从消息哈希创建签名)。

这种“签名”也可以通过以下命令实现:

openssl rsautl -sign -inkey path/to/private/key.pem \
    -in file/to/encrypt -out encrypted/output

是否有任何原生的Go库可以实现与OpenSSL的RSA_private_encrypt相同的结果,或者唯一的方法是使用Cgo从OpenSSL库中调用此函数?也许我漏掉了什么。我的想法是在没有任何非Go依赖的情况下实现客户端。

我是Go的新手,所以我不确定是否可以深入研究crypto/rsa模块的源代码。


找到了类似的问题,但是使用SignPKCS1v15答案显然是错误的(此函数加密消息的哈希,而不是消息本身)。

英文:

I'm trying to implement Chef API client in Go, but stuck trying to create correct request header RSA signature. According to documentation:

> A canonical header is signed with the private key used by the client machine from which the request is sent, and is also encoded using Base64.

The following ruby call to OpenSSL::PKey::RSA.private_encrypt() can be found in mixlib-authentication gem code, it uses OpenSSL bindings, private_encrypt() method calls RSA_private_encrypt openssl function.

Unfortunately, I cannot find matching function in Go's standard library; crypto/rsa looks close, but it only implements conventional cryptography methods: encryption with public key, hash signing with private key. OpenSSL's RSA_private_encrypt does the opposite: it encrypts (small) message with private key (akin to creating a signature from message hash).

This "signing" can also be achieved with this command:

openssl rsautl -sign -inkey path/to/private/key.pem \
    -in file/to/encrypt -out encrypted/output

Are there any native Go libraries to achieve the same result as OpenSSL's RSA_private_encrypt, or the only way is using Cgo to call this function from OpenSSL library? Maybe I'm missing something. My idea was implementing the client without any non-go dependencies.

I'm a Go newbie, so I'm not sure I can dive into crypto/rsa module sources.


Found the similar question, but the answer to use SignPKCS1v15 is obviously wrong (this function encrypts message's hash, not the message itself).

答案1

得分: 5

通过golang社区的大力帮助,找到了解决方案:

原始代码发布在http://play.golang.org/p/jrqN2KnUEM,由Alex提供(参见邮件列表)。

我已经按照rfc2313第8节的规定添加了输入块大小检查:http://play.golang.org/p/dGTl9siO8E

以下是代码:

package main

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
	"math/big"
	"os/exec"
)

var (
	ErrInputSize  = errors.New("输入大小过大")
	ErrEncryption = errors.New("加密错误")
)

func PrivateEncrypt(priv *rsa.PrivateKey, data []byte) (enc []byte, err error) {

	k := (priv.N.BitLen() + 7) / 8
	tLen := len(data)
	// rfc2313,第8节:
	// 数据D的长度不得超过k-11个八位字节
	if tLen > k-11 {
		err = ErrInputSize
		return
	}
	em := make([]byte, k)
	em[1] = 1
	for i := 2; i < k-tLen-1; i++ {
		em[i] = 0xff
	}
	copy(em[k-tLen:k], data)
	c := new(big.Int).SetBytes(em)
	if c.Cmp(priv.N) > 0 {
		err = ErrEncryption
		return
	}
	var m *big.Int
	var ir *big.Int
	if priv.Precomputed.Dp == nil {
		m = new(big.Int).Exp(c, priv.D, priv.N)
	} else {
		// 我们已经预先计算了CRT所需的值。
		m = new(big.Int).Exp(c, priv.Precomputed.Dp, priv.Primes[0])
		m2 := new(big.Int).Exp(c, priv.Precomputed.Dq, priv.Primes[1])
		m.Sub(m, m2)
		if m.Sign() < 0 {
			m.Add(m, priv.Primes[0])
		}
		m.Mul(m, priv.Precomputed.Qinv)
		m.Mod(m, priv.Primes[0])
		m.Mul(m, priv.Primes[1])
		m.Add(m, m2)

		for i, values := range priv.Precomputed.CRTValues {
			prime := priv.Primes[2+i]
			m2.Exp(c, values.Exp, prime)
			m2.Sub(m2, m)
			m2.Mul(m2, values.Coeff)
			m2.Mod(m2, prime)
			if m2.Sign() < 0 {
				m2.Add(m2, prime)
			}
			m2.Mul(m2, values.R)
			m.Add(m, m2)
		}
	}

	if ir != nil {
		// 解除盲化。
		m.Mul(m, ir)
		m.Mod(m, priv.N)
	}
	enc = m.Bytes()
	return
}

func main() {
	// o是openssl的输出
	o, _ := exec.Command("openssl", "rsautl", "-sign", "-inkey", "t.key", "-in", "in.txt").Output()

	// t.key是私钥文件
	// in.txt是要编码的内容
	kt, _ := ioutil.ReadFile("t.key")
	e, _ := ioutil.ReadFile("in.txt")
	block, _ := pem.Decode(kt)
	privkey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
	encData, _ := PrivateEncrypt(privkey, e)
	fmt.Println(encData)
	fmt.Println(o)
	fmt.Println(string(o) == string(encData))
}

**更新:**我们可以期望在Go 1.3中有对这种签名的本地支持,参见适当的提交

英文:

With great help of the golang community, the solution was found:

Original code posted at http://play.golang.org/p/jrqN2KnUEM by Alex (see mailing list).

I've added input block size check as specified in Section 8 of rfc2313: http://play.golang.org/p/dGTl9siO8E

Here's the code:

package main

import (
	&quot;crypto/rsa&quot;
	&quot;crypto/x509&quot;
	&quot;encoding/pem&quot;
	&quot;errors&quot;
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;math/big&quot;
	&quot;os/exec&quot;
)

var (
	ErrInputSize  = errors.New(&quot;input size too large&quot;)
	ErrEncryption = errors.New(&quot;encryption error&quot;)
)

func PrivateEncrypt(priv *rsa.PrivateKey, data []byte) (enc []byte, err error) {

	k := (priv.N.BitLen() + 7) / 8
	tLen := len(data)
	// rfc2313, section 8:
	// The length of the data D shall not be more than k-11 octets
	if tLen &gt; k-11 {
		err = ErrInputSize
		return
	}
	em := make([]byte, k)
	em[1] = 1
	for i := 2; i &lt; k-tLen-1; i++ {
		em[i] = 0xff
	}
	copy(em[k-tLen:k], data)
	c := new(big.Int).SetBytes(em)
	if c.Cmp(priv.N) &gt; 0 {
		err = ErrEncryption
		return
	}
	var m *big.Int
	var ir *big.Int
	if priv.Precomputed.Dp == nil {
		m = new(big.Int).Exp(c, priv.D, priv.N)
	} else {
		// We have the precalculated values needed for the CRT.
		m = new(big.Int).Exp(c, priv.Precomputed.Dp, priv.Primes[0])
		m2 := new(big.Int).Exp(c, priv.Precomputed.Dq, priv.Primes[1])
		m.Sub(m, m2)
		if m.Sign() &lt; 0 {
			m.Add(m, priv.Primes[0])
		}
		m.Mul(m, priv.Precomputed.Qinv)
		m.Mod(m, priv.Primes[0])
		m.Mul(m, priv.Primes[1])
		m.Add(m, m2)

		for i, values := range priv.Precomputed.CRTValues {
			prime := priv.Primes[2+i]
			m2.Exp(c, values.Exp, prime)
			m2.Sub(m2, m)
			m2.Mul(m2, values.Coeff)
			m2.Mod(m2, prime)
			if m2.Sign() &lt; 0 {
				m2.Add(m2, prime)
			}
			m2.Mul(m2, values.R)
			m.Add(m, m2)
		}
	}

	if ir != nil {
		// Unblind.
		m.Mul(m, ir)
		m.Mod(m, priv.N)
	}
	enc = m.Bytes()
	return
}

func main() {
	// o is output from openssl
	o, _ := exec.Command(&quot;openssl&quot;, &quot;rsautl&quot;, &quot;-sign&quot;, &quot;-inkey&quot;, &quot;t.key&quot;, &quot;-in&quot;, &quot;in.txt&quot;).Output()

	// t.key is private keyfile
	// in.txt is what to encode
	kt, _ := ioutil.ReadFile(&quot;t.key&quot;)
	e, _ := ioutil.ReadFile(&quot;in.txt&quot;)
	block, _ := pem.Decode(kt)
	privkey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
	encData, _ := PrivateEncrypt(privkey, e)
	fmt.Println(encData)
	fmt.Println(o)
	fmt.Println(string(o) == string(encData))
}

Update: we can expect to have a native support for this kind of signing in Go 1.3, see the appropriate commit.

答案2

得分: 3

自从go 1.3版本以后,你可以使用SignPKCS1v15函数来轻松实现这个功能。

rsa.SignPKCS1v15(nil, priv, crypto.Hash(0), signedData) 

参考链接:https://groups.google.com/forum/#!topic/Golang-Nuts/Vocj33WNhJQ

英文:

Since go 1.3, you can easily do this using SignPKCS1v15

rsa.SignPKCS1v15(nil, priv, crypto.Hash(0), signedData) 

refer: https://groups.google.com/forum/#!topic/Golang-Nuts/Vocj33WNhJQ

答案3

得分: 1

我在这个问题上卡了一段时间。

最终,我用这里的代码解决了这个问题:
https://github.com/bitmartexchange/bitmart-go-api/blob/master/bm_client.go

// 使用RSA和PKCS 1.5作为填充算法对密钥进行签名
// 结果应该与"openssl rsautl -sign -inkey YOUR_RSA_PRIVATE_KEY -in YOUR_PLAIN_TEXT"完全相同
signer, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey.(*rsa.PrivateKey), crypto.Hash(0), []byte(message))

英文:

I stuck on this question for a while.

Eventually, I soleved that with the code here:
https://github.com/bitmartexchange/bitmart-go-api/blob/master/bm_client.go

// Sign secret with rsa with PKCS 1.5 as the padding algorithm
// The result should be exactly same as &quot;openssl rsautl -sign -inkey &quot;YOUR_RSA_PRIVATE_KEY&quot; -in &quot;YOUR_PLAIN_TEXT&quot;&quot;
signer, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey.(*rsa.PrivateKey), crypto.Hash(0), []byte(message))

答案4

得分: 0

欢迎来到openssl的乐趣...这是一个非常糟糕命名的函数。如果你在Ruby代码中查找,它调用了这个openssl函数。

阅读文档,实际上这是使用私钥对缓冲区进行签名而不是加密。

描述:

这些函数在低级别处理RSA签名。

RSA_private_encrypt()使用私钥rsa对from处的flen字节(通常是消息摘要与算法标识符)进行签名,并将签名存储在to中。to必须指向RSA_size(rsa)字节的内存。

英文:

Welcome to the joys of openssl... That is an incredibly poorly named function. If you poke around in the ruby code it's calling this openssl function

http://www.openssl.org/docs/crypto/RSA_private_encrypt.html

Reading the documentation, this is actually signing the buffer with the private
key and not encrypting it.

DESCRIPTION

These functions handle RSA signatures at a low level.

RSA_private_encrypt() signs the flen bytes at from (usually a message digest with an algorithm identifier) using the private key rsa and stores the signature in to. to must point to RSA_size(rsa) bytes of memory.

huangapple
  • 本文由 发表于 2013年8月2日 15:59:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/18011708.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定