這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。在我的公司中,我們使用 Java 和 Go 作為開發平台,當然有時候這些項目彼此之間會進行互動。在這篇文章中,我想要介紹我們的關於在 Java 端進行訊息簽名並在 Go 服務程式中進行驗證的解決方案。 首先,我們聊一聊下面這個架構,我們的 Java 應用程式運行在雲上建立虛擬機器執行個體中,並且這個基礎鏡像執行個體包含了一個小的 Go 服務程式。這個服務程式是我們的組態管理系統的主入口,我們不希望有來自不可信的用戶端可以修改節點。在請求中包含簽名的雙向 SSL 看起來足以信任用戶端。但由於這兩個組件都是開源的,所以我們在二進位檔案中沒有任何“秘密”,因此我們選擇了RSA非對稱秘鑰對來產生和驗證簽名。Java 端擁有私密金鑰,Go 端擁有公開金鑰。Java 是一個古老的平台(個人有多年的Java經驗)因此,Java 有很多的庫,但是我開始使用Go。我沒有第六感,但我認為 Go 應該是支援協議的列表中最弱的。好訊息是, Go 有一個內建的 crypto/rsa 軟體包,壞訊息是,它只支援 PKCS#1。在研究期間,我發現了一個支援 PKCS#8 的第三方庫,我們不得不在這個計劃點上停下來並重點考察:1. 使用在較老的標準上建立的,經過良好測試的庫2. 使用在新的標準上的未知的庫PKCS#1 不是最新的標準版本,但是另一方面第三方庫看起來風險太大。所以我們還是選擇了第一個選項。在業務中,我們有這樣一個庫,它只有一個功能,即通過 VerifyPSS 函數去驗證 PSS(隨機簽名方案)簽名。```gofunc CheckSignature(rawSign string, pubPem []byte, data []byte) error {var err errorvar sign []bytevar pub interface{}sign, err = base64.StdEncoding.DecodeString(rawSign)if err != nil {return err}block, _ := pem.Decode(pubPem)if block == nil {return errors.New("Failed to decode public PEM")}pub, err = x509.ParsePKIXPublicKey(block.Bytes)if err != nil {return err}newHash := crypto.SHA256.New()newHash.Write(data)opts := rsa.PSSOptions{SaltLength: 20} // Java default salt lengtherr = rsa.VerifyPSS(pub.(*rsa.PublicKey), crypto.SHA256, newHash.Sum(nil), sign, &opts)return err}```在 Java 端,用戶端將額外的頭部放到請求中,該請求中包含了用私密金鑰產生的請求主體簽名。下一步就是找到簽名並調用之前實現的功能。```gofunc Wrap(handler func(w http.ResponseWriter, req *http.Request), signatureKey []byte) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {body := new(bytes.Buffer)defer r.Body.Close()ioutil.ReadAll(io.TeeReader(r.Body, body))r.Body = ioutil.NopCloser(body) // 我們讀取主體兩次, 我們必須封裝原始的 ReadClosersignature := strings.TrimSpace(r.Header.Get("signature"))if err := CheckSignature(signature, signatureKey, body.Bytes()); err != nil {// Error handlingw.WriteHeader(http.StatusNotAcceptable)w.Write([]byte("406 Not Acceptable"))return}http.HandlerFunc(handler).ServeHTTP(w, r) })}```最後實現 HTTP 處理常式並將校正程式一起封裝起來。```gofunc PostItHandler(w http.ResponseWriter, req *http.Request) {w.Write([]byte("ok"))}func RegisterHandler() {signature, _ := ioutil.ReadFile("/path/of/public/key")r := mux.NewRouter()r.Handle("/postit", Wrap(PostItHandler, signature)).Methods("POST")http.Handle("/", r)http.ListenAndServe("8080", nil)}```我寫了單元測試以確保校正是按照設計進行的。```gotype TestWriter struct {header http.Headerstatus intmessage string}func (w *TestWriter) Header() http.Header {return w.header}func (w *TestWriter) Write(b []byte) (int, error) {w.message = string(b)return len(b), nil}func (w *TestWriter) WriteHeader(s int) {w.status = s}func TestWrapAllValid(t *testing.T) {pk, _ := rsa.GenerateKey(rand.Reader, 1024)pubDer, _ := x509.MarshalPKIXPublicKey(&pk.PublicKey)pubPem := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Headers: nil, Bytes: pubDer})content := "body"newHash := crypto.SHA256.New()newHash.Write([]byte(content))opts := rsa.PSSOptions{SaltLength: 20}sign, _ := rsa.SignPSS(rand.Reader, pk, crypto.SHA256, newHash.Sum(nil), &opts)body := bytes.NewBufferString(content)req, _ := http.NewRequest("GET", "http://valami", body)req.Header.Add("signature", base64.StdEncoding.EncodeToString(sign))writer := new(TestWriter)writer.header = req.Headerhandler := Wrap(func(w http.ResponseWriter, req *http.Request) {}, pubPem)handler.ServeHTTP(writer, req)if writer.status != 0 {t.Errorf("writer.status 0 == %d", writer.status)}}```看起來我們已經完成了服務端的實現,現在讓我們編寫一些 Java 代碼吧。我研究了如何在 Java 中產生 PSS 簽名,並且我還發現了我們的一個依賴中已經包含了我們所需的功能。 [Bouncy Castle Crypto API](http://bouncycastle.org/), 在 Java 世界中一個非常出名的庫,應用它非常的簡單。```c// privateKeyPem - PEM 格式的私密金鑰// data - 簽名資料public static String generateSignature(String privateKeyPem, byte[] data) {try (PEMParser pEMParser = new PEMParser(new StringReader(clarifyPemKey(privateKeyPem)))) {PEMKeyPair pemKeyPair = (PEMKeyPair) pEMParser.readObject();KeyFactory factory = KeyFactory.getInstance("RSA");X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pemKeyPair.getPublicKeyInfo().getEncoded());PublicKey publicKey = factory.generatePublic(publicKeySpec);PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pemKeyPair.getPrivateKeyInfo().getEncoded());PrivateKey privateKey = factory.generatePrivate(privateKeySpec);KeyPair kp = new KeyPair(publicKey, privateKey);RSAPrivateKeySpec privKeySpec = factory.getKeySpec(kp.getPrivate(), RSAPrivateKeySpec.class);PSSSigner signer = new PSSSigner(new RSAEngine(), new SHA256Digest(), 20); //確保我們使用預設的 salt lenghtsigner.init(true, new RSAKeyParameters(true, privKeySpec.getModulus(), privKeySpec.getPrivateExponent()));signer.update(data, 0, data.length);byte[] signature = signer.generateSignature();return BaseEncoding.base64().encode(signature);} catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException | CryptoException e) {throw new RuntimeException(e);}}private static String clarifyPemKey(String rawPem) {return "-----BEGIN RSA PRIVATE KEY-----\n" + rawPem.replaceAll("-----(.*)-----|\n", "") + "\n-----END RSA PRIVATE KEY-----"; // PEMParser nem kedveli a sortöréseket}```就是這樣。。。ps: 我不為你介紹如何使用 Java 產生秘鑰對,因為你可以在網上找到很多關於它的知識。
via: https://mhmxs.blogspot.hk/2018/03/how-to-sign-messages-in-java-and-verify.html
作者:Richárd Kovács 譯者:fredvence 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
372 次點擊