2013-08-02 72 views
8

我試圖在Go中實現Chef API client,但試圖創建正確的請求標頭RSA簽名。根據documentation使用RSA私鑰加密郵件(如在OpenSSL的RSA_private_encrypt中)

規範標頭使用發送請求的客戶機使用的私鑰進行簽名,並且使用Base64進行編碼。

OpenSSL::PKey::RSA.private_encrypt()以下紅寶石調用可以在mixlib-authenticationgem code可以發現,它採用OpenSSL bindingsprivate_encrypt()方法調用RSA_private_encryptopenssl function

不幸的是,我在Go的標準庫中找不到匹配函數; crypto/rsa看起來很接近,但它只實現傳統的加密方法:加密公鑰密鑰,加密簽名私鑰密鑰。 OpenSSL的RSA_private_encrypt做了相反的事情:它用私鑰加密(小)消息(類似於從消息散列創建簽名)。

這個「簽約」,也可以用這個命令來實現:

openssl rsautl -sign -inkey path/to/private/key.pem \ 
    -in file/to/encrypt -out encrypted/output 

是否有任何本地轉到庫來實現相同的結果,OpenSSL的RSA_private_encrypt,或者唯一的辦法是使用CGO從調用這個函數OpenSSL庫?也許我錯過了一些東西。我的想法是實現客戶端沒有任何不依賴。

我是一個Go新手,所以我不確定我可以潛入crypto/rsa模塊來源。


找到了similar question,但the answer使用SignPKCS1v15顯然是錯誤的(這function encrypts message's hash, not the message itself)。

+0

這廚師API文檔是可悲的還不清楚,但我認爲你應該簽署頭,這意味着SignPKCS1v15可能是你想要的。 –

+0

@GregS,遺憾的是,情況並非如此,我使用[mixlib-authentication sources](https://github.com/opscode/mixlib-authentication/blob/master/lib/mixlib/authentication/signedheaderauth)進行了雙重檢查。 RB#L94)。 – artyom

+0

我想你可能是對的。我懷疑它使用的是PKCS1 v15 * block type 1 * padding,但是這對你沒有任何幫助。抱歉。 –

回答

5

隨着great help of the golang community,發現該溶液:

原始代碼張貼在http://play.golang.org/p/jrqN2KnUEM由Alex(見mailing list)。

如第8章規定的rfc2313我已經添加輸入塊大小檢查: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

+0

非常感謝您的解決方案。它拯救了我的一天! – holys

+0

實際提交的鏈接似乎被破壞,我也需要這種加密來簽署HTTP請求。這個加密的確切名稱是什麼,因爲你有PKCS命名等? –

+0

傑裏,我已經更新鏈接以提交給github,你需要的函數是'rsa.SignPKCS1v15' – artyom

0

歡迎來到openssl的歡樂......這是一個令人難以置信的命名功能。如果您在Ruby代碼閒逛它調用這個函數的OpenSSL

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

閱讀的文檔,其實這是簽署緩衝與私營 密鑰和加密不是它。

描述

這些函數處理低級別的RSA簽名。

RSA_private_encrypt()使用私鑰rsa從(通常是帶有算法標識符的消息摘要)開始標記flen字節,並將簽名存儲到to中。必須指向RSA_size(rsa)字節的內存。

+0

儘管有文檔,但實際上這是一個簽名操作:您可以對結果執行解密步驟並獲取原始消息。我假設它是用來加密簽名的,這個結論由括號中的註釋支持(參見你引用的描述):*通常是帶有算法標識符的消息摘要*。 – artyom

+0

在之前的消息中,我的意思是'RSA_private_encrypt'是一個* encryption *操作,不是簽名。 – artyom

+0

@artyom問題是,如果你在** public **密鑰的任何人都可以解密的東西中「加密」,那麼你實際上並不**加密任何東西,因爲你隱藏了它。很少有很好的理由使用這個調用來實現任何安全的事情。就這一點而言,「私密加密」確實更接近簽名(你會「加密」一個摘要的地方)。 – Bruno