how to get SHA1 of a string?

1,371 views
Skip to first unread message

larry google groups

unread,
Mar 4, 2013, 10:47:48 AM3/4/13
to Clojure

I have been having problems making an API call to Omniture. I have
exchanged a dozen emails with a developer at Omniture, and he gave me
the impression that I was constructing my security codes incorrectly.
So now I am confronting my ignorance over how Java handles certain
conversions.

The developer at Omniture sent me this explanation in an email:

" The security digest is formed from a sha1 hash of the following
string concatenation:
digest = sha1( Binary Nonce + Created Time String + API Secret Hex
String (32 bytes) ) "

I have been struggling with this for several days and I have tried at
least (literally) 200 variations on this bit of code:

(let [username (get-in @um/interactions [:omniture-api-
credentials :username])
secret (get-in @um/interactions [:omniture-api-
credentials :shared-secret])
nonce (DigestUtils/md5Hex (random-string 32))
nonce-encoded-base64 (Base64/encodeBase64 (.getBytes nonce))
date-formatter (new SimpleDateFormat "yyyy-MM-
dd'T'HH:mm:ss'Z'")
created (.format date-formatter (new Date))
digest-as-string (apply str (.getBytes nonce) created secret)
digest (.digest (java.security.MessageDigest/getInstance "sha1")
digest-as-string)
header (apply str " UsernameToken Username=\"" username "\"
PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-base64 "\"
Created=\"" created "\"")]
header)


This version gives me:

"Exception in the main function: " #<ClassCastException
java.lang.ClassCastException: java.lang.String cannot be cast to [B>

For a long time I was using this for the last 3 lines:

digest-as-string (apply str nonce created secret)
digest (.digest (java.security.MessageDigest/getInstance "sha1")
(.getByes digest-as-string))
header (apply str " UsernameToken Username=\"" username "\"
PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-base64 "\"
Created=\"" created "\"")

Here I wrapped the whole digest-as-string in (.getBytes) so there was
no Java error, but this simply did not work when I pinged Omniture.

In his email, he seems to suggest that the nonce should be binary but
that the date and the secret should be strings:

digest = sha1( Binary Nonce + Created Time String + API Secret Hex
String (32 bytes) ) "

But, as I said, when I tried this I got the ClassCastException.

No doubt some of my confusion is due to my ignorance of Java.

I was able to take their sample PHP code and get that to successfully
ping their API, however, my company has an official policy of moving
to the JVM, and of course I have a personal preference to work with
Clojure. So I'd like to figure out how to get this to work in Clojure.
(Needless to say that Omniture doesn't offer sample code in Clojure.)

I have been using clj-http to make the actual POST calls to Omniture.
Since I am on a Mac, I have been using the excellent Charles network
debugger ( http://www.charlesproxy.com/ ) to watch the actual posts
being made. Everything looks correct, except that in the end the
requests fails, apparently because the digest is malformed.

Any suggestions?






Michael Klishin

unread,
Mar 4, 2013, 10:53:44 AM3/4/13
to clo...@googlegroups.com
2013/3/4 larry google groups <lawrenc...@gmail.com>
Any suggestions?

You can use
https://github.com/clojurewerkz/support/blob/master/src/clojure/clojurewerkz/support/hashing.clj

note that you need to concatenate a byte array and bytes from a string first.
--
MK

http://github.com/michaelklishin
http://twitter.com/michaelklishin

larry google groups

unread,
Mar 4, 2013, 10:58:47 AM3/4/13
to Clojure
> You can use
>https://github.com/clojurewerkz/support/blob/master/src/clojure/cloju...
> note that you need to concatenate a byte array and bytes from a string
> first.


I appreciate your suggestion, but I feel that I need to understand why
I failed when I used these classes:

(org.apache.commons.codec.binary Base64)
(org.apache.commons.codec.digest DigestUtils)

If I am doing something fundamentally wrong, then I doubt I'll make
progress simply by using a different library.

--- lawrence





On Mar 4, 10:53 am, Michael Klishin <michael.s.klis...@gmail.com>
wrote:
> 2013/3/4 larry google groups <lawrencecloj...@gmail.com>
>
> > Any suggestions?
>
> You can usehttps://github.com/clojurewerkz/support/blob/master/src/clojure/cloju...

larry google groups

unread,
Mar 4, 2013, 11:02:43 AM3/4/13
to Clojure

The developer at Omniture very kindly sent me a follow up email that
gives me a sense of where my mistake might be, though I have no clear
idea how to fix it. I sent him my code and my output, to which he
responded:

" Most things seem in order until I see your wsse header. Its not
correct as both the password digest and nonce values are improperly
encoded. These should be base64 encoded representations of the values.
Neither of these strings are base64 encoded. You can tell a given
string is base64 encoded because it can only have 64 specific
characters in the string:
A-Z
a-z
0-9
+
/
That's 26 + 26 + 10 + 2 = 64 "

Sure enough, this is my nonce:

Nonce="[B@180fad0a"

I see his point -- this is not base64. But where did I go wrong? My
code right now, for the nonce, consists of these lines:

nonce (DigestUtils/md5Hex (random-string 32))
nonce-encoded-base64 (Base64/encodeBase64 (.getBytes nonce))

And then this gets added to the header:

header (apply str " UsernameToken Username=\"" username "\"
PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-base64 "\"
Created=\"" created "\"")

So where did I go wrong? Why is this not Base64?






On Mar 4, 10:53 am, Michael Klishin <michael.s.klis...@gmail.com>
wrote:
> 2013/3/4 larry google groups <lawrencecloj...@gmail.com>
>
> > Any suggestions?
>
> You can usehttps://github.com/clojurewerkz/support/blob/master/src/clojure/cloju...
>

Michael Klishin

unread,
Mar 4, 2013, 11:04:58 AM3/4/13
to clo...@googlegroups.com
2013/3/4 larry google groups <lawrenc...@gmail.com>
I appreciate your suggestion, but I feel that I need to understand why

I failed when I used these classes:
(.digest (java.security.MessageDigest/getInstance "sha1")
digest-as-string)

expects its argument to be a byte array:

which can be obtained from a string using String#getBytes.

Also, in

(apply str (.getBytes nonce) created secret)

I am not sure you get what you expect. clojure.core/str will take a string
representation of a byte array and concat it. What you probably want is
to concat all strings and then .getBytes from the result. Compare:

user=> (str (.getBytes "aa") "bb" "cc")
"[B@5d5d0293bbcc"
user=> (str "aa" "bb" "cc")
"aabbcc"
user=> (.getBytes (str "aa" "bb" "cc"))
#<byte[] [B@d9f8120>

larry google groups

unread,
Mar 4, 2013, 11:12:05 AM3/4/13
to Clojure
> expects its argument to be a byte array:http://docs.oracle.com/javase/6/docs/api/java/security/MessageDigest....
>
> which can be obtained from a string using String#getBytes.


I appreciate your suggestion. For most of the attempts that I have
made, I have used this code:

nonce (DigestUtils/md5Hex (random-string 32))
nonce-encoded-base64 (Base64/encodeBase64 (.getBytes nonce))
date-formatter (new SimpleDateFormat "yyyy-MM-dd'T'HH:mm:ss'Z'")
created (.format date-formatter (new Date))
digest-as-string (apply str nonce created secret)
digest (.digest (java.security.MessageDigest/getInstance "sha1")
(.getBytes digest-as-string))

So the SHA1 is given a Byte array. This fails to give me a correctly
formatted digest.





On Mar 4, 11:04 am, Michael Klishin <michael.s.klis...@gmail.com>
wrote:
> 2013/3/4 larry google groups <lawrencecloj...@gmail.com>
>
> > I appreciate your suggestion, but I feel that I need to understand why
> > I failed when I used these classes:
>
> (.digest (java.security.MessageDigest/getInstance "sha1")
> digest-as-string)
>
> expects its argument to be a byte array:http://docs.oracle.com/javase/6/docs/api/java/security/MessageDigest....

Aaron Cohen

unread,
Mar 4, 2013, 12:49:57 PM3/4/13
to clo...@googlegroups.com
On Mon, Mar 4, 2013 at 11:12 AM, larry google groups <lawrenc...@gmail.com> wrote:
> expects its argument to be a byte array:http://docs.oracle.com/javase/6/docs/api/java/security/MessageDigest....
>
> which can be obtained from a string using String#getBytes.


I appreciate your suggestion. For most of the attempts that I have
made, I have used this code:

 nonce (DigestUtils/md5Hex (random-string 32))
 nonce-encoded-base64 (Base64/encodeBase64 (.getBytes nonce))

Is this used somewhere?
 
 date-formatter (new SimpleDateFormat "yyyy-MM-dd'T'HH:mm:ss'Z'")
 created (.format date-formatter (new Date))
 digest-as-string (apply str nonce created secret)

(str binary-array) returns the toString of the array, which is something like "[B@5d5d0293". That has nothing to do with the contents of the array. I think you want the base64 encoded string here.

--Aaron
 

larry google groups

unread,
Mar 4, 2013, 1:09:08 PM3/4/13
to Clojure
> >  nonce (DigestUtils/md5Hex (random-string 32))
> >  nonce-encoded-base64 (Base64/encodeBase64 (.getBytes nonce))
>
> Is this used somewhere?

Yes, at the end, everything gets pulled together in a big string,
which is added as a header to the POST request:

header (apply str " UsernameToken Username=\"" username "\"
PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-base64 "\"
Created=\"" created "\"")

As the developer from Omniture told me:

"The nonce is transmitted in the header as base64 encoded. However,
the digest is formed with the raw binary version (aka decoded) version
of the string. The concept you have to understand is that the
authorization server is re-performing these critical steps on the back
end. If a step isn't done exactly in the client, the digest will
mismatch and cause an auth failure."



On Mar 4, 12:49 pm, Aaron Cohen <aa...@assonance.org> wrote:
> On Mon, Mar 4, 2013 at 11:12 AM, larry google groups <
>

larry google groups

unread,
Mar 4, 2013, 1:14:30 PM3/4/13
to Clojure
> (str binary-array) returns the toString of the array, which is something
> like "[B@5d5d0293". That has nothing to do with the contents of the array.
> I think you want the base64 encoded string here.

Hmm, interesting. I had assumed that I wanted a string run through
SHA1, so I created the string, then called getBytes on it to feed it
to SHA1:

digest-as-string (apply str nonce created secret)
digest (.digest (java.security.MessageDigest/getInstance "sha1")
(.getBytes digest-as-string))

But this is not working.





On Mar 4, 12:49 pm, Aaron Cohen <aa...@assonance.org> wrote:
> On Mon, Mar 4, 2013 at 11:12 AM, larry google groups <
>

Frank Siebenlist

unread,
Mar 4, 2013, 1:20:44 PM3/4/13
to clo...@googlegroups.com, Clojure
Not sure if it's helpful in this context, but I've been playing with a more functional 
message-digest/secure-hashing interface recently. 

Please take a look at:


It's still a little raw, and probably more "educational" than practical right now, 
but my hope was that if could clarify some of the message-digesting workings.

Would love to get some feedback...

Enjoy, Frank.
--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


larry google groups

unread,
Mar 4, 2013, 1:28:40 PM3/4/13
to Clojure
Frank, I thank you for your suggestion, though at the moment I am
trying to reduce the number of places where I could have made a
mistake, switching to a new interface would increase the number of
places that I might make a mistake. I would be interested in
experimenting with the new interface as soon as I have the current
interface working.


On Mar 4, 1:20 pm, Frank Siebenlist <frank.siebenl...@gmail.com>
wrote:
> Not sure if it's helpful in this context, but I've been playing with a more functional
> message-digest/secure-hashing interface recently.
>
> Please take a look at:
>
> https://github.com/franks42/clj.security.message-digest
>
> It's still a little raw, and probably more "educational" than practical right now,
> but my hope was that if could clarify some of the message-digesting workings.
>
> Would love to get some feedback...
>
> Enjoy, Frank.
>

larry google groups

unread,
Mar 4, 2013, 1:31:42 PM3/4/13
to Clojure
So, right now I am using this code:

(let [username (get-in @um/interactions [:omniture-api-
credentials :username])
secret (get-in @um/interactions [:omniture-api-credentials :shared-
secret])
nonce (DigestUtils/md5Hex (str (math/round (* (rand 1 ) 1000000))))
nonce-encoded-base64 (Base64/encodeBase64 (.getBytes nonce))
date-formatter (new SimpleDateFormat "yyyy-MM-dd'T'HH:mm:ss")
formatter gmt-timezone)
created (.format date-formatter (new Date))
digest-as-string (apply str nonce created secret)
digest (.digest (java.security.MessageDigest/getInstance "sha1")
(.getBytes digest-as-string))
digest-base64 (Base64/encodeBase64 digest)
header (apply str " UsernameToken Username=\"" username "\"
PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-base64 "\"
Created=\"" created "\"")]
header)

but when I print the header to the terminal, I still see values that
should be base64 but are not:

PasswordDigest="[B@3652831e" Nonce="[B@631e426e" Created="2\
013-03-04T13:26:21"

The classes I'm using are, I think, fairly standard:

(java.security MessageDigest)
(org.apache.commons.codec.binary Base64)
(org.apache.commons.codec.digest DigestUtils)


What am I doing wrong?






On Mar 4, 1:20 pm, Frank Siebenlist <frank.siebenl...@gmail.com>
wrote:
> Not sure if it's helpful in this context, but I've been playing with a more functional
> message-digest/secure-hashing interface recently.
>
> Please take a look at:
>
> https://github.com/franks42/clj.security.message-digest
>
> It's still a little raw, and probably more "educational" than practical right now,
> but my hope was that if could clarify some of the message-digesting workings.
>
> Would love to get some feedback...
>
> Enjoy, Frank.
>

Craig Brozefsky

unread,
Mar 4, 2013, 1:38:00 PM3/4/13
to clo...@googlegroups.com
larry google groups <lawrenc...@gmail.com> writes:

> Hmm, interesting. I had assumed that I wanted a string run through
> SHA1, so I created the string, then called getBytes on it to feed it
> to SHA1:
>
> digest-as-string (apply str nonce created secret)
> digest (.digest (java.security.MessageDigest/getInstance "sha1")
> (.getBytes digest-as-string))
>

(defn sha1[^bytes b]
(.digest (doto (java.security.MessageDigest/getInstance "SHA-1")
(.reset)
(.update b))))

(defn as-hex [^bytes v]
(clojure.string/lower-case
(javax.xml.bind.DatatypeConverter/printHexBinary v)))

(defn sha1-hex [^String v]
(-> (.getBytes v)
(sha1)
(as-hex)))

larry google groups

unread,
Mar 4, 2013, 1:38:43 PM3/4/13
to Clojure
Hmm, I finally got a different error message (after days of trying). I
re-read the documentation here:

http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html

and realized I might need to use this method:

encodeBase64String

which returns a string. The method I had been using was giving me
bytes instead of a string.

Now the API servers at Omniture tell me:

{\"error\":\"The nonce (ZTQxNWI4ZTU0YTQyMGEwZjRmZTY0ZTU0Nzc4MzgzZTQ=)
has already been used\"}"}

If anyone can suggest a better way to generate a random string, I
would be grateful.




On Mar 4, 1:31 pm, larry google groups <lawrencecloj...@gmail.com>

Craig Brozefsky

unread,
Mar 4, 2013, 1:43:00 PM3/4/13
to clo...@googlegroups.com

Craig Brozefsky <cr...@red-bean.com> writes:

..

Sorry, didn't reaze you wanted the output to be base64 encoded, in which
case, add these funcs:

(defn base64-encode [^bytes v]
(javax.xml.bind.DatatypeConverter/printBase64Binary v))

(defn sha1-base64 [^String v]
(-> (.getBytes v)
(sha1)
(base64-encode)))

--
Craig Brozefsky <cr...@red-bean.com>
Premature reification is the root of all evil

larry google groups

unread,
Mar 4, 2013, 1:47:22 PM3/4/13
to Clojure
> Now the API servers at Omniture tell me:
>
> {\"error\":\"The nonce (ZTQxNWI4ZTU0YTQyMGEwZjRmZTY0ZTU0Nzc4MzgzZTQ=)
> has already been used\"}"}


I may have misunderstood this. clj-http defaults to retrying the same
request 4 times, if it gets a 4xx error, so the nonce gets re-used 4
times, which would explain this error on the 2nd, 3rd and 4th
attempt.

Sadly, I can not figure out what the error is on the 1st attempt.




On Mar 4, 1:38 pm, larry google groups <lawrencecloj...@gmail.com>
wrote:
> Hmm, I finally got a different error message (after days of trying). I
> re-read the documentation here:
>
> http://commons.apache.org/codec/apidocs/org/apache/commons/codec/bina...

larry google groups

unread,
Mar 4, 2013, 1:49:18 PM3/4/13
to Clojure

I am willing to use these functions, but do you have any idea why my
code was wrong? I have the impression that I have somehow ended up
with a binary where I should instead have a string. Using a different
function won't help me if I use it the wrong way.

larry google groups

unread,
Mar 4, 2013, 1:54:13 PM3/4/13
to Clojure
Right now I am using this block of code:

(let [username (get-in @um/interactions [:omniture-api-
credentials :username])
secret (get-in @um/interactions [:omniture-api-credentials :shared-
secret])
random-number (math/round (* (rand 1 ) 1000000))
nonce (DigestUtils/md5Hex (str random-number))
nonce-encoded-base64 (Base64/encodeBase64String (.getBytes nonce))
date-formatter (new SimpleDateFormat "yyyy-MM-dd'T'HH:mm:ss")
created (.format date-formatter (new Date))
digest-as-string (apply str nonce created secret)
digest (.digest (java.security.MessageDigest/getInstance "sha1")
(.getBytes digest-as-string))
digest-base64 (Base64/encodeBase64String digest)
header (apply str " UsernameToken Username=\"" username "\"
PasswordDigest=\"" digest-base64 "\" Nonce=\"" nonce-encoded-base64
"\" Created=\"" created "\"")]
header)

And I get output that contains an equal sign, which I think is
suspect:

PasswordDigest="r+HWjSAk8AUvo/QmKKfbqQFnJ18="
Nonce="NmQxNGUwZjVlMjFhYjE1MzQ4MjUxYTA1MTg1YzE3ZTg="

The developer at Omniture reminded me of the characters allowed into
Base64:

" You can tell a given string is base64 encoded because it can only
have 64 specific characters in the string:
A-Z
a-z
0-9
+
/
That's 26 + 26 + 10 + 2 = 64
http://en.wikipedia.org/wiki/Base64 "


I assume that means that if I see a "=" in the string, then it is not
really Base64 encoded?










On Mar 4, 1:43 pm, Craig Brozefsky <cr...@red-bean.com> wrote:

larry google groups

unread,
Mar 4, 2013, 2:00:08 PM3/4/13
to Clojure
About this:

> (defn sha1-base64 [^String v]
>       (-> (.getBytes v)
>           (sha1)
>           (base64-encode)))

I tried to use it and I got "unknown symbol" for sha1, which makes
sense. What class are you using for sha1?





On Mar 4, 1:43 pm, Craig Brozefsky <cr...@red-bean.com> wrote:

Aaron Cohen

unread,
Mar 4, 2013, 2:02:24 PM3/4/13
to clo...@googlegroups.com
I think you should try to avoid the string concatenation games. I'm not sure what your current code is, but I suspect you're still ending up with array toString's slipping in.

How about the following?

On Mon, Mar 4, 2013 at 1:31 PM, larry google groups <lawrenc...@gmail.com> wrote:
So, right now I am using this code:

 (let [username (get-in @um/interactions [:omniture-api-
credentials :username])
  secret (get-in @um/interactions [:omniture-api-credentials :shared-
secret])
  nonce (DigestUtils/md5Hex (str (math/round (* (rand 1 ) 1000000))))
  nonce-encoded-base64 (Base64/encodeBase64 (.getBytes nonce))
  date-formatter (new SimpleDateFormat "yyyy-MM-dd'T'HH:mm:ss")
  formatter gmt-timezone)
  created (.format date-formatter (new Date))

   nonce-bytes (.getBytes nonce)
   created-bytes (.getBytes created)
   secret-bytes (.getBytes secret)
   digest (-> (java.security.MessageDigest/getInstance "sha1")
                    .reset
                    (.update nonce-bytes)
                    (.update create-bytes)
                    (.update secret-bytes)
                    .digest)
    digest-base64 (Base64/encodeBase64 digest)

;; Should "UsernameToken Username" really be unquoted in the following line?
;; All the other variable names are quoted
    header (apply str " UsernameToken Username=\""  username  

;; You may want digest-base64 here?

Aaron Cohen

unread,
Mar 4, 2013, 2:05:22 PM3/4/13
to clo...@googlegroups.com
On Mon, Mar 4, 2013 at 1:54 PM, larry google groups <lawrenc...@gmail.com> wrote:

PasswordDigest="r+HWjSAk8AUvo/QmKKfbqQFnJ18="
Nonce="NmQxNGUwZjVlMjFhYjE1MzQ4MjUxYTA1MTg1YzE3ZTg="

The developer at Omniture reminded me of the characters allowed into
Base64:

"  You can tell a given string is base64 encoded because it can only
have 64 specific characters in the string:
A-Z
a-z
0-9
+
/
That's 26 + 26 + 10 + 2 = 64
http://en.wikipedia.org/wiki/Base64    "


I assume that means that if I see a "=" in the string, then it is not
really Base64 encoded?

 
Every Base64 encoded string I've ever seen ends with at least one "=". I believe it's used for padding.

larry google groups

unread,
Mar 4, 2013, 2:06:41 PM3/4/13
to Clojure
>
> ;; Should "UsernameToken Username" really be unquoted in the following line?
> ;; All the other variable names are quoted

Apparently, yes. The developer at Omniture reviewed it and said my
only problem was the way the passwordDigest was created.



On Mar 4, 2:02 pm, Aaron Cohen <aa...@assonance.org> wrote:
> I think you should try to avoid the string concatenation games. I'm not
> sure what your current code is, but I suspect you're still ending up with
> array toString's slipping in.
>
> How about the following?
>
> On Mon, Mar 4, 2013 at 1:31 PM, larry google groups <
>

Frank Siebenlist

unread,
Mar 4, 2013, 2:25:32 PM3/4/13
to clo...@googlegroups.com, Frank Siebenlist
> digest (-> (java.security.MessageDigest/getInstance "sha1")
> .reset
> (.update nonce-bytes)
> (.update create-bytes)
> (.update secret-bytes)
> .digest)


There may be an issue with this snippet of code as ".update" does not return anything… i.e. nil.

-FS.

Aaron Cohen

unread,
Mar 4, 2013, 2:32:55 PM3/4/13
to clo...@googlegroups.com, Frank Siebenlist
Ah darn, thanks for the catch. The following is uglier but should work I guess. :\

digest (.digest
               (doto (java.security.MessageDigest/getInstance "sha1")
                     .reset
                     (.update nonce-bytes)
                     (.update created-bytes)
                     (.update secret-bytes)))

Frank Siebenlist

unread,
Mar 4, 2013, 2:43:03 PM3/4/13
to Aaron Cohen, Frank Siebenlist, clo...@googlegroups.com
That should work.

No need for .reset though as the initially constructed MessageDigest is already in its initial state.

Be careful with .digest as it implicitly resets the MessageDigest, and calling it a second time gives you the digest of the initial state which is not what you want.

(all that "incidental complexity" is why I started to write that functional interface ;-) )

-FS.

larry google groups

unread,
Mar 4, 2013, 3:07:39 PM3/4/13
to Clojure


I have a feeling you may be correct, so I copy and pasted your code
into mine:

(let [username (get-in @um/interactions [:omniture-api-
credentials :username])
secret (get-in @um/interactions [:omniture-api-
credentials :shared-secret])
random-number (math/round (* (rand 1 ) 1000000))
nonce (DigestUtils/md5Hex (str random-number))
nonce-encoded-base64 (base64-encode (.getBytes
nonce))
date-formatter (new SimpleDateFormat "yyyy-MM-
dd'T'HH:mm:ss")
created (.format date-formatter (new Date))
nonce-as-bytes (.getBytes nonce)
created-as-bytes (.getBytes created)
secret-as-bytes (.getBytes secret)
digest (-> (java.security.MessageDigest/
getInstance "sha1")
.reset
(.update nonce-as-bytes)
(.update created-as-bytes)
(.update secret-as-bytes)
.digest)
header (apply str " UsernameToken Username=\""
username "\" PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-
base64 "\" Created=\"" created "\"")]
header)



but now I get a null pointer exception here:

digest (-> (java.security.MessageDigest/
getInstance "sha1")
.reset
(.update nonce-as-bytes)
(.update created-as-bytes)
(.update secret-as-bytes)
.digest)





On Mar 4, 2:02 pm, Aaron Cohen <aa...@assonance.org> wrote:
> I think you should try to avoid the string concatenation games. I'm not
> sure what your current code is, but I suspect you're still ending up with
> array toString's slipping in.
>
> How about the following?
>
> On Mon, Mar 4, 2013 at 1:31 PM, larry google groups <
>

Aaron Cohen

unread,
Mar 4, 2013, 3:15:27 PM3/4/13
to Frank Siebenlist, clo...@googlegroups.com
I'm not familiar with the class, but it seems that MessageDigest/getInstance might retrieve some shared instance that could theoretically need to be reset, no?

Frank Siebenlist

unread,
Mar 4, 2013, 3:31:23 PM3/4/13
to Aaron Cohen, Frank Siebenlist, clo...@googlegroups.com
You should get a new, separate instance for every digest you would like to generate "concurrently". Nothing, i.e. no mutable state, is shared between different MessageDigest instances in the subsequent digesting/secure-hashing.

The .reset is only used if you want to give up you current effort without generating the final digest with .digest as the latter already does the reset implicitly. After the reset you can reuse that same MessageDigest object for a new digest.

You can optionally .clone a MessageDigest object is you want to have a final digest value for a partial byte-list. For example, if you want the digest for both "a" and "abc", you could take a new MessageDigest object, .update it for "a"'s bytes, .clone that MessageDigest, use one copy to generate the final .digest for "a", and use the other copy to add the digest of "bc"'s bytes and .digest that.

Ough… hope that's clear...

-FS.

larry google groups

unread,
Mar 4, 2013, 3:38:12 PM3/4/13
to Clojure
To be clear about an earlier point, if I do this:

digest-as-string (apply str nonce created secret)

and then print this to the terminal, I can see it really is just a
string made of these 3 items. Just a plain string, exactly what I
assumed.


On Mar 4, 2:25 pm, Frank Siebenlist <frank.siebenl...@gmail.com>
wrote:
> > digest (-> (java.security.MessageDigest/getInstance "sha1")
> >                     .reset
> >                     (.update nonce-bytes)
> >                     (.update create-bytes)
> >                     (.update secret-bytes)
> >                     .digest)
>
> There may be an issue with this snippet of code as ".update" does not return anything… i.e. nil.
>
> -FS.
>

Aaron Cohen

unread,
Mar 4, 2013, 3:52:50 PM3/4/13
to clo...@googlegroups.com
Frank pointed out this mistake in my code a little farther in the thread. Sorry for the confusion.

digest (.digest
               (doto (java.security.MessageDigest/getInstance "sha1")
                     .reset
                     (.update nonce-as-bytes)
                     (.update created-as-bytes)
                     (.update secret-as-bytes)))


larry google groups

unread,
Mar 4, 2013, 3:54:08 PM3/4/13
to Clojure
I keep trying different things, but I feel like I'm missing some
obvious mistake. Right now my code includes this function:

(defn sha1 [string]
;; 2013-03-04 - this function comes from here:
;; https://github.com/dmitriy-kiriyenko/clojure-api-quiz/blob/master/src/clojure_api_quiz/sha1_url.clj
(apply str
(map (partial format "%02x")
(.digest (doto (java.security.MessageDigest/getInstance
"SHA-1")
.reset
(.update (.getBytes string)))))))


And then I create the headers like this:

(let [username (get-in @um/interactions [:omniture-api-
credentials :username])
secret (get-in @um/interactions [:omniture-api-
credentials :shared-secret])
random-number (math/round (* (rand 1 ) 1000000))
nonce (DigestUtils/md5Hex (str random-number))
nonce-encoded-base64 (base64-encode (.getBytes
nonce))
date-formatter (new SimpleDateFormat "yyyy-MM-
dd'T'HH:mm:ss")
created (.format date-formatter (new Date))
digest-as-string (apply str nonce created secret)
digest (sha1 digest-as-string)
digest-base64 (base64-encode (.getBytes digest))
header (apply str " UsernameToken Username=\""
username "\" PasswordDigest=\"" digest-base64 "\" Nonce=\"" nonce-
encoded-base64 "\" Created=\"" created "\"")]
header)

Which gives me, in part:

PasswordDigest="ZDk4NGZlMjgxMDZhMWIxZjZmZGMzYWYwZTY5Mjc1YTY\
xNjc1ODA0Yg==" Nonce="ODlmOTY5MGRkMTczZTlkMDU4ZWY0M2JkYTQ5NjFmMzc="

Which still fails to correctly authenticate.









On Mar 4, 10:47 am, larry google groups <lawrencecloj...@gmail.com>
wrote:
> I have been having problems making an API call to Omniture. I have
> exchanged a dozen emails with a developer at Omniture, and he gave me
> the impression that I was constructing my security codes incorrectly.
> So now I am confronting my ignorance over how Java handles certain
> conversions.
>
> The developer at Omniture sent me this explanation in an email:
>
> " The security digest is formed from a sha1 hash of the following
> string concatenation:
> digest = sha1( Binary Nonce + Created Time String + API Secret Hex
> String (32 bytes) )  "
>
> I have been struggling with this for several days and I have tried at
> least (literally) 200 variations on this bit of code:
>
> (let [username (get-in @um/interactions [:omniture-api-
> credentials :username])
>       secret (get-in @um/interactions [:omniture-api-
> credentials :shared-secret])
>       nonce (DigestUtils/md5Hex (random-string 32))
>       nonce-encoded-base64 (Base64/encodeBase64 (.getBytes nonce))
>       date-formatter (new SimpleDateFormat "yyyy-MM-
> dd'T'HH:mm:ss'Z'")
>       created (.format date-formatter (new Date))
>       digest-as-string (apply str (.getBytes nonce) created secret)
>       digest (.digest (java.security.MessageDigest/getInstance "sha1")
> digest-as-string)
>       header (apply str " UsernameToken Username=\""  username  "\"
> PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-base64 "\"
> Created=\"" created "\"")]
>                 header)
>
> This version gives me:
>
> "Exception in the main function: " #<ClassCastException
> java.lang.ClassCastException: java.lang.String cannot be cast to [B>
>
> For a long time I was using this for the last 3 lines:
>
>       digest-as-string (apply str nonce created secret)
>       digest (.digest (java.security.MessageDigest/getInstance "sha1")
> (.getByes digest-as-string))
>       header (apply str " UsernameToken Username=\""  username  "\"
> PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-base64 "\"
> Created=\"" created "\"")
>
> Here I wrapped the whole digest-as-string in (.getBytes) so there was
> no Java error, but this simply did not work when I pinged Omniture.
>
> In his email, he seems to suggest that the nonce should be binary but
> that the date and the secret should be strings:
>
> digest = sha1( Binary Nonce + Created Time String + API Secret Hex
> String (32 bytes) )  "
>
> But, as I said, when I tried this I got the ClassCastException.
>
> No doubt some of my confusion is due to my ignorance of Java.
>
> I was able to take their sample PHP code and get that to successfully
> ping their API, however, my company has an official policy of moving
> to the JVM, and of course I have a personal preference to work with
> Clojure. So I'd like to figure out how to get this to work in Clojure.
> (Needless to say that Omniture doesn't offer sample code in Clojure.)
>
> I have been using clj-http to make the actual POST calls to Omniture.
> Since I am on a Mac, I have been using the excellent Charles network
> debugger (http://www.charlesproxy.com/) to watch the actual posts
> being made. Everything looks correct, except that in the end the
> requests fails, apparently because the digest is malformed.
>
> Any suggestions?

Frank Siebenlist

unread,
Mar 4, 2013, 3:58:28 PM3/4/13
to clo...@googlegroups.com, Frank Siebenlist
Hi Larry,

One approach that helps a lot when trying to figure out where the issue is with all that message digesting, is by using well-known test cases. If you look at:

http://en.wikipedia.org/wiki/SHA-1

you will find the test cases:

SHA1("The quick brown fox jumps over the lazy dog") =
2fd4e1c6 7a2d28fc ed849ee1 bb76e739 1b93eb12

and

SHA1("") =
da39a3ee 5e6b4b0d 3255bfef 95601890 afd80709

If you split up your "nonce created secret" as partial strings of "The quick brown fox jumps over the lazy dog", then you know what hex'ified digest to expect, and helps you to track-down/eliminate errors.

The empty string digest is also important to have, because the initial MessageDigest object, or any reset one, or after a call to .digest, will generate that particular digest value. In other words, if you see that digest then you're probably working with a (accidentally?) reset MessageDigest object.


Hope that helps.

-FrankS.

larry google groups

unread,
Mar 4, 2013, 4:00:08 PM3/4/13
to Clojure
Thank you for the suggestions. If I do this:

(let [username (get-in @um/interactions [:omniture-api-
credentials :username])
secret (get-in @um/interactions [:omniture-api-
credentials :shared-secret])
random-number (math/round (* (rand 1 ) 1000000))
nonce (DigestUtils/md5Hex (str random-number))
nonce-encoded-base64 (base64-encode (.getBytes
nonce))
date-formatter (new SimpleDateFormat "yyyy-MM-
dd'T'HH:mm:ss")
created (.format date-formatter (new Date))
nonce-as-bytes (.getBytes nonce)
created-as-bytes (.getBytes created)
secret-as-bytes (.getBytes secret)
digest (.digest
(doto (java.security.MessageDigest/
getInstance "sha1")
.reset
(.update nonce-as-bytes)
(.update created-as-bytes)
(.update secret-as-
bytes)))
;; ;;digest-base64 (base64-encode (.getBytes
digest))
header (apply str " UsernameToken Username=\""
username "\" PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-
base64 "\" Created=\"" created "\"")]
header)


I end up with, in part:

PasswordDigest="[B@26f7b2f4"
Nonce="Y2MwN2JiYzA5MDlmZjE2ZjExMGYzMjRhODA2Yjc5ODc="
Created="2013-03-04T15:57:52"

Which I think is still incorrect.

I also tried this with the PasswordDigest base64 encoded, and that did
not work either.







On Mar 4, 2:43 pm, Frank Siebenlist <frank.siebenl...@gmail.com>
wrote:
> That should work.
>
> No need for .reset though as the initially constructed MessageDigest is already in its initial state.
>
> Be careful with .digest as it implicitly resets the MessageDigest, and calling it a second time gives you the digest of the initial state which is not what you want.
>
> (all that "incidental complexity" is why I started to write that functional interface ;-) )
>
> -FS.
>
> On Mar 4, 2013, at 11:32 AM, Aaron Cohen <aa...@assonance.org> wrote:
>
>
>
>
>
>
>
> > Ah darn, thanks for the catch. The following is uglier but should work I guess. :\
>
> > digest (.digest
> >                (doto (java.security.MessageDigest/getInstance "sha1")
> >                      .reset
> >                      (.update nonce-bytes)
> >                      (.update created-bytes)
> >                      (.update secret-bytes)))
>
> > On Mon, Mar 4, 2013 at 2:25 PM, Frank Siebenlist <frank.siebenl...@gmail.com> wrote:
> > > digest (-> (java.security.MessageDigest/getInstance "sha1")
> > >                     .reset
> > >                     (.update nonce-bytes)
> > >                     (.update create-bytes)
> > >                     (.update secret-bytes)
> > >                     .digest)
>
> > There may be an issue with this snippet of code as ".update" does not return anything… i.e. nil.
>
> > -FS.
>
> > > For more options, visithttps://groups.google.com/groups/opt_out.

Frank Siebenlist

unread,
Mar 4, 2013, 4:17:53 PM3/4/13
to clo...@googlegroups.com, Frank Siebenlist
.digest returns a byte array - so your PasswordDigest value is unprintable and is not fit for any header/URL.

Not sure what this base64-encode function does exactly, but normally base64 encoding takes a bunch of bytes already, so the (commented-out) "digest-base64 (base64-encode (.getBytes digest))" does not need the (.getBytes …) transformation as your digest consists already of bytes.

Note that base64 encoded stuff is not url/html safe and has to be encoded also before sending it over the http-wire.

-FS.

larry google groups

unread,
Mar 4, 2013, 4:22:37 PM3/4/13
to Clojure

> Be careful with .digest as it implicitly resets the MessageDigest, and
> calling it a second time gives you the digest of the initial state
> which is not what you want.

I am somewhat ignorant of how mutable Java variables behave inside of
Clojure. Can I assume that the instance of MessageDigest disappears as
soon as the function returns? If I call the function again, I am
creating a new instance MessageDigest, and so I don't have to worry
about calling .digest()?



On Mar 4, 2:43 pm, Frank Siebenlist <frank.siebenl...@gmail.com>
wrote:
> That should work.
>
> No need for .reset though as the initially constructed MessageDigest is already in its initial state.
>
> Be careful with .digest as it implicitly resets the MessageDigest, and calling it a second time gives you the digest of the initial state which is not what you want.
>
> (all that "incidental complexity" is why I started to write that functional interface ;-) )
>
> -FS.
>
> On Mar 4, 2013, at 11:32 AM, Aaron Cohen <aa...@assonance.org> wrote:
>
>
>
>
>
>
>
> > Ah darn, thanks for the catch. The following is uglier but should work I guess. :\
>
> > digest (.digest
> >                (doto (java.security.MessageDigest/getInstance "sha1")
> >                      .reset
> >                      (.update nonce-bytes)
> >                      (.update created-bytes)
> >                      (.update secret-bytes)))
>
> > On Mon, Mar 4, 2013 at 2:25 PM, Frank Siebenlist <frank.siebenl...@gmail.com> wrote:
> > > digest (-> (java.security.MessageDigest/getInstance "sha1")
> > >                     .reset
> > >                     (.update nonce-bytes)
> > >                     (.update create-bytes)
> > >                     (.update secret-bytes)
> > >                     .digest)
>
> > There may be an issue with this snippet of code as ".update" does not return anything… i.e. nil.
>
> > -FS.
>
> > > For more options, visithttps://groups.google.com/groups/opt_out.

Frank Siebenlist

unread,
Mar 4, 2013, 4:26:12 PM3/4/13
to clo...@googlegroups.com, Frank Siebenlist
Yup, in your case, with:

>> digest (.digest
>> (doto (java.security.MessageDigest/getInstance "sha1")
>> .reset
>> (.update nonce-bytes)
>> (.update created-bytes)
>> (.update secret-bytes)))


you do not have to worry about calling .digest twice on the same instance.

-FS.

larry google groups

unread,
Mar 4, 2013, 4:31:13 PM3/4/13
to Clojure
I finally got this to work. Many thanks for all of the help that I was
given here.

The final, winning combination was:

(let [username (get-in @um/interactions [:omniture-api-
credentials :username])
secret (get-in @um/interactions [:omniture-api-credentials :shared-
secret])
random-number (math/round (* (rand 1 ) 1000000))
nonce (DigestUtils/md5Hex (str random-number))
nonce-encoded-base64 (base64-encode (.getBytes nonce))
date-formatter (new SimpleDateFormat "yyyy-MM-dd'T'HH:mm:ss")
created (.format date-formatter (new Date))
nonce-as-bytes (.getBytes nonce)
created-as-bytes (.getBytes created)
secret-as-bytes (.getBytes secret)
digest (.digest
(doto (java.security.MessageDigest/getInstance
"sha1")
.reset
(.update nonce-as-bytes)
(.update created-as-bytes)
(.update secret-as-bytes)))
digest-base64 (base64-encode digest)
header (apply str " UsernameToken Username=\"" username "\"
PasswordDigest=\"" digest-base64 "\" Nonce=\"" nonce-encoded-base64
"\" Created=\"" created "\"")]
header)



On Mar 4, 10:47 am, larry google groups <lawrencecloj...@gmail.com>
wrote:
> I have been having problems making an API call to Omniture. I have
> exchanged a dozen emails with a developer at Omniture, and he gave me
> the impression that I was constructing my security codes incorrectly.
> So now I am confronting my ignorance over how Java handles certain
> conversions.
>
> The developer at Omniture sent me this explanation in an email:
>
> " The security digest is formed from a sha1 hash of the following
> string concatenation:
> digest = sha1( Binary Nonce + Created Time String + API Secret Hex
> String (32 bytes) )  "
>
> I have been struggling with this for several days and I have tried at
> least (literally) 200 variations on this bit of code:
>
> (let [username (get-in @um/interactions [:omniture-api-
> credentials :username])
>       secret (get-in @um/interactions [:omniture-api-
> credentials :shared-secret])
>       nonce (DigestUtils/md5Hex (random-string 32))
>       nonce-encoded-base64 (Base64/encodeBase64 (.getBytes nonce))
>       date-formatter (new SimpleDateFormat "yyyy-MM-
> dd'T'HH:mm:ss'Z'")
>       created (.format date-formatter (new Date))
>       digest-as-string (apply str (.getBytes nonce) created secret)
>       digest (.digest (java.security.MessageDigest/getInstance "sha1")
> digest-as-string)
>       header (apply str " UsernameToken Username=\""  username  "\"
> PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-base64 "\"
> Created=\"" created "\"")]
>                 header)
>
> This version gives me:
>
> "Exception in the main function: " #<ClassCastException
> java.lang.ClassCastException: java.lang.String cannot be cast to [B>
>
> For a long time I was using this for the last 3 lines:
>
>       digest-as-string (apply str nonce created secret)
>       digest (.digest (java.security.MessageDigest/getInstance "sha1")
> (.getByes digest-as-string))
>       header (apply str " UsernameToken Username=\""  username  "\"
> PasswordDigest=\"" digest "\" Nonce=\"" nonce-encoded-base64 "\"
> Created=\"" created "\"")
>

Frank Siebenlist

unread,
Mar 4, 2013, 4:55:57 PM3/4/13
to clo...@googlegroups.com, Frank Siebenlist
Glad Larry has working code now...

As I mentioned before in this thread, I'm working on this functional interface for the message-digesting/secure-hashing, and this whole discussion reads like a use case for the "why?" ;-)

It "proofs" to me that there may be real value in a more user-friendly approach than the one offered by java.security.MessageDigest.

So instead of writing:


(let [...
nonce-as-bytes (.getBytes nonce)
created-as-bytes (.getBytes created)
secret-as-bytes (.getBytes secret)
digest (.digest
(doto (java.security.MessageDigest/getInstance "sha1")
.reset
(.update nonce-as-bytes)
(.update created-as-bytes)
(.update secret-as-bytes)))
…]


my library lets you write:


(let […
digest (md/digest :sha-1 :utf-8 nonce created secret)
…]


and the advantages of the more functional approach is much more than just saving a few lines of code!

Although it still needs some more work, any feedback on
"https://github.com/franks42/clj.security.message-digest"
is much appreciated.

Regards, FrankS.

Vladimir Tsichevski

unread,
Mar 4, 2013, 4:56:49 PM3/4/13
to clo...@googlegroups.com
It is. See  http://en.wikipedia.org/wiki/Base64 , the "Padding" section.

larry google groups

unread,
Mar 4, 2013, 5:42:27 PM3/4/13
to Clojure

> If you split up your "nonce created secret" as partial strings of "The
> quick brown fox jumps over the lazy dog", then you know
> what hex'ified digest to expect, and helps you to track-down/eliminate errors.

That is a good tip and I'll use it in the future.




On Mar 4, 3:58 pm, Frank Siebenlist <frank.siebenl...@gmail.com>
wrote:
> Hi Larry,
>
> One approach that helps a lot when trying to figure out where the issue is with all that message digesting, is by using well-known test cases. If you look at:
>
> http://en.wikipedia.org/wiki/SHA-1
>
> you will find the test cases:
>
> SHA1("The quick brown fox jumps over the lazy dog") =
> 2fd4e1c6 7a2d28fc ed849ee1 bb76e739 1b93eb12
>
> and
>
> SHA1("") =
> da39a3ee 5e6b4b0d 3255bfef 95601890 afd80709
>
> If you split up your "nonce created secret" as partial strings of "The quick brown fox jumps over the lazy dog", then you know what hex'ified digest to expect, and helps you to track-down/eliminate errors.
>
> The empty string digest is also important to have, because the initial MessageDigest object, or any reset one, or after a call to .digest, will generate that particular digest value. In other words, if you see that digest then you're probably working with a (accidentally?) reset MessageDigest object.
>
> Hope that helps.
>
> -FrankS.
>

larry google groups

unread,
Mar 4, 2013, 5:54:17 PM3/4/13
to Clojure
> Note that base64 encoded stuff is not url/html safe and has to be encoded also
> before sending it over the http-wire.

Hmm, interesting. This is going as the headers in a POST. It did seem
to work the last time I ran it, just now, a few minutes ago.

I am using the clj-http library and my actual POST function is:

(defn omniture-call-api [url-with-queue-method api-payload headers]
(timbre/spy :debug " return value of omniture-call-api "
(try+
(http-client/post url-with-queue-method
{:body api-payload
:debug true
:debug-body true
:insecure true
:headers {"X-Api-Version" "2"
"X-WSSE" headers}
:content-type :json
:socket-timeout 4000
:conn-timeout 4000
:accept :json
:client-params
{"http.protocol.allow-circular-redirects" true
"http.useragent"
"clj-http"}})
(catch Object o (println (pp/pprint o))))))


The 3rd argument, headers, is what you helped me figure out earlier.



On Mar 4, 4:17 pm, Frank Siebenlist <frank.siebenl...@gmail.com>
wrote:
> .digest returns a byte array - so your PasswordDigest value is unprintable and is not fit for any header/URL.
>
> Not sure what this base64-encode function does exactly, but normally base64 encoding takes a bunch of bytes already, so the (commented-out) "digest-base64 (base64-encode (.getBytes digest))" does not need the (.getBytes …) transformation as your digest consists already of bytes.
>
> Note that base64 encoded stuff is not url/html safe and has to be encoded also before sending it over the http-wire.
>
> -FS.
>

larry google groups

unread,
Mar 4, 2013, 5:56:02 PM3/4/13
to Clojure
> my library lets you write:
>
>   (let […
>         digest (md/digest :sha-1 :utf-8 nonce created secret)
>          …]
>

Great. Now that I have the code working I'll switch over to your
library. I felt the need to get a mental picture of what the
underlying Java code was doing, but now that I have that I would
happily switch to a cleaner interface.





On Mar 4, 4:55 pm, Frank Siebenlist <frank.siebenl...@gmail.com>
wrote:
> Glad Larry has working code now...
>
> As I mentioned before in this thread, I'm working on this functional interface for the message-digesting/secure-hashing, and this whole discussion reads like a use case for the "why?" ;-)
>
> It "proofs" to me that there may be real value in a more user-friendly approach than the one offered by java.security.MessageDigest.
>
> So instead of writing:
>
>   (let [...
>         nonce-as-bytes (.getBytes nonce)
>         created-as-bytes (.getBytes created)
>         secret-as-bytes (.getBytes secret)
>         digest (.digest
>                   (doto (java.security.MessageDigest/getInstance "sha1")
>                       .reset
>                        (.update nonce-as-bytes)
>                        (.update created-as-bytes)
>                         (.update secret-as-bytes)))
>          …]
>
> my library lets you write:
>
>   (let […
>         digest (md/digest :sha-1 :utf-8 nonce created secret)
>          …]
>
> and the advantages of the more functional approach is much more than just saving a few lines of code!
>
> Although it still needs some more work, any feedback on
> "https://github.com/franks42/clj.security.message-digest"
> is much appreciated.
>
> Regards, FrankS.
>

larry google groups

unread,
Mar 4, 2013, 6:09:30 PM3/4/13
to Clojure
Frank,

Any idea when you might release your code in a stable form? I am using
this code at work so I am nervous about using code that is still
marked SNAPSHOT. Lein reminds me not to use a SNAPSHOT:

Could not find metadata org.clojars.franks42:clj.security.message-
digest:0.1.0-SNAPSHOT/maven-metadata.xml in central (http://
repo1.maven.org/maven2)
Could not find metadata org.clojars.franks42:clj.security.message-
digest:0.1.0-SNAPSHOT/maven-metadata.xml in central-proxy (https://
repository.sonatype.org/content/repositories/centralm1/)
Retrieving org/clojars/franks42/clj.security.message-digest/0.1.0-
SNAPSHOT/maven-metadata.xml (1k)
from https://clojars.org/repo/
Could not find artifact org.clojars.franks42:clj.security.message-
digest:pom:0.1.0-20130304.220822-1 in central (http://repo1.maven.org/
maven2)
Retrieving org/clojars/franks42/clj.security.message-digest/0.1.0-
SNAPSHOT/clj.security.message-digest-0.1.0-20130304.220822-1.pom (3k)
from https://clojars.org/repo/
Retrieving org/clojure/clojure/1.5.0/clojure-1.5.0.pom (6k)
from http://repo1.maven.org/maven2/
Retrieving org/clojars/franks42/clj.security.message-digest/0.1.0-
SNAPSHOT/clj.security.message-digest-0.1.0-20130304.220822-1.jar (6k)
from https://clojars.org/repo/
Compiling 1 source files to /Users/lkrubner/projects/multi-platform-
data-visualization/mpdv-clojure/target/classes
Release versions may not depend upon snapshots.
Freeze snapshots to dated versions or set the
LEIN_SNAPSHOTS_IN_RELEASE environment variable to override.






On Mar 4, 4:55 pm, Frank Siebenlist <frank.siebenl...@gmail.com>
wrote:
> Glad Larry has working code now...
>
> As I mentioned before in this thread, I'm working on this functional interface for the message-digesting/secure-hashing, and this whole discussion reads like a use case for the "why?" ;-)
>
> It "proofs" to me that there may be real value in a more user-friendly approach than the one offered by java.security.MessageDigest.
>
> So instead of writing:
>
>   (let [...
>         nonce-as-bytes (.getBytes nonce)
>         created-as-bytes (.getBytes created)
>         secret-as-bytes (.getBytes secret)
>         digest (.digest
>                   (doto (java.security.MessageDigest/getInstance "sha1")
>                       .reset
>                        (.update nonce-as-bytes)
>                        (.update created-as-bytes)
>                         (.update secret-as-bytes)))
>          …]
>
> my library lets you write:
>
>   (let […
>         digest (md/digest :sha-1 :utf-8 nonce created secret)
>          …]
>
> and the advantages of the more functional approach is much more than just saving a few lines of code!
>
> Although it still needs some more work, any feedback on
> "https://github.com/franks42/clj.security.message-digest"
> is much appreciated.
>
> Regards, FrankS.
>

Frank Siebenlist

unread,
Mar 4, 2013, 6:17:37 PM3/4/13
to clo...@googlegroups.com, Frank Siebenlist
If your code is for production… do not use my code!

It's pretty much written over the weekend and it's "security code", meaning that it deserves much more scrutiny than "ordinary" code.

As mentioned in the readme, it's more an "educational exercise", although it was good to see you struggling as it validated my concerns about the java library's approach ;-)

Don't even know if I'm willing to maintain it either…

Sorry for the bad news - I was just trying to sollicit feedback about alternative interfaces for the secure hashing.

Regards, FrankS.

Frank Siebenlist

unread,
Mar 4, 2013, 6:46:07 PM3/4/13
to clo...@googlegroups.com, Frank Siebenlist
Larry,

What I can advise though, is to look at my library code and it may give you different perspectives.

Furthermore, copy, borrow, and steal what you like and make it your own.

-FS.

Mark C

unread,
Mar 4, 2013, 10:32:38 PM3/4/13
to clo...@googlegroups.com, Frank Siebenlist
For another cut at a hashing interface, you may want to look at one I made specifically for passwords.  You make a password hasher by passing in algorithm, salt length, and iterations, and you get back a map containing a pair of complementary functions: one that digests, the other that verifies.  https://gist.github.com/mchampine/868342

Example: Digester/verifier pair using SHA-256 with 16 bytes salt and 10k iterations

(def strongPWHasher (pwfuncs "SHA-256" 16 10000)) ; make the digester/verifier pair
(def hashed-pw ((strongPWHasher :digest) "mysecret")) ; hash the password
((strongPWHasher :verify) "mysecret" hashed-pw) ; verify it

M.

Frank Siebenlist

unread,
Mar 5, 2013, 1:52:46 AM3/5/13
to Mark C, Frank Siebenlist, clo...@googlegroups.com
Hi Mark,

Thanks for sharing!

I like the approach of hiding all the interaction with the java.security.MessageDigest library,
and returning the pair of matching digest&verify functions - it will avoid many mistakes.

As far as the presented algorithm is concerned, it may be more prudent to stick with bcrypt and friends though…

Regards, Frank.

Frank Siebenlist

unread,
Mar 6, 2013, 1:05:37 AM3/6/13
to Clojure, Frank Siebenlist
I just became aware of this crypto-password clojure-library by compojure/hiccup/codox's James Reeve:
https://github.com/weavejester/crypto-password

Supports bcrypt, script and pbkdf2.

Very well designed and easy to use!

This should be your goto-library for secure hashing of passwords!!!

-Frank.

Mark C

unread,
Mar 6, 2013, 9:16:38 AM3/6/13
to clo...@googlegroups.com, Mark C, Frank Siebenlist
I used a widely accepted pattern: SHA2+Salt+Iteration, that's been used many times, e.g. in Jasypt http://www.jasypt.org/ so I think that aspect of it is pretty conservative, and virtually all the algorithm's strength lies within the SHA implementation itself - so my opportunity to screw up via this wrapper is limited. Still, crypto is hard, and subtle flaws can be introduced in unexpected ways. I agree: just use bcrypt.

M.
Reply all
Reply to author
Forward
0 new messages