Skip to content

Commit

Permalink
v0.3.1 Add MD5 and SHA2 family hashing
Browse files Browse the repository at this point in the history
  • Loading branch information
skinkade committed Nov 9, 2021
1 parent 28265cd commit 3ab2b3b
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Change Log
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).

## [0.3.1]
### Added
MD5 and SHA2 family hashing

## [0.3.0]
### Added
RSA support
Expand Down Expand Up @@ -37,6 +41,7 @@ JVM and ClojureScript support for cryptographically-random:
- collection samples
- passwords/passphrases

[0.3.1]: https://github.com/skinkade/uniformity/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/skinkade/uniformity/compare/v0.2.2...v0.3.0
[0.2.2]: https://github.com/skinkade/uniformity/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/skinkade/uniformity/compare/v0.2.0...v0.2.1
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ Output can be chosen to be a Clojure map, JSON, or msgpack.
[See documentation](doc/random.md).


### Hash
`uniformity.hash` provides MD5 and SHA2 family hashing.
[See examples](doc/hash.md).


### Util
`uniformity.util`, at the moment, is largely for:
- Encoding / decoding Base64 / Hex
Expand Down
33 changes: 33 additions & 0 deletions doc/hash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Hashing

`uniformity.hash` provides MD5, SHA1, SHA256, SHA384, and SHA512 hashes
with binary, hex, base64, or URL-safe base64 encoding.

It also provides a `hashes-equal?` function to check for hash equality.
Note that encoding of its two inputs must be the same (i.e. both hex
or both binary format).

