使用RSA私钥加密消息(如在OpenSSL的RSA_private_encrypt中)

时间:2013-08-02 07:59:27

标签: go rsa pem

我正在尝试在Go中实现Chef API client,但是试图创建正确的请求标头RSA签名。根据{{​​3}}:

  

规范标头使用发送请求的客户端计算机使用的私钥进行签名,并使用Base64进行编码。

可以在OpenSSL::PKey::RSA.private_encrypt() documentation中找到以下对mixlib-authentication的红宝石电话,它使用gem codeprivate_encrypt()方法调用RSA_private_encrypt {{ 3}}

不幸的是,我在Go的标准库中找不到匹配函数; OpenSSL bindings看起来很接近,但它只实现了传统的加密方法:使用 public 密钥加密,使用私有密钥进行散列签名。 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新手,所以我不确定我是否可以潜入crypto/rsa模块资源。


找到openssl function,但crypto/rsa使用SignPKCS1v15显然是错误的(similar question}。

4 个答案:

答案 0 :(得分:5)

使用great help of the golang community,找到了解决方案:

原创代码由Alex发布在http://play.golang.org/p/jrqN2KnUEM(请参阅mailing list)。

我添加了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("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))
}

更新:我们可以在Go 1.3中获得此类签名的本机支持,请参阅the appropriate commit

答案 1 :(得分:2)

1.3开始,您可以使用SignPKCS1v15

轻松完成此操作
rsa.SignPKCS1v15(nil, priv, crypto.Hash(0), signedData) 

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

答案 2 :(得分:1)

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

最终,我在这里用代码来讨价还价: 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))

答案 3 :(得分:0)

欢迎来到openssl的乐趣......这是一个令人难以置信的命名功能。如果你在ruby代码中徘徊,那就是调用这个openssl函数

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

阅读文档,这实际上是使用private签署缓冲区 键而不是加密它。

说明

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

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