英文:
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 (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"os/exec"
)
var (
ErrInputSize = errors.New("input size too large")
ErrEncryption = errors.New("encryption error")
)
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 > 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 {
// 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() < 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 {
// Unblind.
m.Mul(m, ir)
m.Mod(m, priv.N)
}
enc = m.Bytes()
return
}
func main() {
// o is output from openssl
o, _ := exec.Command("openssl", "rsautl", "-sign", "-inkey", "t.key", "-in", "in.txt").Output()
// t.key is private keyfile
// in.txt is what to encode
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))
}
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 "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))
答案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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论