【问题标题】:Encrypt message with RSA private key (as in OpenSSL's RSA_private_encrypt)使用 RSA 私钥加密消息(如在 OpenSSL 的 RSA_private_encrypt 中)
【发布时间】:2013-08-02 07:59:27
【问题描述】:

我正在尝试在 Go 中实现 Chef API client,但一直在尝试创建正确的请求标头 RSA 签名。根据documentation

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

以下对OpenSSL::PKey::RSA.private_encrypt()的ruby调用可以在mixlib-authenticationgem code中找到,它使用OpenSSL bindingsprivate_encrypt()方法调用RSA_private_encryptopenssl function

很遗憾,我在 Go 的标准库中找不到匹配的函数; crypto/rsa 看起来很接近,但它只实现了传统的加密方法:使用 public 密钥加密,使用 private 密钥进行哈希签名。 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 模块源代码。


找到了similar question,但是the answer使用SignPKCS1v15显然是错误的(这个function encrypts message's hash, not the message itself)。

【问题讨论】:

  • Chef API 文档非常不清楚,但我认为您应该签署标头,这意味着 SignPKCS1v15 可能是您想要的。
  • @GregS,可悲的是,事实并非如此,我仔细检查了mixlib-authentication sources
  • 我认为您可能是正确的。我怀疑它使用的是 PKCS1 v15 block type 1 填充,但这对你没有帮助。对不起。

标签: go rsa pem


【解决方案1】:

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

【讨论】:

  • 实际提交的链接似乎已损坏,我也需要这种加密来签署 HTTP 请求。由于您有 PKCS 命名等,因此该加密的确切名称是什么?
  • Jerry,我更新了提交到github的链接,你需要的函数是rsa.SignPKCS1v15
【解决方案2】:

既然去1.3,你可以使用SignPKCS1v15轻松做到这一点

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

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

【讨论】:

    【解决方案3】:

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

    最后,我用这里的代码解决了这个问题: 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】:

      欢迎来到 openssl 的乐趣...这是一个非常糟糕的命名函数。如果你在 ruby​​ 代码中四处寻找,它会调用这个 openssl 函数

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

      阅读文档,这实际上是用私有签名缓冲区 密钥而不是加密它。

      描述

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

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

      【讨论】:

      • 尽管有文档,但这实际上是一个签名操作:您可以对结果执行解密步骤并取回原始消息。我假设它是为了加密签名,这个结论由括号中的注释支持(参见你引用的描述):通常是带有算法标识符的消息摘要
      • 在上一条消息中,我的意思是RSA_private_encrypt 是一个加密操作,而不是签名。
      • @artyom 问题是,如果你在任何拥有 public 密钥的人都可以解密的东西中“加密”,那么你实际上并没有加密 b> 任何东西,从某种意义上说,你正在隐藏它。使用此调用来实现任何安全的理由很少。从这个意义上说,“私人加密”确实更接近于签名(您将“加密”摘要)。
      猜你喜欢
      • 2020-02-28
      • 2018-02-13
      • 1970-01-01
      • 2018-02-13
      • 2021-01-08
      • 2011-03-01
      • 1970-01-01
      • 2011-03-20
      • 2011-09-08
      相关资源
      最近更新 更多