Question

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

Était-ce utile?

La solution

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.

Autres conseils

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

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

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top