donqo88682 2013-08-02 07:59
浏览 1025
已采纳

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

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).

  • 写回答

4条回答 默认 最新

  • douzheng5717 2013-10-27 17:24
    关注

    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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(3条)

报告相同问题?

悬赏问题

  • ¥15 用土力学知识进行土坡稳定性分析与挡土墙设计
  • ¥15 帮我写一个c++工程
  • ¥30 Eclipse官网打不开,官网首页进不去,显示无法访问此页面,求解决方法
  • ¥15 关于smbclient 库的使用
  • ¥15 微信小程序协议怎么写
  • ¥15 c语言怎么用printf(“\b \b”)与getch()实现黑框里写入与删除?
  • ¥20 怎么用dlib库的算法识别小麦病虫害
  • ¥15 华为ensp模拟器中S5700交换机在配置过程中老是反复重启
  • ¥15 java写代码遇到问题,求帮助
  • ¥15 uniapp uview http 如何实现统一的请求异常信息提示?