From 54b1fc15ea2846c80f4fc9d7b46dbf32aedf21c7 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Wed, 25 Nov 2015 14:25:40 -0800 Subject: [PATCH] Fairly cleanup, SHA3 and bytes instead of strings! * shaX_YYY() functions now return bytes instead of strings (Closes https://github.com/staticfloat/SHA.jl/issues/12) * Cleanup some of the SHA2 functions to be more readable * First draft of SHA3 algorithm, although it is pretty slow as-is. --- README.md | 28 +++++++-- src/SHA.jl | 46 +++++++++----- src/base_functions.jl | 2 + src/common.jl | 65 ++++++-------------- src/constants.jl | 32 ++++++++-- src/sha2.jl | 4 +- src/sha3.jl | 76 ++++++++++++++++++++++++ src/types.jl | 135 ++++++++++++++++++++++++++++++------------ test/perf.jl | 16 ++++- test/runtests.jl | 105 ++++++++++++++++++++++++-------- 10 files changed, 373 insertions(+), 136 deletions(-) create mode 100644 src/sha3.jl diff --git a/README.md b/README.md index b4525d04a5a3c..efcf8cfb98d0e 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ Usage is very straightforward: ``` julia> using SHA -julia> sha256("test") +julia> bytes2hex(sha256("test")) "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" ``` -Each exported function (at the time of this writing, only SHA-1, SHA-2 224, 256, 384 and 512 functions are implemented) takes in either an `Array{UInt8}`, a `ByteString` or an `IO` object. This makes it trivial to checksum a file: +Each exported function (at the time of this writing, SHA-1, SHA-2 224, 256, 384 and 512, and SHA-3 224, 256, 384 and 512 functions are implemented) takes in either an `Array{UInt8}`, a `ByteString` or an `IO` object. This makes it trivial to checksum a file: ``` shell> cat /tmp/test.txt @@ -22,9 +22,29 @@ test julia> using SHA julia> open("/tmp/test.txt") do f - sha256(f) + sha2_256(f) end -"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" +32-element Array{UInt8,1}: + 0x9f + 0x86 + 0xd0 + 0x81 + 0x88 + 0x4c + 0x7d + 0x65 + ⋮ + 0x5d + 0x6c + 0x15 + 0xb0 + 0xf0 + 0x0a + 0x08 ``` Note the lack of a newline at the end of `/tmp/text.txt`. Julia automatically inserts a newline before the `julia>` prompt. + +Due to the colloquial usage of `sha256` to refer to `sha2_256`, convenience functions are provided, mapping `shaxxx()` function calls to `sha2_xxx()`. For SHA-3, no such colloquialisms exist and the user must use the full `sha3_xxx()` names. + +Note that, at the time of this writing, the SHA3 code is not optimized, and as such is roughly an order of magnitude slower than SHA2. Pull requests are welcome. diff --git a/src/SHA.jl b/src/SHA.jl index b8f0e54db00f1..8aaae10b37760 100644 --- a/src/SHA.jl +++ b/src/SHA.jl @@ -1,39 +1,57 @@ -isdefined(Base, :__precompile__) && __precompile__() +#isdefined(Base, :__precompile__) && __precompile__() module SHA using Compat -export sha1, sha224, sha256, sha384, sha512 +# Export convenience functions, context types, update!() and digest!() functions +export sha1, SHA1_CTX, update!, digest! +export sha224, sha256, sha384, sha512 +export sha2_224, sha2_256, sha2_384, sha2_512 +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 + include("constants.jl") include("types.jl") include("base_functions.jl") include("sha1.jl") include("sha2.jl") +include("sha3.jl") include("common.jl") - # Create data types and convenience functions for each hash implemented for (f, ctx) in [(:sha1, :SHA1_CTX), (:sha224, :SHA224_CTX), (:sha256, :SHA256_CTX), (:sha384, :SHA384_CTX), - (:sha512, :SHA512_CTX)] + (:sha512, :SHA512_CTX), + (:sha2_224, :SHA2_224_CTX), + (:sha2_256, :SHA2_256_CTX), + (:sha2_384, :SHA2_384_CTX), + (:sha2_512, :SHA2_512_CTX), + (:sha3_224, :SHA3_224_CTX), + (:sha3_256, :SHA3_256_CTX), + (:sha3_384, :SHA3_384_CTX), + (:sha3_512, :SHA3_512_CTX),] @eval begin - # Allows things like: - # open("test.txt") do f - # sha256(f) - # done - function $f(io::IO) + # Our basic function is to process arrays of bytes + function $f(data::Array{UInt8,1}) ctx = $ctx() - update!(ctx, readbytes(io)); - return bytes2hex(digest!(ctx)) + update!(ctx, data); + return digest!(ctx) end - # Allows the same as above, but on ByteStrings and Arrays - $f(str::ByteString) = $f(IOBuffer(str)) - $f(arr::Array{UInt8,1}) = $f(IOBuffer(arr)) + # ByteStrings are a pretty handy thing to be able to crunch through + $f(str::ByteString) = $f(str.data) + + # Convenience function for IO devices, allows for things like: + # open("test.txt") do f + # sha256(f) + # done + $f(io::IO) = $f(readbytes(io)) end end diff --git a/src/base_functions.jl b/src/base_functions.jl index d99d54889fbf3..b5aeab32cb0df 100644 --- a/src/base_functions.jl +++ b/src/base_functions.jl @@ -17,6 +17,8 @@ R(b,x) = (x >> b) S32(b,x) = rrot(b,x,32) # 64-bit Rotate-right (used in SHA-384 and SHA-512): S64(b,x) = rrot(b,x,64) +# 64-bit Rotate-left (used in SHA3) +L64(b,x) = lrot(b,x,64) # Two of six logical functions used in SHA-256, SHA-384, and SHA-512: Ch(x,y,z) = ((x & y) $ (~x & z)) diff --git a/src/common.jl b/src/common.jl index 54fc43ca0d529..83c056a61317e 100644 --- a/src/common.jl +++ b/src/common.jl @@ -2,66 +2,39 @@ # update! takes in variable-length data, buffering it into blocklen()-sized pieces, # calling transform!() when necessary to update the internal hash state. -function update!{T<:SHA_CTX}(context::T, data::Array{UInt8,1}) - if length(data) == 0 - return - end +function update!{T<:Union{SHA1_CTX,SHA2_CTX,SHA3_CTX}}(context::T, data::Array{UInt8,1}) + # We need to do all our arithmetic in the proper bitwidth + UIntXXX = typeof(context.bytecount) - data_idx = 0 - len = convert(typeof(context.bytecount), length(data)) + # Process as many complete blocks as possible + len = UIntXXX(length(data)) + data_idx = UIntXXX(0) usedspace = context.bytecount % blocklen(T) - if usedspace > 0 - # Calculate how much free space is available in the buffer - freespace = blocklen(T) - usedspace - - if len >= freespace - # Fill the buffer completely and process it - for i in 1:freespace - context.buffer[usedspace + i] = data[data_idx + i] - end - - # Round bytecount up to the nearest blocklen - context.bytecount += freespace - data_idx += freespace - len -= freespace - transform!(context) - else - # The buffer is not yet full - for i = 1:len - context.buffer[usedspace + i] = data[data_idx + i] - end - context.bytecount += len - return + while len - data_idx + usedspace >= blocklen(T) + # Fill up as much of the buffer as we can with the data given us + for i in 1:(blocklen(T) - usedspace) + context.buffer[usedspace + i] = data[data_idx + i] end - end - - # Process as many complete blocks as possible, now that the buffer is full - data_idx = one(len) - while len - (data_idx - 1) >= blocklen(T) - for i in 1:blocklen(T) - context.buffer[i] = data[data_idx + i - 1] - end transform!(context) - data_idx += blocklen(T) + context.bytecount += blocklen(T) - usedspace + data_idx += blocklen(T) - usedspace + usedspace = UIntXXX(0) end - context.bytecount += (data_idx - 1) - # If there are leftovers, save them in buffer until next update!() or digest!() - if data_idx < len - # There's left-overs, so save 'em - for i = 1:(len - data_idx + 1) - context.buffer[i] = data[data_idx + i - 1] + # There is less than a complete block left, but we need to save the leftovers into context.buffer: + if len > data_idx + for i = 1:(len - data_idx) + context.buffer[usedspace + i] = data[data_idx + i] end - context.bytecount += (len - data_idx + 1) + context.bytecount += len - data_idx end end # Clear out any saved data in the buffer, append total bitlength, and return our precious hash! -function digest!{T<:SHA_CTX}(context::T) +function digest!{T<:Union{SHA1_CTX,SHA2_CTX}}(context::T) usedspace = context.bytecount % blocklen(T) - # If we have anything in the buffer still, pad and transform that data if usedspace > 0 # Begin padding with a 1 bit: diff --git a/src/constants.jl b/src/constants.jl index 34b24eae3c77b..fb5d5ef40be0a 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -33,13 +33,13 @@ const K256 = UInt32[ ] # Initial hash value H for SHA-224: -const SHA224_initial_hash_value = UInt32[ +const SHA2_224_initial_hash_value = UInt32[ 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 ] -const SHA256_initial_hash_value = UInt32[ +const SHA2_256_initial_hash_value = UInt32[ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ] @@ -89,7 +89,7 @@ const K512 = UInt64[ ] # Initial hash value H for SHA-384 -const SHA384_initial_hash_value = UInt64[ +const SHA2_384_initial_hash_value = UInt64[ 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, 0x67332667ffc00b31, 0x8eb44a8768581511, @@ -97,9 +97,33 @@ const SHA384_initial_hash_value = UInt64[ ] # Initial hash value H for SHA-512 -const SHA512_initial_hash_value = UInt64[ +const SHA2_512_initial_hash_value = UInt64[ 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 ] + +# Round constants for SHA3 rounds +const SHA3_ROUND_CONSTS = UInt64[ + 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, + 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, + 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, + 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, + 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, + 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, + 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, + 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 +] + +# Rotation constants for SHA3 rounds +const SHA3_ROTC = UInt64[ + 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, + 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 +] + +# Permutation indices for SHA3 rounds (+1'ed so as to work with julia's 1-based indexing) +const SHA3_PILN = UInt64[ + 11, 8, 12, 18, 19, 4, 6, 17, 9, 22, 25, 5, + 16, 24, 20, 14, 13, 3, 21, 15, 23, 10, 7, 2 +] diff --git a/src/sha2.jl b/src/sha2.jl index 96dac4aa6234a..921e0fe15a398 100644 --- a/src/sha2.jl +++ b/src/sha2.jl @@ -1,4 +1,4 @@ -function transform!{T<:SHA2_CTX_SMALL}(context::T) +function transform!{T<:Union{SHA2_224_CTX,SHA2_256_CTX}}(context::T) buffer = reinterpret(eltype(context.state), context.buffer) # Initialize registers with the previous intermediate values (our state) a = context.state[1] @@ -65,7 +65,7 @@ function transform!{T<:SHA2_CTX_SMALL}(context::T) end -function transform!(context::SHA2_CTX_BIG) +function transform!(context::Union{SHA2_384_CTX,SHA2_512_CTX}) buffer = reinterpret(eltype(context.state), context.buffer) # Initialize registers with the prev. intermediate value a = context.state[1] diff --git a/src/sha3.jl b/src/sha3.jl new file mode 100644 index 0000000000000..9acf5d84df7d3 --- /dev/null +++ b/src/sha3.jl @@ -0,0 +1,76 @@ +function transform!{T<:SHA3_CTX}(context::T) + # First, update state with buffer + buffer_as_uint64 = reinterpret(eltype(context.state), context.buffer) + for idx in 1:div(blocklen(T),8) + context.state[idx] $= buffer_as_uint64[idx] + end + bc = Array{UInt64,1}(5) + + # We always assume 24 rounds + for round in 0:23 + # Theta function + for i in 1:5 + bc[i] = context.state[i] $ context.state[i + 5] $ context.state[i + 10] $ context.state[i + 15] $ context.state[i + 20] + end + + for i in 1:5 + temp = bc[mod1(i + 4, 5)] $ L64(1, bc[mod1(i + 1, 5)]) + for j in 0:5:20 + context.state[i + j] $= temp + end + end + + # Rho Pi + temp = context.state[2] + for i in 1:24 + j = SHA3_PILN[i] + bc[1] = context.state[j] + context.state[j] = L64(SHA3_ROTC[i], temp) + temp = bc[1] + end + + # Chi + for j in 0:5:20 + for i in 1:5 + bc[i] = context.state[i + j] + end + for i in 1:5 + context.state[j + i] $= (~bc[mod1(i + 1, 5)] & bc[mod1(i + 2, 5)]) + end + end + + # Iota + context.state[1] $= SHA3_ROUND_CONSTS[round+1] + end + + return context.state +end + + + +# Finalize data in the buffer, append total bitlength, and return our precious hash! +function digest!{T<:SHA3_CTX}(context::T) + usedspace = context.bytecount % blocklen(T) + # If we have anything in the buffer still, pad and transform that data + if usedspace < blocklen(T) - 1 + # Begin padding with a 0x06 + context.buffer[usedspace+1] = 0x06 + # Fill with zeros up until the last byte + context.buffer[usedspace+2:end-1] = 0x00 + # Finish it off with a 0x80 + context.buffer[end] = 0x80 + else + # Otherwise, we have to add on a whole new buffer just for the zeros and 0x80 + context.buffer[end] = 0x06 + transform!(context) + + context.buffer[1:end-1] = 0x0 + context.buffer[end] = 0x80 + end + + # Final transform: + transform!(context) + + # Return the digest + return reinterpret(UInt8, context.state)[1:digestlen(T)] +end diff --git a/src/types.jl b/src/types.jl index 52dbf7115371d..0cfc61d487a9c 100644 --- a/src/types.jl +++ b/src/types.jl @@ -1,13 +1,12 @@ # Type hierarchy to aid in splitting up of SHA2 algorithms # as SHA224/256 are similar, and SHA-384/512 are similar abstract SHA_CTX -abstract SHA2_CTX_BIG <: SHA_CTX -abstract SHA2_CTX_SMALL <: SHA_CTX +abstract SHA2_CTX <: SHA_CTX +abstract SHA3_CTX <: SHA_CTX +import Base: copy - -# We derive SHA1_CTX straight from SHA_CTX since it doesn't need -# to follow the split between "big" and "small" algorithms like -# SHA2 needs to due to its two different Sigma_* functions +# We derive SHA1_CTX straight from SHA_CTX since it doesn't have a +# family of types like SHA2 or SHA3 do type SHA1_CTX <: SHA_CTX state::Array{UInt32,1} bytecount::UInt64 @@ -15,71 +14,131 @@ type SHA1_CTX <: SHA_CTX W::Array{UInt32,1} end -# SHA-224/256/384/512 Context Structures -type SHA224_CTX <: SHA2_CTX_SMALL +# SHA2 224/256/384/512-bit Context Structures +type SHA2_224_CTX <: SHA2_CTX state::Array{UInt32,1} bytecount::UInt64 buffer::Array{UInt8,1} end -type SHA256_CTX <: SHA2_CTX_SMALL +type SHA2_256_CTX <: SHA2_CTX state::Array{UInt32,1} bytecount::UInt64 buffer::Array{UInt8,1} end -type SHA384_CTX <: SHA2_CTX_BIG +type SHA2_384_CTX <: SHA2_CTX state::Array{UInt64,1} bytecount::UInt128 buffer::Array{UInt8,1} end -type SHA512_CTX <: SHA2_CTX_BIG +type SHA2_512_CTX <: SHA2_CTX + state::Array{UInt64,1} + bytecount::UInt128 + buffer::Array{UInt8,1} +end + +# Typealias common nicknames for SHA2 family of functions +typealias SHA224_CTX SHA2_224_CTX +typealias SHA256_CTX SHA2_256_CTX +typealias SHA384_CTX SHA2_384_CTX +typealias SHA512_CTX SHA2_512_CTX + + +# SHA3 224/256/384/512-bit context structures +type SHA3_224_CTX <: SHA3_CTX + state::Array{UInt64,1} + bytecount::UInt128 + buffer::Array{UInt8,1} +end +type SHA3_256_CTX <: SHA3_CTX + state::Array{UInt64,1} + bytecount::UInt128 + buffer::Array{UInt8,1} +end +type SHA3_384_CTX <: SHA3_CTX + state::Array{UInt64,1} + bytecount::UInt128 + buffer::Array{UInt8,1} +end +type SHA3_512_CTX <: SHA3_CTX state::Array{UInt64,1} bytecount::UInt128 buffer::Array{UInt8,1} end # Define constants via functions so as not to bloat context objects. Yay dispatch! -# The difference in block length here is part of what separates the "big" and "small" SHA2 algorithms -blocklen(::Type{SHA1_CTX}) = 64 -blocklen(::Type{SHA224_CTX}) = 64 -blocklen(::Type{SHA256_CTX}) = 64 -blocklen(::Type{SHA384_CTX}) = 128 -blocklen(::Type{SHA512_CTX}) = 128 +# Digest lengths for SHA1, SHA2 and SHA3. This is easy to figure out from the typename digestlen(::Type{SHA1_CTX}) = 20 -digestlen(::Type{SHA224_CTX}) = 28 -digestlen(::Type{SHA256_CTX}) = 32 -digestlen(::Type{SHA384_CTX}) = 48 -digestlen(::Type{SHA512_CTX}) = 64 +digestlen(::Type{SHA2_224_CTX}) = 28 +digestlen(::Type{SHA3_224_CTX}) = 28 +digestlen(::Type{SHA2_256_CTX}) = 32 +digestlen(::Type{SHA3_256_CTX}) = 32 +digestlen(::Type{SHA2_384_CTX}) = 48 +digestlen(::Type{SHA3_384_CTX}) = 48 +digestlen(::Type{SHA2_512_CTX}) = 64 +digestlen(::Type{SHA3_512_CTX}) = 64 +# SHA1 and SHA2 have differing element types for the internal state objects state_type(::Type{SHA1_CTX}) = UInt32 -state_type(::Type{SHA224_CTX}) = UInt32 -state_type(::Type{SHA256_CTX}) = UInt32 -state_type(::Type{SHA384_CTX}) = UInt64 -state_type(::Type{SHA512_CTX}) = UInt64 +state_type(::Type{SHA2_224_CTX}) = UInt32 +state_type(::Type{SHA2_256_CTX}) = UInt32 +state_type(::Type{SHA2_384_CTX}) = UInt64 +state_type(::Type{SHA2_512_CTX}) = UInt64 + +# blocklen is the number of bytes of data processed by the transform!() function at once +blocklen(::Type{SHA1_CTX}) = UInt64(64) +blocklen(::Type{SHA2_224_CTX}) = UInt64(64) +blocklen(::Type{SHA2_256_CTX}) = UInt64(64) +blocklen(::Type{SHA2_384_CTX}) = UInt64(128) +blocklen(::Type{SHA2_512_CTX}) = UInt64(128) + +blocklen(::Type{SHA3_224_CTX}) = UInt64(25*8 - 2*digestlen(SHA3_224_CTX)) +blocklen(::Type{SHA3_256_CTX}) = UInt64(25*8 - 2*digestlen(SHA3_256_CTX)) +blocklen(::Type{SHA3_384_CTX}) = UInt64(25*8 - 2*digestlen(SHA3_384_CTX)) +blocklen(::Type{SHA3_512_CTX}) = UInt64(25*8 - 2*digestlen(SHA3_512_CTX)) + # short_blocklen is the size of a block minus the width of bytecount -short_blocklen{T<:SHA_CTX}(::Type{T}) = blocklen(T) - 2*sizeof(state_type(T)) +short_blocklen{T<:Union{SHA1_CTX,SHA2_CTX}}(::Type{T}) = blocklen(T) - 2*sizeof(state_type(T)) +# Once the "blocklen" methods are defined, we can define our outer constructors for SHA types: +SHA2_224_CTX() = SHA2_224_CTX(copy(SHA2_224_initial_hash_value), 0, zeros(UInt8, blocklen(SHA2_224_CTX))) +SHA2_256_CTX() = SHA2_256_CTX(copy(SHA2_256_initial_hash_value), 0, zeros(UInt8, blocklen(SHA2_256_CTX))) +SHA2_384_CTX() = SHA2_384_CTX(copy(SHA2_384_initial_hash_value), 0, zeros(UInt8, blocklen(SHA2_384_CTX))) +SHA2_512_CTX() = SHA2_512_CTX(copy(SHA2_512_initial_hash_value), 0, zeros(UInt8, blocklen(SHA2_512_CTX))) -# Once the "blocklength" methods are defined, we can define our outer constructors for SHA types: -for ALG in ["SHA224", "SHA256", "SHA384", "SHA512"] - TYPE = symbol("$(ALG)_CTX") - HASH_VAL = symbol("$(ALG)_initial_hash_value") - @eval begin - $TYPE() = $TYPE(copy($HASH_VAL), 0, zeros(UInt8, blocklen($TYPE))) - end -end +SHA3_224_CTX() = SHA3_224_CTX(zeros(UInt64, 25), 0, zeros(UInt8, blocklen(SHA3_224_CTX))) +SHA3_256_CTX() = SHA3_256_CTX(zeros(UInt64, 25), 0, zeros(UInt8, blocklen(SHA3_256_CTX))) +SHA3_384_CTX() = SHA3_384_CTX(zeros(UInt64, 25), 0, zeros(UInt8, blocklen(SHA3_384_CTX))) +SHA3_512_CTX() = SHA3_512_CTX(zeros(UInt64, 25), 0, zeros(UInt8, blocklen(SHA3_512_CTX))) + +# Nickname'd outer constructor methods for SHA2 +SHA224_CTX = SHA2_224_CTX +SHA256_CTX = SHA2_256_CTX +SHA384_CTX = SHA2_384_CTX +SHA512_CTX = SHA2_512_CTX # SHA1 is special; he needs extra workspace SHA1_CTX() = SHA1_CTX(copy(SHA1_initial_hash_value), 0, zeros(UInt8, blocklen(SHA1_CTX)), Array(UInt32, 80)) + +# Copy functions +copy{T <: SHA1_CTX}(ctx::T) = T(copy(ctx.state), ctx.bytecount, copy(ctx.buffer), copy(ctx.W)) +copy{T <: SHA2_CTX}(ctx::T) = T(copy(ctx.state), ctx.bytecount, copy(ctx.buffer)) +copy{T <: SHA3_CTX}(ctx::T) = T(copy(ctx.state), ctx.bytecount, copy(ctx.buffer)) + + # Make printing these types a little friendlier import Base.show show(io::IO, ::SHA1_CTX) = write(io, "SHA1 hash state") -show(io::IO, ::SHA224_CTX) = write(io, "SHA-224 hash state") -show(io::IO, ::SHA256_CTX) = write(io, "SHA-256 hash state") -show(io::IO, ::SHA384_CTX) = write(io, "SHA-384 hash state") -show(io::IO, ::SHA512_CTX) = write(io, "SHA-512 hash state") +show(io::IO, ::SHA2_224_CTX) = write(io, "SHA2 224-bit hash state") +show(io::IO, ::SHA2_256_CTX) = write(io, "SHA2 256-bit hash state") +show(io::IO, ::SHA2_384_CTX) = write(io, "SHA2 384-bit hash state") +show(io::IO, ::SHA2_512_CTX) = write(io, "SHA2 512-bit hash state") +show(io::IO, ::SHA3_224_CTX) = write(io, "SHA3 224-bit hash state") +show(io::IO, ::SHA3_256_CTX) = write(io, "SHA3 256-bit hash state") +show(io::IO, ::SHA3_384_CTX) = write(io, "SHA3 384-bit hash state") +show(io::IO, ::SHA3_512_CTX) = write(io, "SHA3 512-bit hash state") diff --git a/test/perf.jl b/test/perf.jl index 6b76bc7274ddb..c130288f87d1a 100644 --- a/test/perf.jl +++ b/test/perf.jl @@ -21,15 +21,25 @@ function do_tests(filepath) gc() @time sha1(bytes) - print("SHA-256: ") + print("SHA2-256: ") sha256(bytes) gc() @time sha256(bytes) - print("SHA-512: ") + print("SHA2-512: ") sha512(bytes) gc() @time sha512(bytes) + + print("SHA3-256: ") + sha3_256(bytes) + gc() + @time sha3_256(bytes) + + print("SHA3-512: ") + sha3_512(bytes) + gc() + @time sha3_512(bytes) end -do_tests(ARGS[1]) \ No newline at end of file +do_tests(ARGS[1]) diff --git a/test/runtests.jl b/test/runtests.jl index 7d5322ef682c3..d7086de73dea9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,8 +8,12 @@ data = Any["", "test", lorem, so_many_as] # Descriptions of the data, the SHA functions we'll run on the data, etc... data_desc = ["the empty string", "the string \"test\"", "lorem ipsum", "one million a's"] -sha_types = [SHA.SHA1_CTX, SHA.SHA224_CTX, SHA.SHA256_CTX, SHA.SHA384_CTX, SHA.SHA512_CTX] -sha_funcs = [sha1, sha224, sha256, sha384, sha512] +sha_types =Dict(sha1 => SHA.SHA1_CTX, + sha2_224 => SHA.SHA2_224_CTX, sha2_256 => SHA.SHA2_256_CTX, sha2_384 => SHA.SHA2_384_CTX, sha2_512 => SHA.SHA2_512_CTX, + sha3_224 => SHA.SHA3_224_CTX, sha3_256 => SHA.SHA3_256_CTX, sha3_384 => SHA.SHA3_384_CTX, sha3_512 => SHA.SHA3_512_CTX) +sha_funcs = [sha1, + sha2_224, sha2_256, sha2_384, sha2_512, + sha3_224, sha3_256, sha3_384, sha3_512] answers = @compat Dict( sha1 => [ @@ -18,35 +22,71 @@ sha1 => [ "19afa2a4a37462c7b940a6c4c61363d49c3a35f4", "34aa973cd4c4daa4f61eeb2bdbad27316534016f", ], -sha224 => [ +sha2_224 => [ "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", "90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809", "6a0644abcf1e2cecbec2814443dab5f24b7ad8ebb66c75667ab67959", "20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67" ], -sha256 => [ +sha2_256 => [ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "2c7c3d5f244f1a40069a32224215e0cf9b42485c99d80f357d76f006359c7a18", "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0" ], -sha384 => [ +sha2_384 => [ "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", "768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9", "63980fd0425cd2c3d8a400ee0f2671ef135db03b947ec1af21b6e28f19c16ca272036469541f4d8e336ac6d1da50580f", "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985" ], -sha512 => [ +sha2_512 => [ "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", "f41d92bc9fc1157a0d1387e67f3d0893b70f7039d3d46d8115b5079d45ad601159398c79c281681e2da09bf7d9f8c23b41d1a0a3c5b528a7f2735933a4353194", "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b" +], +sha3_224 => [ +"6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", +"3797bf0afbbfca4a7bbba7602a2b552746876517a7f9b7ce2db0ae7b", +"ea5395370949ad8c7d2ca3e7c045ef3306fe3a3f4740de452ef87a28", +"d69335b93325192e516a912e6d19a15cb51c6ed5c15243e7a7fd653c" +], +sha3_256 => [ +"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", +"36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80", +"8c8142d2ca964ab307ace567ddd5764f17ebb76eb8ff25543ab54c14fe2ab139", +"5c8875ae474a3634ba4fd55ec85bffd661f32aca75c6d699d0cdcb6c115891c1" +], +sha3_384 => [ +"0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", +"e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7ff0f1b41eecb9db3ff219007c4e097260d58621bd", +"eb9fbba3eb916a4efe384b3125f5d03ceb9c5c1b94431ac30fa86c54408b92701ca5d2628cd7113aa5541177ec3ccd1d", +"eee9e24d78c1855337983451df97c8ad9eedf256c6334f8e948d252d5e0e76847aa0774ddb90a842190d2c558b4b8340" +], +sha3_512 => [ +"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", +"9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14", +"3a4318353396a12dfd20442cfce1d8ad4d7e732e85cc56b01b4cf9057a41c8827c0a03c70812e76ace68d776759225c213b4f581aac0dba5dd43b785b1a33fe5", +"3c3a876da14034ab60627c077bb98f7e120a2a5370212dffb3385a18d4f38859ed311d0a9d5141ce9cc5c66ee689b266a8aa18ace8282a0e0db596c90b0a7b87" ] ) -# Our code coverage reaches all the way to the show() methods for the SHA types, and gives us -# an excuse to give readers of this code a little mental parsing workout. -println("Loaded hash types: $(join([split(string(t()))[1] for t in sha_types], ", ", " and "))") +function describe_hash{S<:SHA.SHA_CTX}(T::Type{S}) + if T <: SHA.SHA1_CTX + return "SHA1" + end + + if T <: SHA.SHA2_CTX + return "SHA2-$(SHA.digestlen(T)*8)" + end + + if T <: SHA.SHA3_CTX + return "SHA3-$(SHA.digestlen(T)*8)" + end +end + +println("Loaded hash types: $(join(sort([describe_hash(t[2]) for t in sha_types]), ", ", " and "))") # First, test processing the data in one go nerrors = 0 @@ -54,13 +94,15 @@ for idx in 1:length(data) desc = data_desc[idx] print("Testing on $desc$(join(["." for z in 1:(34-length(desc))]))") nerrors_old = nerrors - for sha_func in sha_funcs - hash = sha_func(data[idx]) + for sha_idx in 1:length(sha_funcs) + sha_func = sha_funcs[sha_idx] + + hash = bytes2hex(sha_func(data[idx])) if hash != answers[sha_func][idx] print("\n") warn( """ - For $("$(sha_func)"[1:min(6,end)]) expected: + For $(describe_hash(sha_types[sha_func])) expected: $(answers[sha_func][idx]) Calculated: $(hash) @@ -78,17 +120,17 @@ end # in order to test multiple update!() calls print("Testing on one million a's (chunked properly)") nerrors_old = nerrors -for idx in 1:length(sha_funcs) - ctx = sha_types[idx]() +for sha_idx in 1:length(sha_funcs) + ctx = sha_types[sha_funcs[sha_idx]]() SHA.update!(ctx, so_many_as[1:2*SHA.blocklen(typeof(ctx))]) SHA.update!(ctx, so_many_as[2*SHA.blocklen(typeof(ctx))+1:end]) hash = bytes2hex(SHA.digest!(ctx)) - if hash != answers[sha_funcs[idx]][end] + if hash != answers[sha_funcs[sha_idx]][end] print("\n") warn( """ - For $("$(sha_funcs[idx])"[1:min(6,end)]) expected: - $(answers[sha_funcs[idx]][end-1]) + For $(describe_hash(sha_types[sha_funcs[sha_idx]])) expected: + $(answers[sha_funcs[sha_idx]][end-1]) Calculated: $(hash) """) @@ -104,18 +146,31 @@ println("Done! [$(nerrors - nerrors_old) errors]") # in order to test multiple update!() calls as well as the overflow codepaths print("Testing on one million a's (chunked clumsily)") nerrors_old = nerrors -for idx in 1:length(sha_funcs) - ctx = sha_types[idx]() - SHA.update!(ctx, so_many_as[1:round(Int, 1.3*SHA.blocklen(typeof(ctx)))]) - SHA.update!(ctx, so_many_as[round(Int, 1.3*SHA.blocklen(typeof(ctx)))+1:round(Int, 1.7*SHA.blocklen(typeof(ctx)))]) - SHA.update!(ctx, so_many_as[round(Int, 1.7*SHA.blocklen(typeof(ctx)))+1:end]) +for sha_idx in 1:length(sha_funcs) + ctx = sha_types[sha_funcs[sha_idx]]() + + # Get indices awkwardly placed for the blocklength of this hash type + idx0 = round(Int, 0.3*SHA.blocklen(typeof(ctx))) + idx1 = round(Int, 1.7*SHA.blocklen(typeof(ctx))) + idx2 = round(Int, 2.6*SHA.blocklen(typeof(ctx))) + + # Feed data in according to our dastardly blocking scheme + SHA.update!(ctx, so_many_as[0 + 1:1*idx0]) + SHA.update!(ctx, so_many_as[1*idx0 + 1:2*idx0]) + SHA.update!(ctx, so_many_as[2*idx0 + 1:3*idx0]) + SHA.update!(ctx, so_many_as[3*idx0 + 1:4*idx0]) + SHA.update!(ctx, so_many_as[4*idx0 + 1:idx1]) + SHA.update!(ctx, so_many_as[idx1 + 1:idx2]) + SHA.update!(ctx, so_many_as[idx2 + 1:end]) + + # Ensure the hash is the appropriate one hash = bytes2hex(SHA.digest!(ctx)) - if hash != answers[sha_funcs[idx]][end] + if hash != answers[sha_funcs[sha_idx]][end] print("\n") warn( """ - For $("$(sha_funcs[idx])"[1:min(6,end)]) expected: - $(answers[sha_funcs[idx]][end-1]) + For $(describe_hash(sha_types[sha_funcs[sha_idx]])) expected: + $(answers[sha_funcs[sha_idx]][end-1]) Calculated: $(hash) """)