【问题标题】:Clojure (or Java) equivalent to Ruby's HMAC.hexdigestClojure(或 Java)等价于 Ruby 的 HMAC.hexdigest
【发布时间】:2015-10-22 03:05:39
【问题描述】:

使用 Github API 设置 webhook 时,我可以提供一个秘密。当 Github 向我发送 POST 请求时,使用了这个秘密 to encode one of the headers

此标头的值计算为正文的 HMAC 十六进制摘要,使用密钥作为密钥。

在手册页上,它们链接到this Ruby example

OpenSSL::HMAC.hexdigest(HMAC_DIGEST, secret, body)

我需要一种在 Clojure 中重现这一行的方法。

谷歌搜索,我找到了一些用于此目的的示例函数 (1,2,3),但它们都不起作用。我显然做错了什么,因为它们都提供相同的结果,但它与我从 Github 收到的标头不匹配。

例如,这是我设法编写的最简单的实现。

(ns website.test
  (:import javax.crypto.Mac
           javax.crypto.spec.SecretKeySpec
           org.apache.commons.codec.binary.Base64))

;;; Used in core.clj to verify that the payload matches the secret.x
(defn- hmac
  "Generates a Base64 HMAC with the supplied key on a string of data."
  [^String data]
  (let [algo "HmacSHA1"
        signing-key (SecretKeySpec. (.getBytes hook-secret) algo)
        mac (doto (Mac/getInstance algo) (.init signing-key))]
    (str "sha1="
         (String. (Base64/encodeBase64 (.doFinal mac (.getBytes data)))
                  "UTF-8"))))

用特定的hook-secret 集合在特定的body 上调用它,给我"sha1=VtNhKZDOHPU4COL2FSke2ArvtQE="。同时,我从 Github 得到的 header 是sha1=56d3612990ce1cf53808e2f615291ed80aefb501

显然,Github 是以十六进制打印的,但我所有将输出格式化为十六进制的尝试都导致了比那个长得多的字符串。我做错了什么?

【问题讨论】:

    标签: github clojure hmac hmacsha1


    【解决方案1】:

    试试这个,excerpted from my github repo:

    (ns crypto-tutorial.lib.hmac-test
      (:require [clojure.test :refer :all]
                [crypto-tutorial.lib.util :refer :all]
                [crypto-tutorial.lib.hmac :as hmac]))
    
    (defn sha-1-hmac-reference-impl [key bytes]
      (let [java-bytes (->java-bytes bytes)
            java-key (->java-bytes key)]
        (->>
          (doto (javax.crypto.Mac/getInstance "HmacSHA1")
            (.init (javax.crypto.spec.SecretKeySpec. java-key "HmacSHA1")))
          (#(.doFinal % java-bytes))
          (map (partial format "%02x"))
          (apply str))))
    

    【讨论】:

    • 啊!我的尝试之一是完全相同的代码,但我使用了%x 而不是%02x。谢谢
    【解决方案2】:

    您正在对摘要进行 Base64 编码,而您需要将其转换为十六进制。您可以按照@RedDeckWins 推荐的using map 执行此操作,但使用Java 库可能会更有效。 This answer 对类似问题使用 org.apache.commons.codec.binary.Hex 进行编码。

    【讨论】:

    • 两个答案都有效。接受这个,因为它是我最终使用的那个。函数是org.apache.commons.codec.binary.Hex/encodeHexString
    【解决方案3】:

    为了将来参考,这里有一个完整的环中间件,用于根据本文中的答案和引用的线程验证 Clojure 中的 GitHub webhook 调用:

    https://gist.github.com/ska2342/4567b02531ff611db6a1208ebd4316e6#file-gh-validation-clj

    编辑

    链接代码中最重要的部分在此处重复(正确地)在 cmets 中要求。

    ;; (c) 2016 Stefan Kamphausen
    ;; Released under the Eclipse Public License 
    (def ^:const ^:private signing-algorithm "HmacSHA1")
    
    (defn- get-signing-key* [secret]
      (SecretKeySpec. (.getBytes secret) signing-algorithm))
    (def ^:private get-signing-key (memoize get-signing-key*))
    
    (defn- get-mac* [signing-key]
      (doto (Mac/getInstance signing-algorithm)
        (.init signing-key)))
    (def ^:private get-mac (memoize get-mac*))
    
    (defn hmac [^String s signature secret]
      (let [mac (get-mac (get-signing-key secret))]
        ;; MUST use .doFinal which resets mac so that it can be
        ;; reused!
        (str "sha1="
             (Hex/encodeHexString (.doFinal mac (.getBytes s))))))
    
    (defn- validate-string [^String s signature secret]
      (let [calculated (hmac s signature secret)]
        (= signature calculated)))
    
    ;; Warn: Body-stream can only be slurped once. Possible
    ;; conflict with other ring middleware
    (defn body-as-string [request]
      (let [body (:body request)]
        (if (string? body)
          body
          (slurp body))))
    
    (defn- valid-github? [secrets request]
      (let [body (body-as-string request)
            signature (get-in request [:headers "x-hub-signature"])]
        (log/debug "Found signature" signature)
        (cond
          ;; only care about post
          (not (= :post (:request-method request)))
          "no-validation-not-a-post"
    
          ;; No secrets defined, no need to validate
          (not (seq secrets))
          "no-validation-no-secrets"
    
          ;; we have no signature but secrets are defined -> fail
          (and (not signature) (seq secrets))
          false
    
          ;; must validate this content
          :else
          (some (partial validate-string body signature) secrets))))
    
    (def default-invalid-response
      {:status  400
       :headers {"Content-Type" "text/plain"}
       :body    "Invalid X-Hub-Signature in request."})
    
    (defn wrap-github-validation
      {:arglists '([handler] [handler options])}
      [handler & [{:keys [secret secrets invalid-response]
                   :or   {secret           nil
                          secrets          nil
                          invalid-response default-invalid-response}}]]
      (let [secs (if secret [secret] secrets)]
        (fn [request]
          (if-let [v (valid-github? secs request)]
            (do
              (log/debug "Request validation OK:" v)
              (handler (assoc request
                              :validation {:valid true
                                           :validation v}
                              ;; update body which must be an
                              ;; InputStream
                              :body (io/input-stream (.getBytes body)))))
    
            (do
              (log/warn "Request invalid! Returning" invalid-response)
    invalid-response)))))
    

    【讨论】:

      猜你喜欢
      • 2021-09-05
      • 1970-01-01
      • 2012-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-18
      • 2019-03-14
      • 2019-08-15
      相关资源
      最近更新 更多