```clojure
clj꞉uniformity.hash꞉> (def test-bytes (byte-array [1 2 3 4]))
#'uniformity.hash/test-bytes

;; defaults to hex encoding
clj꞉uniformity.hash꞉> (md5 test-bytes)
"08d6c05a21512a79a1dfeb9d2a8f262f"

;; or you can specify format
clj꞉uniformity.hash꞉> (sha1 test-bytes :format :hex)
"12dada1fff4d4787ade3333147202c3b443e376f"

clj꞉uniformity.hash꞉> (sha256 test-bytes :format :bytes)
#object["[B" 0x528b135d "[B@528b135d"]

clj꞉uniformity.hash꞉> (sha384 test-bytes :format :base64)
"WmZ9YkMKjCU+uuQzMzkE3G4dQdzcR5cEdzFZuQWjrYLSutd2LYGjZsxG+7LiMn9c"

clj꞉uniformity.hash꞉> (sha512 test-bytes :format :base64-urlsafe)
"p8l22xcjrbQSdBeNyC6bd3lBqyAcad5h0PK8bSejWY9ZT6dI5Q2I08K_HiwucsPP73jDxtSvqQOR9-M6urykjg"

;; equality comparison
clj꞉uniformity.hash꞉> (hashes-equal? (md5 test-bytes) "08d6c05a21512a79a1dfeb9d2a8f262f")
true
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject io.github.skinkade/uniformity "0.3.0"
(defproject io.github.skinkade/uniformity "0.3.1"
:description "A Clojure(Script) library for easy-to-use cryptography"
:url "https://github.com/skinkade/uniformity"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
Expand Down
86 changes: 86 additions & 0 deletions src/uniformity/hash.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
(ns uniformity.hash
(:require #?(:clj [uniformity.internals.java.hash :as internals]
:cljs [uniformity.internals.js.hash :as internals])
[uniformity.util :as util]))

(defn ^:private hash-format [hash format]
(case format
:bytes hash
:hex (util/hex-encode hash)
:base64 (util/base64-encode hash)
:base64-urlsafe (util/base64-encode-urlsafe hash)
(throw (ex-info "Unidentified hash format"
{:format format}))))

(defn md5
"Produces a MD5 hash of input bytes.
Optional :format parameter determines output:
:bytes
:hex (default)
:base64
:base64-urlsafe"
[bytes
& {:keys [format] :or {format :hex}}]
(hash-format (internals/md5 bytes)
format))

(defn sha1
"Produces a SHA-1 hash of input bytes.
Optional :format parameter determines output:
:bytes
:hex (default)
:base64
:base64-urlsafe"
[bytes
& {:keys [format] :or {format :hex}}]
(hash-format (internals/sha1 bytes)
format))

(defn sha256
"Produces a SHA-256 hash of input bytes.
Optional :format parameter determines output:
:bytes
:hex (default)
:base64
:base64-urlsafe"
[bytes
& {:keys [format] :or {format :hex}}]
(hash-format (internals/sha256 bytes)
format))

(defn sha384
"Produces a SHA-384 hash of input bytes.
Optional :format parameter determines output:
:bytes
:hex (default)
:base64
:base64-urlsafe"
[bytes
& {:keys [format] :or {format :hex}}]
(hash-format (internals/sha384 bytes)
format))

(defn sha512
"Produces a SHA-512 hash of input bytes.
Optional :format parameter determines output:
:bytes
:hex (default)
:base64
:base64-urlsafe"
[bytes
& {:keys [format] :or {format :hex}}]
(hash-format (internals/sha512 bytes)
format))

(defn hashes-equal?
"Compares equality of two hashes by comparing every element
to inhibit timing attacks."
[hash1 hash2]
(if (not= (count hash1) (count hash2))
false
(reduce (fn [equals idx]
(and equals
(= (get hash1 idx)
(get hash2 idx))))
true
(range (count hash1)))))
30 changes: 30 additions & 0 deletions src/uniformity/internals/java/hash.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
(ns uniformity.internals.java.hash
;; (:require [clojure.core.async :refer [go]]
;; [async-error.core :refer [go-try <?]])
(:import [java.security MessageDigest]))

(defn ^:private java-hash [bytes type]
(let [hasher (MessageDigest/getInstance type)]
(.update hasher bytes)
(.digest hasher)))

(defn md5 [^bytes bytes]
(java-hash bytes "MD5"))

(defn sha1 [^bytes bytes]
(java-hash bytes "SHA-1"))

(defn sha256 [^bytes bytes]
(java-hash bytes "SHA-256"))

(defn sha384 [^bytes bytes]
(java-hash bytes "SHA-384"))

(defn sha512 [^bytes bytes]
(java-hash bytes "SHA-512"))

(comment
(-> [1 2 3 4]
byte-array
md5
uniformity.internals.java.util/hex-encode))
31 changes: 31 additions & 0 deletions src/uniformity/internals/js/hash.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
(ns uniformity.internals.js.hash
(:require [goog.crypt.Md5]
[goog.crypt.Sha1]
[goog.crypt.Sha256]
[goog.crypt.Sha384]
[goog.crypt.Sha512]))

(defn md5 [bytes]
(let [hasher (goog.crypt.Md5.)]
(.update hasher bytes)
(js/Uint8Array. (.digest hasher))))

(defn sha1 [bytes]
(let [hasher (goog.crypt.Sha1.)]
(.update hasher bytes)
(js/Uint8Array. (.digest hasher))))

(defn sha256 [bytes]
(let [hasher (goog.crypt.Sha256.)]
(.update hasher bytes)
(js/Uint8Array. (.digest hasher))))

(defn sha384 [bytes]
(let [hasher (goog.crypt.Sha384.)]
(.update hasher bytes)
(js/Uint8Array. (.digest hasher))))

(defn sha512 [bytes]
(let [hasher (goog.crypt.Sha512.)]
(.update hasher bytes)
(js/Uint8Array. (.digest hasher))))
47 changes: 47 additions & 0 deletions test/uniformity/hash_test.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
(ns uniformity.hash-test
(:require [uniformity.internals.validation :refer [compat-byte-array]]
[uniformity.hash :as hash]
#?(:clj [clojure.test :refer [deftest is]]
:cljs [cljs.test :refer-macros [deftest is]])))

(def reference-bytes
(compat-byte-array [1 2 3 4]))

(def md5-reference-hash
"08d6c05a21512a79a1dfeb9d2a8f262f")

(deftest md5-test
(is (hash/hashes-equal? (hash/md5 reference-bytes)
md5-reference-hash)))


(def sha1-reference-hash
"12dada1fff4d4787ade3333147202c3b443e376f")

(deftest sha1-test
(is (hash/hashes-equal? (hash/sha1 reference-bytes)
sha1-reference-hash)))


(def sha256-reference-hash
"9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")

(deftest sha256-test
(is (hash/hashes-equal? (hash/sha256 reference-bytes)
sha256-reference-hash)))


(def sha384-reference-hash
"5a667d62430a8c253ebae433333904dc6e1d41dcdc479704773159b905a3ad82d2bad7762d81a366cc46fbb2e2327f5c")

(deftest sha384-test
(is (hash/hashes-equal? (hash/sha384 reference-bytes)
sha384-reference-hash)))


(def sha512-reference-hash
"a7c976db1723adb41274178dc82e9b777941ab201c69de61d0f2bc6d27a3598f594fa748e50d88d3c2bf1e2c2e72c3cfef78c3c6d4afa90391f7e33ababca48e")

(deftest sha512-test
(is (hash/hashes-equal? (hash/sha512 reference-bytes)
sha512-reference-hash)))

0 comments on commit 3ab2b3b

Please sign in to comment.