From 43b2bad8364086edb02df2e18fc0d20ab1ea5642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=9E=E5=94=AF?= Date: Wed, 8 Nov 2017 14:55:48 +0800 Subject: [PATCH] add HMAC functionalities (#45) --- src/SHA.jl | 22 ++++++++++++++++++++++ src/hmac.jl | 33 +++++++++++++++++++++++++++++++++ test/runtests.jl | 26 ++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/hmac.jl diff --git a/src/SHA.jl b/src/SHA.jl index 2dc48c20cec1a..ae0f8516c8942 100644 --- a/src/SHA.jl +++ b/src/SHA.jl @@ -10,6 +10,10 @@ export sha3_224, sha3_256, sha3_384, sha3_512 export SHA224_CTX, SHA256_CTX, SHA384_CTX, SHA512_CTX export SHA2_224_CTX, SHA2_256_CTX, SHA2_384_CTX, SHA2_512_CTX export SHA3_224_CTX, SHA3_256_CTX, SHA3_384_CTX, SHA3_512_CTX +export HMAC_CTX, hmac_sha1 +export hmac_sha224, hmac_sha256, hmac_sha384, hmac_sha512 +export hmac_sha2_224, hmac_sha2_256, hmac_sha2_384, hmac_sha2_512 +export hmac_sha3_224, hmac_sha3_256, hmac_sha3_384, hmac_sha3_512 include("constants.jl") @@ -19,6 +23,7 @@ include("sha1.jl") include("sha2.jl") include("sha3.jl") include("common.jl") +include("hmac.jl") # Create data types and convenience functions for each hash implemented for (f, ctx) in [(:sha1, :SHA1_CTX), @@ -34,6 +39,8 @@ for (f, ctx) in [(:sha1, :SHA1_CTX), (:sha3_256, :SHA3_256_CTX), (:sha3_384, :SHA3_384_CTX), (:sha3_512, :SHA3_512_CTX),] + g = Symbol(:hmac_, f) + @eval begin # Our basic function is to process arrays of bytes function $f(data::T) where T<:Union{Array{UInt8,1},NTuple{N,UInt8} where N} @@ -41,9 +48,15 @@ for (f, ctx) in [(:sha1, :SHA1_CTX), update!(ctx, data) return digest!(ctx) end + function $g(key::Vector{UInt8}, data::T) where T<:Union{Array{UInt8,1},NTuple{N,UInt8} where N} + ctx = HMAC_CTX($ctx(), key) + update!(ctx, data) + return digest!(ctx) + end # AbstractStrings are a pretty handy thing to be able to crunch through $f(str::AbstractString) = $f(Vector{UInt8}(str)) + $g(key::Vector{UInt8}, str::AbstractString) = $g(key, Vector{UInt8}(str)) # Convenience function for IO devices, allows for things like: # open("test.txt") do f @@ -58,6 +71,15 @@ for (f, ctx) in [(:sha1, :SHA1_CTX), end return digest!(ctx) end + function $g(key::Vector{UInt8}, io::IO, chunk_size=4*1024) + ctx = HMAC_CTX($ctx(), key) + buff = Vector{UInt8}(chunk_size) + while !eof(io) + num_read = readbytes!(io, buff) + update!(ctx, buff[1:num_read]) + end + return digest!(ctx) + end end end diff --git a/src/hmac.jl b/src/hmac.jl new file mode 100644 index 0000000000000..1bb21bf146a3a --- /dev/null +++ b/src/hmac.jl @@ -0,0 +1,33 @@ +struct HMAC_CTX{CTX<:SHA_CTX} + context::CTX + outer::Vector{UInt8} + + function HMAC_CTX(ctx::CTX, key::Vector{UInt8}, blocksize::Integer=blocklen(CTX)) where CTX + if length(key) > blocksize + _ctx = CTX() + update!(_ctx, key) + key = digest!(_ctx) + end + + pad = blocksize - length(key) + + if pad > 0 + key = [key; fill(0x00, pad)] + end + + update!(ctx, key .⊻ 0x36) + new{CTX}(ctx, key .⊻ 0x5c) + end +end + +function update!(ctx::HMAC_CTX, data) + update!(ctx.context, data) +end + +function digest!(ctx::HMAC_CTX{CTX}) where CTX + digest = digest!(ctx.context) + _ctx = CTX() + update!(_ctx, ctx.outer) + update!(_ctx, digest) + digest!(_ctx) +end diff --git a/test/runtests.jl b/test/runtests.jl index 787db96ea10e9..abaeeb2f65ac4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -209,6 +209,32 @@ for sha_idx in 1:length(sha_funcs) end println("Done! [$(nerrors - nerrors_old) errors]") +# test hmac correctness using the examples on [wiki](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code#Examples) +print("Testing on the hmac functions") +nerrors_old = nerrors +for (key, msg, fun, hash) in ( + (b"", b"", hmac_sha1, "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d"), + (b"", b"", hmac_sha256, "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"), + (b"key", b"The quick brown fox jumps over the lazy dog", hmac_sha1, "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"), + (b"key", b"The quick brown fox jumps over the lazy dog", hmac_sha256, "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"), +) + digest = bytes2hex(fun(key, msg)) + if digest != hash + print("\n") + warn( + """ + For $fun($(String(key)), $(String(msg))) expected: + $hash + Calculated: + $digest + """) + nerrors += 1 + else + print(".") + end +end +println("Done! [$(nerrors - nerrors_old) errors]") + if VERSION >= v"0.7.0-DEV.1472" replstr(x) = sprint((io, x) -> show(IOContext(io, :limit => true), MIME("text/plain"), x), x) else